原文連結:http://www.zhoubotong.site/post/50.html
defer語句用於延遲函式呼叫,每次會把一個函式壓入棧中,函式返回前再把延遲的函式取出並執行。延遲函式可以有引數:
-
延遲函式的引數在defer語句出現時就已確定下來(傳值的就是當前值)
-
return先賦值(對於命名返回值),然後執行defer,最後函式返回
-
延遲函式執行按後進先出順序執行
-
延遲函式可操作主函式的變數名返回值(修改返回值)
-
defer後面的表示式可以是func或者是method的呼叫,如果defer的函式為nil,則會panic
日常開發中,使用不當很容易造成意外的“坑”。下面我整理了下常規使用場景下,defer的問題可能的踩坑彙總。
釋放資源
defer 語句正好是在函式退出時執行的語句,所以使用 defer 能非常方便地處理資源釋放、控制程式碼關閉等問題。
package main import ( "fmt" "os" ) func fileSize(filename string) int64 { f, err := os.Open(filename) if err != nil { return 0 } // 延遲呼叫Close, 此時Close不會被呼叫 defer f.Close() info, err := f.Stat() if err != nil { // defer機制觸發, 呼叫Close關閉檔案 return 0 } size := info.Size() // defer機制觸發, 呼叫Close關閉檔案 return size } func main() { fmt.Println(fileSize("demo.txt")) }
變數捕獲
defer中的變數會被提前捕獲,後續的修改不會影響到已捕獲的值,舉個例子:
package main import ( "fmt" ) func main() { i := 0 defer fmt.Println("Defer執行值:", i) i = 10 // 這裡雖然修改了值,但是不會影響上面的i值 fmt.Println("最後輸出值:", i) }
結果defer語句中列印的值是修改前的值。:
最後輸出值: 10
Defer執行值: 0
變數名返回值
在defer中修改具體變數名返回值時,會影響到函式的實際返回值,繼續舉個例子:
package main import ( "fmt" ) func ShowDefer() { fmt.Println("最後輸出值:", deferValue()) } func deferValue() (ret int) { // 注意這裡返回res變數值 ret = 0 defer func() { // 會直接修改棧中對應的返回值 ret += 10 fmt.Println("Defer 執行值:", ret) }() ret = 2 fmt.Println("Ret重置值:", ret) return //返回的ret最後是 其實是本次2+上面的ret+=10的結果 } func main() { ShowDefer() } //Ret重置值: 2 //Defer 執行值: 12 //最後輸出值: 12
非變數名返回值
當函式為非具體名返回值時,defer無法影響返回值(因在return時,對應返回值已存入棧中),繼續舉個例子:
package main import ( "fmt" ) func ShowDefer() { fmt.Println("最後輸出值:", deferValue()) } func deferValue() int { // 非命名變數返回 ret := 0 defer func() { ret += 10 fmt.Println("Defer 執行值:", ret) }() ret = 2 return ret // 這裡直接返回ret2 } func main() { ShowDefer() } //Defer 執行值: 12 //最後輸出值: 2
經過上面的實踐理解,我們來看下下面的筆試題:
筆試題一
package main import "fmt" func f() (result int) { defer func() { result *= 7 }() return 3 } func main() { fmt.Println(f()) }
問題解析:這裡return先給result賦值為3,之後執行defer,result變為21,最後返回21。
筆試題二
package main import "fmt" func f() int { result := 3 defer func() { result *= 7 }() return result } func main() { fmt.Println(f()) }
問題解析:這裡return確定返回值3,之後defer才修改result,最後函式返回return確定的返回值3。
筆試題三
package main import "fmt" // 多個defer func multiDefer() { for i := 3; i > 0; i-- { defer func(n int) { fmt.Print(n, " ") }(i) } for i := 33; i > 30; i-- { defer fmt.Print(i, " ") } } func main() { multiDefer() }
問題解析:多個defer函式,按順序逆序執行,這裡輸出31 32 33 1 2 3 。
筆試題四
package main import "fmt" var fun func() string func main() { fmt.Println("hello monkey") defer fun() }
問題解析:由於這裡的defer指定的func為nil,所以會panic 。
筆試題五
package main import "fmt" func main() { for i := 3; i > 0; i-- { defer func() { fmt.Print(i, " ") }() } }
問題解析:這裡是極度容易踩坑的地方,由於defer這裡呼叫的func沒有引數,等執行的時候,i已經為0(按3 2 1逆序,最後一個i=1時,i--的結果最後是0),所以這裡輸出3個0 。
如果還不太好理解?
package main import "fmt" func main() { for i := 3; i > 1; i-- { // 迴圈滿足條件的是 3 2, defer func() { // 因為func 沒有引數,defer執行最後i--即 2-- 結果為 1 fmt.Print(i, " ") // 迴圈2次 結果均為 1 }() } }//輸出 1 1
按照常規的思維理解應該是這樣:
package main import "fmt" func main() { for i := 3; i > 0; i-- { defer func(i int) { fmt.Print(i, " ") }(i) } }
感興趣的朋友可以細細品下。