defer和go语句将函数调用作为参数

这是一个非常简单的错误,它是编译器有时会为你捕获的错误,但并非总是如此。 当你使用defer或go 在 golang中时,你需要传递一个函数调用作为参数,而不仅仅是一个函数声明。

如果你在内联创建闭包,很难犯这个错误,因为编译器会抓住它。


此代码将导致go / defer中的编译错误表达式必须是函数调用,因此很难捕获,但是假设你有一个函数来设置应用程序服务器并返回一个应该在之前运行的函数,你的应用程序退出以处理关闭数据库连接或清除其他任何需要清除的内容。 该功能可能看起来像这样。


现在假设我们在main()函数中使用它来设置我们的服务器,并且我们想要推迟返回的函数。 这是错误滑入的地方,你可能需要仔细检查代码以捕获它。


你对输出的期望是什么?

继续运行代码。 你将获得以下输出。

什么地方出了错? 为什么我们看不到输出“ output pretend to tear things down”?

证明是defer将函数调用作为参数,而不是函数。 这很重要,因为这意味着我们的代码实际上是将调用推迟到setup(),并且它实际上从不运行返回的函数。

相反,我们真正想要的是以下内容。


值得注意的是,这种设置/拆卸策略是在代码中使用闭包的另一种好方法!

看到不同? 它非常微妙 - 在调用setup()之后我们需要第二个()因为我们告诉程序延迟调用setup()返回的函数。

更明确的方式可能是:


在for循环中声明的变量通过引用传递

在for循环中声明一个新变量时,重要的是要记住每次迭代都不会重新声明变量。 相反,变量是相同的,而是更新存储在变量中的值。

让我们看一个如何导致闭包问题的示例。


你期望这个输出什么?

你将获得的输出是:


我们在这里遇到的问题是我在for循环中被声明,并且它随着for循环的每次迭代而被更改。 当我们最终在函数切片中调用所有函数时,它们都引用了相同的i变量,该变量在for循环的最后一次迭代中设置为10。

如果使用range,也会发生同样的事情。 这是一个类似的示例,但它使用关键字range。


在这个例子中,我们的输出将是:


这是由于以前相同的问题引起的。 我们不是在每个闭包中使用val的值,而是引用随循环的每次迭代而改变的变量。

那么,我们该如何解决呢? 一种方法是利用Go中的函数参数按值传递的事实。 这意味着如果我们在for循环中调用函数doStuff(i),它会将i的值作为特定时间的参数传递给函数,而不是对i变量的引用。

以下是此操作的示例:


如果我们不想全局创建build()函数怎么办?

不幸的是,这个例子要求我们全局创建build()函数。 如果我们必须为我们想要创建的每个闭包执行此操作,我们的代码可能会很快变得非常拥挤。 特别是如果我们只想创建一个真正基本的闭包。

幸运的是,我们仍然可以通过使用匿名函数来解决这个问题,但要小心这种方法,因为如果它变得太大,很快就会变得难以阅读,理解和维护。

这是与之前相同的示例,但它使用匿名函数来创建闭包。


让我们花一点时间来了解这里发生的事情。

首先,我们声明一个内联函数,它接受一个整数值并返回一个函数。


这就像任何其他匿名函数一样,除了我们不将它分配给变量。 相反,我们立即使用i作为传入的参数来调用函数。这是函数声明之后的(i)部分。

在调用匿名函数之后,它返回一个func(),然后使用line functions = append(functions,...)将其附加到函数slice。

如果这看起来仍然令人困惑,让我们看一下使用相同匿名函数的另一个例子,但这次我们将它分配给一个变量。


注意我们是如何不将fn添加到函数切片中的,但是我们传递的是func()的返回值。

在Reddit上也指出,你也可以通过创建一个新变量并用i的值赋值来解决这个问题。 以下是此方法的示例。


你甚至可以使用i作为你的新变量(就像最初看起来一样奇怪),所以你的代码可以改为读取i:= i而不是j:= i。 这称为阴影变量,如果被滥用,可能会导致一些混乱的错误。