简析go语言defer关键字后表达式求值时机
在go中,只有在函数和方法内部才能使用defer;defer关键字后面只能接函数或者方法,这些函数被称为deferred函数。defer将他们注册到其所在goroutine用于存放deferred函数的栈数据结构中,这些deferred函数将在执行defer的函数退出前按后进先出(LIFO)的顺序调度执行。无论是执行到函数体尾部返回,还是在某个错误处理分支显式调用return返回,抑或出现panic,已经存储到deferred函数栈中的函数都会被调度执行。
defer关键字后表达式求值时机如何?下面看个例子:
func foo1() { for i := 0; i <= 3; i++ { defer fmt.Println(i) } } func foo2() { for i := 0; i <= 3; i++ { defer func(n int) { fmt.Println(n) }(i) } } func foo3() { for i := 0; i <= 3; i++ { defer func() { fmt.Println(i) }() } } func main() { fmt.Println("foo1:") foo1() fmt.Println("foo2:") foo2() fmt.Println("foo3:") foo3() }
执行结果如下:
foo1: 3 2 1 0 foo2: 3 2 1 0 foo3: 4 4 4 4
分析以上执行结果:
在foo1中,defer后面是fmt.Println函数,每当defer将fmt.Println注册的deferred函数栈的时候,都会对fmt.Println后边的参数进行求值,故依次压入deferred函数栈是:fmt.Println(0) fmt.Println(1) fmt.Println(2) fmt.Println(3)。在foo1执行到函数体尾部,deferred函数被执行,根据LIFO原则,因此输出3 2 1 0。
在foo2中,defer后面是一个带参数的匿名函数,每当defer将匿名函数注册的deferred函数栈的时候,都会对匿名函数的参数进行求值,故依次压入deferred函数栈是: func(0) func(1) func(2) func(3)。在foo2执行到函数体尾部,deferred函数被执行,根据LIFO原则,因此输出3 2 1 0。
在foo3中,defer后面是一个不带参数的匿名函数,故依次压入deferred函数栈是: func() func() func() func()。在foo3执行到函数体尾部,deferred函数被执行,匿名函数以闭包的方式访问外围函数的变量,此时i=4,根据LIFO原则,因此输出4 4 4 4。
结论:defer关键字后边的表达式实在将deferred函数注册到deferred函数栈的时候进行求值的!
Go语言学习笔记、语法知识、技术要点和个人理解及实战