什么时候在 Golang 的匿名函数中声明变量?

When do you declare variables in anonymous functions in Golang?

提问人:Greg T. Wallace 提问时间:8/9/2023 更新时间:8/9/2023 访问量:58

问:

我在代码中使用了一些匿名函数,并试图了解在函数中调用的这两个代码snipet之间的区别(如果有的话):

defer func(s *Service, ID string) {
    err := s.Deprovision(ID)
    if err != nil {
        s.logger.Error(err)
    }
}(service, identifier)


defer func() {
    err := service.Deprovision(identifier)
    if err != nil {
        service.logger.Error(err)
    }
}()

一个直接使用父函数中可用的变量,另一个实质上是在匿名函数中声明这些变量,然后指定要传入的父变量。

这有什么不同吗?如果是这样,我什么时候需要注意这一点?

函数 go 闭包匿名

评论

2赞 mkopriva 8/9/2023
这两个函数是否会做同样的事情取决于周围的函数对这两个变量做了什么。换言之,只有当语句与周围函数的主体端(或最后可能的退出)之间的周围函数更改了这两个变量时,才需要注意差异。defer
1赞 JimB 8/9/2023
您需要查看 的作用域,以及 -- 它们是否需要限定为匿名函数?serviceidentifier
2赞 mkopriva 8/9/2023
基本上,第一个延迟函数被赋予变量的值。第二个延迟函数“捕获”变量本身。如果变量的值在 defer 语句之后被修改,则第二个 deferred 函数将看到修改后的值,但第一个 deferred 函数不会,它将只看到传递给它的值。(这里的修改是指重新分配变量,例如,如果您只修改实例的一个字段,那么该修改对第一个字段也是可见的)defer*Servicedefer
1赞 Greg T. Wallace 8/9/2023
谢谢大家。我现在明白了。这在某种程度上是我所怀疑的,但我在任何地方都找不到明确的答案。非常感谢!

答:

1赞 Adem_Bc 8/9/2023 #1

您提供的两个代码片段都使用带有语句的匿名函数。但是,它们在如何捕获和使用封闭函数中的变量方面有所不同。让我们分解这些差异,并讨论何时需要注意它们:defer

  1. 直接使用变量(第一个代码段):在第一个代码片段中,匿名函数直接使用参数,并且来自封闭函数的作用域。这意味着这些变量由闭包捕获,可以直接在匿名函数中访问,而无需作为参数传递。sID

  2. 变量作为参数传递(第二个代码段):在第二个代码片段中,匿名函数不使用封闭函数作用域中的参数。相反,它从封闭函数的作用域中按值捕获 and 变量,然后在函数体中使用它们。这实质上等同于在匿名函数中声明和初始化新的局部变量。serviceidentifier

这有什么不同吗?

是的,这两种方法之间存在差异,它们之间的选择取决于您的特定用例。

  • 范围界定和清晰度:第一种方法更明确,对于阅读代码的人来说可能更清楚,因为它准确地显示了封闭作用域中正在使用哪些变量。这有助于防止潜在的错误并提高代码的可读性。

  • 性能和内存:第二种方法(按值捕获变量)对性能和内存使用的影响很小,尤其是在封闭函数的作用域包含大型对象的情况下。这是因为每个闭包都有自己的捕获变量副本,这可能会增加内存消耗。

何时要注意:

  1. 变量更改:如果变量的值(在您的例子中)在定义匿名函数之后但在执行之前(由于循环或其他控制结构)发生更改,则第二种方法的行为可能是意外的。serviceidentifier

  2. 闭包和 Goroutines:当将 goroutines 与闭包结合使用时,捕获的变量可能会对并发行为产生微妙的影响。确保您了解闭包如何捕获变量以及它们如何影响并发执行。

  3. 性能注意事项:如果内存使用是一个问题,或者按值捕获会导致性能问题,则可能需要考虑第一种方法,即直接使用现有变量。

在大多数情况下,这两种方法之间的差异可能并不显着,但在做出选择时了解这些注意事项是件好事。选择最适合您的特定用例并提高代码清晰度和可维护性的方法。

评论

0赞 Greg T. Wallace 8/10/2023
非常感谢!这是非常有帮助的。