這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
一、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函數中的i的地址和deferReturn()中的i的地址是不一樣的
|
如果把函數的傳回值改成指標類型,這時候的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
了。