函数中被defer关键字声明的语句会被延迟执行,延迟到函数结束之前才执行。
首先对于函数中的return语句,它是由两步组成,而非一个原子操作:
return=赋值给返回值+返回
func f1(x int) int { //x=5 defer func() { x++ }() return x }
如果传入的参数是5,这个函数的返回值是5,在函数最后返回的时候,首先创建一个匿名变量作为返回值,并且将x的值赋值给这个匿名变量,然后defer的延迟执行函数将x+1,但是此时已经和返回值没有关系了,因为x和返回值是两个变量,最后就是执行RET返回,所以一个函数的return语句是分两步执行,然后defer声明的语句则延迟到这两句之间执行
func f1(x int) (y int) { //x=5 defer func() { y++ }() return x }
如果这个时候传入的参数是5,这个函数的返回值是6,此时我们已经将返回值由匿名变量显示的变为变量y,所以执行的过程为首先将x的赋值给y,此时y的值是5,然后执行defer声明的语句,将y++,所以返回值这个变量的值变为了6,最后执行RET返回,因此最后的返回值是6.
func f1() (y int) { defer func(y int) { y++ }(5) return y }
这个函数的返回值是0,首先返回值y的类型是int,默认值是0,所以在返回的时候首先给y赋值为0,然后执行defer声明的函数,此时采用的是值传递,所以这个函数中的y其实是函数返回值的那个y的副本,他们是两个变量,所以此时修改y不能对函数返回值的那个y起作用,最后再返回。
func f1() (y int) { defer func(y *int) { *y++ }(&y) return 5 }
这个函数最后的返回值是6,过程如下,首先对函数的返回值y赋值为5,然后执行defer声明的函数,这个函数的入口参数是一个指针,所以采用的是地址传递而不是值传递,上面那个函数就是把返回值y的地址传入了defer的函数,所以最后修改了返回值y的值,y+1,所以最后返回值是6
然后再看一个比较复杂的例子,这个例子说明了在对defer的语句进行压栈的时候,会保存当前的状态:
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { a, b := 1, 2 // a=1 // b=2 defer calc("1", a, calc("10", a, b)) // defer的函数中还调用了函数,所以在对这条语句压栈的时候,会先执行内层的calc函数 // calc("10",1,2)==>输出"10" 1 2 3 // 然后对defer语句当前的状态压栈,当前a=1 // 所以压栈的语句为: // defer calc("1",1,3) a = 0 // a=0 defer calc("2", a, calc("20", a, b)) // defer的函数中还调用了函数,所以在对这条语句压栈的时候,会先执行内层的calc函数 // calc("20",0,2)==>输出"20" 0 2 2 // 然后对defer语句当前的状态压栈,当前a=0 // 所以压栈的语句为: // defer calc("2",0,2) b = 1 // b=1 // 然后执行压栈的defer语句 // calc("2",0,2)==>输出"2" 0 2 2 // calc("1",1,3)==>输出"1" 1 3 4 // 所以最后的输出结果是: // "10" 1 2 3 // "20" 0 2 2 // "2" 0 2 2 // "1" 1 3 4 }
从上面的代码可以看出两点,defer声明的函数内部如果还有函数调用(比如参数是另一个函数),那么会首先执行那个函数,并且得到一个结果,然后将这个结果压栈,另一点就是对于defer函数的变量,会在defer被压栈的时候保存这些变量当前的值,defer语句被压栈以后,再修改这些变量的值,并不会对defer语句中的这些变量产生影响。
defer和panic与recover
recover必须和defer搭配使用,defer必须定义在panic之前
当函数遇到panic语句的时候会退出,如果没有recover()语句,那么整个程序就会崩溃退出,在执行panic语句退出之前,会首先执行在panic语句之前定义的defer,如果在这个defer语句中有recover()函数,那么执行的结果就是recover()恢复错误之前的现场,并且仅仅退出这个函数,但是整个程序不会崩溃,而仅仅是这个函数退出了不再执行,但是整个程序依旧继续向后执行:
func f1() { fmt.Println("1") } func f2() { panic("there is a serious error") fmt.Println("2") } func f3() { fmt.Println("3") } func main() { f1() f2() f3() }
此时这段代码的输出为:
输出了panic的错误信息以后,整个程序直接崩溃,而不再执行main函数后面的信息
func f1() { fmt.Println("1") } func f2() { defer func() { err := recover() //recover()函数恢复了错误,结果是f2这个函数退出,整个程序不退出 if err != nil { fmt.Println("recover in function 2") } }() panic("there is a serious error") fmt.Println("2") } func f3() { fmt.Println("3") } func main() { f1() f2() f3() }
所以在上面的代码中只有f2函数退出了,后面的f3函数仍然继续执行,整个程序不会异常崩溃。结果如下: