一、 defer 用法
defer
是 golang 中独有的流程控制语句,用于延迟指定语句的运行时机,运行于函数的内部,当他所属函数运行完之后它才会被调用。
例如以下使用 defer 的代码:
1 2 3 4 |
func main() { defer fmt.Println("HelloDefer") fmt.Println("HelloWorld") } |
输出结果为:
1 2 |
HelloWorld HelloDefer |
它会先打印出 HelloWorld ,然后再打印出 HelloDefer 。一个函数中如果有多个 defer
,运行顺序和函数中的调用顺序相反,因为它们都是被写在了栈中:
1 2 3 4 5 |
func deferTest(){ defer fmt.Println("HelloDefer1") defer fmt.Println("HelloDefer2") fmt.Println("HelloWorld") } |
运行结果:
1 2 3 |
fmt.Println("HelloDefer2") fmt.Println("HelloDefer1") fmt.Println("HelloWorld") |
二、 defer 和 return
在包含有 return 语句的函数中,defer 的运行顺序位于 return 之后,但是 defer 所运行的代码片段会生效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func deferReturn() int { i := 1 defer func() { fmt.Println("Defer") i += 1 }() return func() int { fmt.Println("Return") return i }() } func main(){ fmt.Println(deferReturn) } |
运行结果:
1 2 3 |
Return Defer 1 |
这里很明显就能看到 defer 是在 return 之后运行的!但是有一个问题是 defer 里执行了语句 i += 1
,按照这个逻辑的话返回的 i
值应该是 2
而不是 1
。这个问题是由于 return 的运行机制导致的:return 返回一个对象时,如果返回类型不是指针或者引用类型,那么 return 返回的就不是这个对象本身,而是这个对象的副本。也就是说,实际上当 defer 执行的时候,return 已经把值返回了,返回的是一个副本值,此时 defer 对值的修改就无效了。
如果返回的是引用值,defer 的改动是有效的。我们可以把返回值得 int 类型改成*int:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func deferAndReturn() *int { i := 1 defer func() { fmt.Println("Defer:\t", i, &i) i += 1 }() return func() *int { fmt.Println("Return:\t", i, &i) return &i }() } func main() { x := deferAndReturn() fmt.Println("main:\t", *x, x) } |
程序的输出为:
1 2 3 |
Return: 1 0xc000014060 Defer: 1 0xc000014060 main: 2 0xc000014060 |
可以看到,在 return 和 defer 执行前,i 的值都是 1,并且地址一样。返回到 main 函数后,i 的地址也是和函数体的一样,但是值是 2 了,这就说明 defer 中对 i 的修改生效了。
三、 defer 和 panic
panic 会在 defer 运行完之后才把恐慌扩散到其他函数:
1 2 3 4 |
func deferPanic(){ defer fmt.Println("HelloDefer") panic("Hey, I'm panic") } |
结果:
1 2 3 4 5 6 7 8 |
HelloDefer //会先输出 defer 中的语句 panic: Hey, I'm panic goroutine 1 [running]: main.deferPanic() /Users/maqian/code/go/src/awesomeProject/defer/defer.go:21 +0xb9 main.main() /Users/maqian/code/go/src/awesomeProject/defer/defer.go:29 +0x82 |
四、 defer 和 for 循环
不要在 defer 内使用外部变量,可能会造成一些意想不到的错误:
1 2 3 4 5 6 7 |
func deferTest() { for i := 0; i < 5; i++ { defer func() { fmt.Printf("%d", i) }() } } |
它的输出的结果是 55555 ,原理很简单,因为 defer 会在 for 循环运行完后才会调用,for 循环运行完时 i 的值为 5,所以打印的 i 值会是 55555 。
正确的做法是不要在 defer 函数中使用共享变量,而是手动传入参数:
1 2 3 4 5 6 7 |
func deferTest() { for i := 0; i < 5; i++ { defer func(i int) { fmt.Printf("%d", i) }(i) } } |
此时就会打印出 43210 而不是 55555 了。
评论