02.6 defer关键字
defer关键字的作用是当外围函数返回之后才执行被推迟的函数。在文件输入输出操作中经常可以见到defer关键字,因为它使您不必记住何时关闭已打开的文件:defer关键字调用文件关闭函数关闭已打开的文件时,可以紧靠着文件打开函数之后。在第八章“告诉Unix系统要做什么”中,会介绍如何在文件相关操作中使用defer关键字,本节将介绍defer的其他用法。您还将在panic()recover()两个Go内置函数中看到defer操作。
首先记住一点重要的原则:defer函数在外围函数返回之后,以后进先出(LIFO)的原则执行。简单点说,在一个外围函数中有3个defer函数:f1()最先出现,然后f2(),最后f3(),当外围函数执行返回之后,f3()最先被执行,接着是f2(),最后是f1()
如果你感觉这个定义不是很清晰,查看并执行defer.go源码,这应该会让你理解的更清楚些。下面分三部分呈现这段源码。
源码第一部分:
1
package main
2
import (
3
"fmt"
4
)
5
func d1() {
6
for i := 3; i > 0; i-- {
7
defer fmt.Print(i, " ")
8
}
9
}
Copied!
上面的Go代码实现了一个名为d1()函数,函数里面有一个for循环和一个defer语句,这个defer语句将会执行三次。
第二部分源码:
1
func d2() {
2
for i := 3; i > 0; i-- {
3
defer func() {
4
fmt.Print(i, " ")
5
}()
6
}
7
fmt.Println()
8
}
Copied!
这部分代码实现了d2()函数,它也包含了一个for循环和一个defer语句,这个defer语句也会执行三次,但是这个defer执行的是匿名函数,并且匿名函数没有带参数。
最后一部分源码:
1
func d3() {
2
for i := 3; i > 0; i-- {
3
defer func(n int) {
4
fmt.Print(n, " ")
5
}(i)
6
}
7
}
8
func main() {
9
d1()
10
d2()
11
fmt.Println()
12
d3()
13
fmt.Println()
14
}
Copied!
这部分代码,定义了d3()函数,它包含了for循环defer语句,这个defer执行带参数的匿名函数,其中参数n使用的是循环中变量i的值。main()函数调用这三个函数。
执行源码会得到如下输出:
1
$ go run defer.go
2
1 2 3
3
0 0 0
4
1 2 3
Copied!
您很可能会发现生成的输出很复杂且难以理解。 这表明,如果您的代码不清晰或不明确,执行defer操作可能会产生非常棘手的结果。
让我们分析一下上面的结果,更进一步了解如果不密切关注自己的代码,defer将会变得多么棘手。第一行是d1()函数输出的(1 2 3),变量i的值在这个函数中顺序是3 、2和1 ,在d1()中延迟的函数是fmt.Print()。 因此,当d1()函数即将返回时,您将以相反的顺序获取for循环中变量i的三个值,因为被延迟的函数以LIFO顺序执行。
第二行是d2()函数的输出,很奇怪的是我们得到了单个0,而不是想象中的1、2和3 ,原因很简单,在循环结束后,i的值为0,因为是0值使循环终止。但是,这里棘手的部分是在循环结束后会执行被延迟的匿名函数。因为它没有参数,这意味着,将值为0的i进行三次打印输出! 在您的项目中,这种令人困惑的代码可能导致令人讨厌的错误,所以尽量避免它!
第三行是d3()函数的输出,因为匿名函数的参数的存在,每次匿名函数被推迟执行的时候,它都会获取并使用变量i当前的值,所以每次执行匿名函数都有不同的值输出。
到这里,你应该清楚了,使用defer的最好的方式就是第三个函数展示的方法,这是因为您故意以易于理解的方式在匿名函数中传递所需的变量。
Last modified 2yr ago
Copy link