5 年 Gopher 都不知道的 defer 細節,你別再掉進坑裡

最近面試題 和 都涉及到 defer,所以我們一起來看下分析。

問題

昨天在群裡貼了下面兩段代碼,主要想了解下大家對 defer 用法掌握的如何。

<code>1.funcincreaseA()int{variintdeferfunc(){i++}()returni}2.funcincreaseB()(rint){deferfunc(){r++}()returnr}/<code>

在不運行代碼的情況下,能夠全回答正確的還是少數(包括我自己)。有的同學可能知道答案,但不明白其中的原因。所以,有必要總結下,免得以後再掉坑裡。試著先給出你的答案,再往下讀!

什麼是 defer

defer 是 Go 語言提供的一種用於註冊延遲調用的機制,每一次 defer 都會把函數壓入棧中,當前函數返回前再把延遲函數取出並執行。

defer 語句並不會馬上執行,而是會進入一個棧,函數 return 前,會按先進後出(FILO)的順序執行。也就是說最先被定義的 defer 語句最後執行。先進後出的原因是後面定義的函數可能會依賴前面的資源,自然要先執行;否則,如果前面先執行,那後面函數的依賴就沒有了

採坑點

使用 defer 最容易採坑的地方是和帶命名返回參數的函數

一起使用時。

defer 語句定義時,對外部變量的引用是有兩種方式的,分別是作為函數參數和作為閉包引用。作為函數參數,則在 defer 定義時就把值傳遞給 defer,並被緩存起來;作為閉包引用的話,則會在 defer 函數真正調用時根據整個上下文確定當前的值。

避免掉坑的關鍵是要理解這條語句:

<code>returnxxx/<code>

這條語句並不是一個原子指令,經過編譯之後,變成了三條指令:

<code>1.返回值=xxx2.調用defer函數3.空的return/<code>

1,3 步才是 return 語句真正的命令,第 2 步是 defer 定義的語句,這裡就有可能會操作返回值

我們一起來做幾個題目:

<code>1.funcf1()(rint){deferfunc(){r++}()return0}2.funcf2()(rint){t:=5deferfunc(){t=t+5}()returnt}3.funcf3()(rint){deferfunc(rint){r=r+5}(r)return1}/<code>

我們試著用上面的拆解方式,給出自己的答案,再往下看!
第一題拆解過程:

<code>funcf1()(rint){//1.賦值r=0//2.閉包引用,返回值被修改deferfunc(){r++}()//3.空的returnreturn}/<code>

defer 是閉包引用,返回值被修改,所以 f() 返回 1。

第二題拆解過程:

<code>funcf2()(rint){t:=5//1.賦值r=t//2.閉包引用,但是沒有修改返回值rdeferfunc(){t=t+5}()//3.空的returnreturn}/<code>

第二步沒涉及返回值 r 的操作,所以返回 5。

第三題拆解過程:

<code>funcf3()(rint){//1.賦值r=1//2.r作為函數參數,不會修改要返回的那個r值deferfunc(rint){r=r+5}(r)//3.空的returnreturn}/<code>

第二步,r 是作為函數參數使用,是一份複製,defer 語句裡面的 r 和 外面的 r 其實是兩個變量,裡面變量的改變不會影響外層變量 r,所以不是返回 6 ,而是返回 1。

文章寫到這裡,相信你可以很快解出文章開頭的第二個例程,沒錯,第二個例程的 increaseB() 函數返回 1,分解如下:

<code>funcincreaseB()(rint){//1.賦值r=0//2.閉包引用,r++deferfunc(){r++}()//3.空returnreturn}/<code>

那第一個例程怎麼解呢?我把代碼再貼一遍:

<code>funcincreaseA()int{variintdeferfunc(){i++}()returni}/<code>

大家可能注意到,函數 increaseA() 是匿名返回值,返回局部變量,同時 defer 函數也會操作這個局部變量。對於匿名返回值來說,可以假定有一個變量存儲返回值,比如假定返回值變量為 anony,上面的返回語句可以拆分成以下過程:

<code>annoy=ii++return/<code>

由於 i 是整型,會將值拷貝給 anony,所以 defer 語句中修改 i 值,對函數返回值不造成影響,所以返回 0 。

後記

這篇文章算是一份學習總結,梳理下相關知識。看完文章之後,相信你一定掌握了這些細節,那就來試試今天的面試題吧,也是跟 defer 相關的。

相關閱讀:


1.Golang之輕鬆化解defer的溫柔陷阱https://segmentfault.com/a/1190000018169295#articleHeader4
2.Go defer實現原理剖析 https://my.oschina.net/renhc/blog/2870345
3.golang之defer簡介 https://leokongwq.github.io/2016/10/15/golang-defer.html


分享到:


相關文章: