💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## defer 关键字 `defer`关键字将函数的执行推迟到周围的函数返回之前,这在文件输入和输出操作中被广泛使用,因为它使你不必记住何时关闭打开的文件。使用`defer`关键字,可以将关闭已打开文件的函数调用放在打开该文件的函数调用附近。在第 8 章“告诉 UNIX 系统该做什么”中你将学习`defer`在与文件相关的操作,在本节将介绍`defer`的两种不同用法。你还将在讨论`panic()`和`restore()`内置 Go 函数的部分以及与日志记录相关的部分中看到`defer`。 重要的是要记住,延迟函数在返回周围函数后以后进先出(LIFO)的顺序执行。简而言之,这意味着如果在同一个周围函数中先延迟函数`f1()`,然后再延迟函数`f2()`,然后再延迟函数`f3()`,则当周围函数即将返回时,将执行函数`f3()`,然后`f2()`,最后才是`f3()`。 由于对`defer`的定义尚不清楚,我认为你可以通过查看 Go 代码和`defer.go`程序的输出来更好地理解`defer`的用法,该代码将分为三部分。 第一部分的代码: ```Go package main import ( "fmt" ) func d1() { for i := 3; i > 0; i-- { defer fmt.Print(i, " ") } } ``` 除了 import 块,前面的 Go 代码还实现了一个名为`d1()`的函数。包含有`for`循环和`defer`语句,该语句将执行 3 次。 第二部分的代码: ```Go func d2() { for i := 3; i > 0; i-- { defer func() { fmt.Print(i, " ") }() } fmt.Println() } ``` 在这部分代码中,你可以看到另一个函数的实现,该函数名为`d2()`。`d2()`函数还包含一个`for`循环和一个`defer`语句,它们还将执行 3 次。但是,这一次,将 defer 关键字应用于匿名函数,而不是单个`fmt.Print()`语句。此外,匿名函数不带任何参数。 最后一部分的代码: ```Go func d3() { for i := 3; i > 0; i-- { defer func(n int) { fmt.Print(n, " ") }(i) } } func main() { d1() d2() fmt.Println() d3() fmt.Println() } ``` 除了调用`d1(),d2()和d3()`函数的`main()`函数之外,你还可以看到`d3()`函数的实现,该函数具有一个`for`循环,该循环使用匿名的`defer`关键字功能。但是,这一次,匿名函数需要一个名为`n`的整数参数。Go 代码告诉我们,`n`参数从`for`循环中使用的`i`变量中获取其值。 执行`defer.go`将会得的输出: ```shell $ go run defer.go 1 2 3 0 0 0 1 2 3 ``` 你很可能会发现生成的输出非常复杂且难以理解,这证明如果代码不清晰,则操作和使用`defer`的结果可能会很棘手。 我们将从`d1()`函数生成的第一行输出`(1 2 3)`开始。`d1()`中`i`的值依次为`3、2和1`。 `d1()`中延迟的函数是 fmt.Print()语句;结果,当`d1()`函数将要返回时,你将以相反的顺序获得 for 循环的 i 变量的三个值,因为延迟的函数以 LIFO 顺序执行。 现在,让我解释一下`d2()`函数产生的第二行输出。我们得到三个零而不是`1 2 3`真是奇怪。但是,原因很简单。 在`for`循环结束之后,`i`的值为 0,因为正是`i`的值使`for`循环终止。但是,这里的棘手之处在于,因为没有参数,所以在 for 循环结束后评估了延迟的匿名函数,这意味着对于`i`值为 0,它会被评估三次,因此生成了输出。这种令人困惑的代码可能会导致在你的项目中创建烦人的错误,因此请避免使用它。 最后,我们将讨论输出的第三行,它是由`d3()`函数生成的。由于匿名函数的参数,每次推迟匿名函数时,它都会获取并因此使用`i`的当前值。结果,匿名函数的每次执行都需要处理不同的值,因此产生的输出也不同。 所以你应该清楚,使用`defer`的最佳方法是第三种方法,它出现在`d3()`函数中,这种方法以易于理解的方式在匿名函数中传递了所需的变量。