你是否是以爲defer很簡單、很好用,但也許你掉坑裏了都不知道!golang
這篇文章不介紹defer的經常使用功能,而是介紹你在用defer時,也許會踩的坑。數據庫
defer容許咱們進行一些函數執行完成後的收尾工做,而且代碼更加簡潔,例如:c#
關閉文件流:函數
// open a file defer file.Close()
解鎖一個加鎖的資源測試
mu.Lock() defer mu.Unlock()
打印最終報告lua
printHeader() defer printFooter()
關閉數據庫連接spa
// open a database connection defer disconnectFromDB()
可是:3d
我曾經在Stack Overflow討論過這個問題,有興趣的能夠看下,本打算在週末寫個文章分享給你們defer的坑,今天不當心瀏覽到一個有誤解的文章,決定如今就寫下來,但願你們不要踩坑。code
若是下面這段代碼的結果你都知道,恭喜你,你已經瞭解defer的執行原理,沒有必要再看這篇文章了。blog
func test1() (x int) { defer fmt.Printf("in defer: x = %d\n", x) x = 7 return 9 } func test2() (x int) { x = 7 defer fmt.Printf("in defer: x = %d\n", x) return 9 } func test3() (x int) { defer func() { fmt.Printf("in defer: x = %d\n", x) }() x = 7 return 9 } func test4() (x int) { defer func(n int) { fmt.Printf("in defer x as parameter: x = %d\n", n) fmt.Printf("in defer x after return: x = %d\n", x) }(x) x = 7 return 9 } func main() { fmt.Println("test1") fmt.Printf("in main: x = %d\n", test1()) fmt.Println("test2") fmt.Printf("in main: x = %d\n", test2()) fmt.Println("test3") fmt.Printf("in main: x = %d\n", test3()) fmt.Println("test4") fmt.Printf("in main: x = %d\n", test4()) }
你已經計算出結果了嗎?看看和運行結果是否是同樣的,若是不同繼續閱讀本文吧:
test1 in defer: x = 0 in main: x = 9 test2 in defer: x = 7 in main: x = 9 test3 in defer: x = 9 in main: x = 9 test4 in defer x as parameter: x = 0 in defer x after return: x = 9 in main: x = 9
要想知道爲什麼是這個結果,就得先回答前面的2個問題:
依次來回答,這2個問題。
問題1:defer在defer語句處執行,defer的執行結果是把defer後的函數壓入到棧,等待return或者函數panic後,再按先進後出的順序執行被defer的函數。
問題2:defer的函數的參數是在執行defer時計算的,defer的函數中的變量的值是在函數執行時計算的。
defer及defer函數的執行順序分2步:
這4個測試函數中,都是return 9
而且沒有對返回值進行修改,因此main中都是in main: x = 9
,我相信這個你們應該是沒有疑問的。接下來看每一個測試函數defer的打印。
test1:defer執行時,對Printf
的入參x進行計算,它的值是0,而且傳遞給函數,return 9
後執行Printf
,因此結果是in defer: x = 0
。
test2:與test1相似,不一樣僅是,defer執行是在x=7
以後,因此x的值是7,而且傳遞給Printf
,因此結果是:in defer: x = 7
。
test3:defer後跟的是一個匿名函數,匿名函數能訪問外部函數的變量,這裏訪問的是test3的x,defer執行時,匿名函數沒有入參,因此把func()()
壓入到棧,return語句以後,執行func()()
,此時匿名函數得到x的值是9,因此結果是in defer: x = 9
。
test4:與test3的不一樣是,匿名函數有一個入參n,咱們把x做爲入參打印,還有就是匿名函數訪問外部打印x。defer執行時,x=0
,因此入棧的函數是func(int)(0)
,return語句以後執行func(int)(0)
,即n=0
,x在匿名函數內沒有定義,依然訪問test4中的x,此時x=9
,因此結果爲:in defer x as parameter: x = 0, in defer x after return: x = 9
。
最後,看下誤解讀者文章的截圖,看看你能不能發現那篇文章做者的思路問題。
上文的做者的目的想知道defer是在return以前,仍是以後執行,因此作了這麼個測試,他把上面的代碼和修改爲下面的代碼,發現等效後,就給出了錯誤結論:defer確實是在return以前調用的。
等效的能證實,順序嗎?請各位自行思考吧。
Golang對於defer的介紹)很精簡,可是把上面提到的問題都說清楚了,我也是讀了幾遍和其餘人交流,才徹底理解透,不妨好好讀讀,最核心的一句:
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.
若是這篇文章對你有幫助,請點個贊/喜歡,讓我知道個人寫做是有價值的,感謝。