一、defer概述
defer
是golang
中独有的流程控制语句,用于延迟指定语句的运行时机,只能运行于函数的内部,且当他所属函数运行完之后它才会被调用。例如:
1 2 3 4
| func deferTest(){ defer fmt.Println("HelloDefer") fmt.Println("HelloWorld") }
|
它会先打印出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
| func main(){ fmt.Println(deferReturn) } func deferReturn() int{ i := 1 defer func(){ fmt.Println("Defer") i += 1 }() return func()int{ fmt.Println("Return") return i }() }
|
运行结果:
这里很明显就能看到defer
是在return
之后运行的!但是有一个问题是defer
里执行了语句i +=1
,按照这个逻辑的话返回的i
值应该是2
而不是1
。这个问题是由于return
的运行机制导致的:return
在返回一个对象时,如果返回类型不是指针或者引用类型,那么return
返回的就不会是这个对象本身,而是这个对象的副本。
我们可以验证这一个观点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func main(){ ... fmt.Println("main: ", x, &x) } func deferReturn() int{ ... defer ...{ fmt.Println("Defer: ", i, &i) ... }() return ...{ fmt.Println("Return: ", i, &i) ... }() }
|
程序的输出为:
1 2 3
| Return: 1 0xc042008238 Defer: 1 0xc042008238 main: 1 0xc042008230
|
如果把函数的返回值改成指针类型,这时候的main函数中的返回值就会和函数体内的一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func main(){ x := deferReturn() fmt.Println("main: ", x, *x) } func deferReturn()*int{ i := 1 p := &i defer func() { *p += 1 fmt.Println("defer: ", p, *p) }() return func() *int{ fmt.Println("Return: ", p, *p) return p }() }
|
结果:
1 2 3
| Return: 0xc0420361d0 1 defer: 0xc0420361d0 2 main: 0xc0420361d0 2
|
三、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
| HelloDefer //会先输出defer部分的代码 panic: Hey, I"m panic goroutine 1 [running]: main.deferTest() E:/code/golang/src/test_src/defer/main.go:12 +0xfc main.main() E:/code/golang/src/test_src/defer/main.go:6 +0x27
|
四、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
了。