你是否是以为defer很简单、很好用,但也许你掉坑里了都不知道!golang
这篇文章不介绍defer的经常使用功能,而是介绍你在用defer时,也许会踩的坑。数据库
defer容许咱们进行一些函数执行完成后的收尾工做,而且代码更加简洁,例如:c#
关闭文件流:函数
// open a file defer file.Close()
解锁一个加锁的资源测试
mu.Lock() defer mu.Unlock()
打印最终报告lua
printHeader() defer printFooter()
关闭数据库连接spa
// open a database connection defer disconnectFromDB()
可是:3d
我曾经在Stack Overflow讨论过这个问题,有兴趣的能够看下,本打算在周末写个文章分享给你们defer的坑,今天不当心浏览到一个有误解的文章,决定如今就写下来,但愿你们不要踩坑。code
若是下面这段代码的结果你都知道,恭喜你,你已经了解defer的执行原理,没有必要再看这篇文章了。blog
func test1() (x int) { defer fmt.Printf("in defer: x = %d\n", x) x = 7 return 9 } func test2() (x int) { x = 7 defer fmt.Printf("in defer: x = %d\n", x) return 9 } func test3() (x int) { defer func() { fmt.Printf("in defer: x = %d\n", x) }() x = 7 return 9 } func test4() (x int) { defer func(n int) { fmt.Printf("in defer x as parameter: x = %d\n", n) fmt.Printf("in defer x after return: x = %d\n", x) }(x) x = 7 return 9 } func main() { fmt.Println("test1") fmt.Printf("in main: x = %d\n", test1()) fmt.Println("test2") fmt.Printf("in main: x = %d\n", test2()) fmt.Println("test3") fmt.Printf("in main: x = %d\n", test3()) fmt.Println("test4") fmt.Printf("in main: x = %d\n", test4()) }
你已经计算出结果了吗?看看和运行结果是否是同样的,若是不同继续阅读本文吧:
test1 in defer: x = 0 in main: x = 9 test2 in defer: x = 7 in main: x = 9 test3 in defer: x = 9 in main: x = 9 test4 in defer x as parameter: x = 0 in defer x after return: x = 9 in main: x = 9
要想知道为什么是这个结果,就得先回答前面的2个问题:
依次来回答,这2个问题。
问题1:defer在defer语句处执行,defer的执行结果是把defer后的函数压入到栈,等待return或者函数panic后,再按先进后出的顺序执行被defer的函数。
问题2:defer的函数的参数是在执行defer时计算的,defer的函数中的变量的值是在函数执行时计算的。
defer及defer函数的执行顺序分2步:
这4个测试函数中,都是return 9
而且没有对返回值进行修改,因此main中都是in main: x = 9
,我相信这个你们应该是没有疑问的。接下来看每一个测试函数defer的打印。
test1:defer执行时,对Printf
的入参x进行计算,它的值是0,而且传递给函数,return 9
后执行Printf
,因此结果是in defer: x = 0
。
test2:与test1相似,不一样仅是,defer执行是在x=7
以后,因此x的值是7,而且传递给Printf
,因此结果是:in defer: x = 7
。
test3:defer后跟的是一个匿名函数,匿名函数能访问外部函数的变量,这里访问的是test3的x,defer执行时,匿名函数没有入参,因此把func()()
压入到栈,return语句以后,执行func()()
,此时匿名函数得到x的值是9,因此结果是in defer: x = 9
。
test4:与test3的不一样是,匿名函数有一个入参n,咱们把x做为入参打印,还有就是匿名函数访问外部打印x。defer执行时,x=0
,因此入栈的函数是func(int)(0)
,return语句以后执行func(int)(0)
,即n=0
,x在匿名函数内没有定义,依然访问test4中的x,此时x=9
,因此结果为:in defer x as parameter: x = 0, in defer x after return: x = 9
。
最后,看下误解读者文章的截图,看看你能不能发现那篇文章做者的思路问题。
上文的做者的目的想知道defer是在return以前,仍是以后执行,因此作了这么个测试,他把上面的代码和修改为下面的代码,发现等效后,就给出了错误结论:defer确实是在return以前调用的。
等效的能证实,顺序吗?请各位自行思考吧。
Golang对于defer的介绍)很精简,可是把上面提到的问题都说清楚了,我也是读了几遍和其余人交流,才彻底理解透,不妨好好读读,最核心的一句:
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.
若是这篇文章对你有帮助,请点个赞/喜欢,让我知道个人写做是有价值的,感谢。