你知道defer的坑嗎?
你是不是覺得defer很簡單、很好用,但也許你掉坑裡了都不知道!
這篇文章不介紹defer的常用功能,而是介紹你在用defer時,也許會踩的坑。
defer允許我們進行一些函式執行完成後的收尾工作,並且程式碼更加簡潔,例如:
-
關閉檔案流:
// open a file defer file.Close()
-
解鎖一個加鎖的資源
mu.Lock() defer mu.Unlock()
-
列印最終報告
printHeader() defer printFooter()
-
關閉資料庫連結
// open a database connection defer disconnectFromDB()
但是:
- 你知道defer和defer後的函式什麼時候執行嗎?
- 你知道defer後函式裡的變數值是什麼時候計算的嗎?
我曾經在ofollow,noindex" target="_blank">Stack Overflow 討論過這個問題,有興趣的可以看下,本打算在週末寫個文章分享給大家defer的坑,今天不小心瀏覽到一個,決定現在就寫下來,希望大家不要踩坑。
defer陷阱測試
如果下面這段程式碼的結果你都知道,恭喜你,你已經瞭解defer的執行原理,沒有必要再看這篇文章了。
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
defer執行原理
要想知道為何是這個結果,就得先回答前面的2個問題:
- defer和defer後的函式什麼時候執行嗎?
- defer後函式裡的變數值是什麼時候計算的嗎?
依次來回答,這2個問題。
問題1:defer在defer語句處執行,defer的執行結果是把defer後的函式壓入到棧,等待return或者函式panic後,再按先進後出的順序執行被defer的函式。
問題2:defer的函式的引數是在執行defer時計算的,defer的函式中的變數的值是在函式執行時計算的。
defer及defer函式的執行順序分2步:
- 執行defer,計算函式的入參的值,並傳遞給函式,但不執行函式,而是將函式壓入棧。
- 函式return語句後,或panic後,執行壓入棧的函式,函式中變數的值,此時會被計算。
defer測試解析
這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之前呼叫的。
等效的能證明,順序嗎?請各位自行思考吧。
defer的核心
Golang對於defer的介紹)很精簡,但是把上面提到的問題都說清楚了,我也是讀了幾遍和其他人交流,才完全理解透,不妨好好讀讀,最核心的一句:
Each time a "defer" statement executes, the function value and parameters to the call areevaluated as usual and saved anew but the actual function is not invoked.
參考資料
如果這篇文章對你有幫助,請點個贊/喜歡,讓我知道我的寫作是有價值的,感謝。