Golang 中的 defer 关键字
0x00 defer 是啥
用一段简单的代码演示
package main
import (
"fmt"
)
func main() {
defer fmt.Println("this defer fmt!!")
fmt.Println("this is normal fmt!!")
}
以上代码的输出如下:
defer 的作用就是注册一个方法的调用,该方法将在程序返回后进行输出。顺带,defer 的执行队列是一个链表,当使用一次 defer ,就会在链表的头结点插入一个执行结点,然后函数返回执行 defer 注册的操作时会依次取头结点执行,所以最后呈现的效果就是 “先入后出” 的执行顺序。
0x01 实诚的建议
defer 很好用,特别是用来关闭一切需要显式关闭的对象时,比如 file 的读写对象,golang 里协程的计数器 sync.WaitGroup ,在创建了一个类似的对象后为其注册一个 defer *.close() 调用绝对是一个很明智的选择。
除此之外不建议用 defer,如果用 defer 来计算函数返回某些数值的话,往往会得到预期之外的结果。举个栗子:
func defer_test1(){
a, b := 1, 2
defer add(a, b)
a++
b++
}
func add (a,b int){
fmt.Println(a + b)
}
上述代码将输出 3 而不是使用 defer 设想的 5,出现这个问题的原因是因为 golang 中 defer 底层仍然采用的是值传递,当我们使用 defer 关键字时,它会向 defer 的执行队列里面拷贝引用的外层参数,比如上面这串代码中的 add 方法就是外层参数,那么内层参数 golang 又会怎么处理呢?上面说到 defer 的函数调用也是值传递的,它会将内层参数直接计算出来然后传递给外层参数,也就是 a = 1, b = 2 ,所以计算出来是 3
这个时候就肯定有人想,既然是因为值传递引起的这个问题,那么我强制使用指针传递引用会发生什么呢?结果显而易见,引用传递的值将得到预期的 5
func defer_test1(){
a, b := 1, 2
defer add(&a, &b)
a++
b++
}
func add (a,b *int){
fmt.Println(*a + *b)
}
但是,你以为这就结束了嘛 -~-,当然没有!如果吧代码改成下面这样呢
func defer_test1(){
a, b := 1, 2
defer fmt.Println(add(&a, &b))
a++
b++
}
func add (a,b *int) int{
fmt.Println(*a + *b)
return *a + *b
}
func add2(a, b int) int{
return a + b
}
会输出 3 还是 5 ?答案是 3
对于这个输出的理解,我是这样子理解的:对于 defer 来说外层参数为 fmt.Println 内层参数为 add 这里会拷贝 add 的值,也就是计算后的返回值 3。再写个代码检验一下。
func defer_test1(){
a, b := 1, 2
defer add2(add(&a, &b), add(&a, &b))
a++
b++
}
func add (a,b *int) int{
fmt.Println(*a + *b)
return *a + *b
}
func add2(a, b int) int{
return a + b
}
预期结果是 6 ,实际输出跟预期一致
0x02 不需要建议
那如果非要在结束时计算呢?我们可以使用匿名函数的方式来达到效果。
func defer_test1(){
a, b := 1, 2
defer func (){
fmt.Println(a + b)
}()
a++
b++
}
输出:
如上,就能够达到我们的目的。
鸣谢
《Go 语言设计与实现》第 5.3 节