一、defer 的作用和執行時機
go 的 defer 語句是用來延遲執行函數的,而且延遲發生在調用函數 return之后,比如
func a() int { defer b() return 0 }
b 的執行是發生在return 0之后,注意defer
的語法,關鍵字defer
之后是函數的調用。
二、defer 的重要用途一:清理釋放資源
由於defer
的延遲特性,defer
常用在函數調用結束之后清理相關的資源,比如
f, _ := os.Open(filename) defer f.Close()
文件資源的釋放會在函數調用結束之后借助defer
自動執行,不需要時刻記住哪里的資源需要釋放,打開和釋放必須相對應。
用一個例子深刻詮釋一下defer
帶來的便利和簡潔。
代碼的主要目的是打開一個文件,然后復制內容到另一個新的文件中,沒有defer
時這樣寫:
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } dst, err := os.Create(dstName) if err != nil { //1 return } written, err = io.Copy(dst, src) dst.Close() src.Close() return }
代碼在#1
處返回之后,src
文件沒有執行關閉操作,可能會導致資源不能正確釋放,改用defer
實現:
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src) }
src
和dst
都能及時清理和釋放,無論return
在什么地方執行。
鑒於defer
的這種作用,defer
常用來釋放數據庫連接,文件打開句柄等釋放資源的操作。
###defer
的重要用途二:執行recover
被defer
的函數在return
之后執行,這個時機點正好可以捕獲函數拋出的panic
,因而defer
的另一個重要用途就是執行recover
。
recover
只有在defer
中使用才更有意義,如果在其他地方使用,由於program
已經調用結束而提前返回而無法有效捕捉錯誤。
package main import ( "fmt" ) func main() { defer func() { if ok := recover(); ok != nil { fmt.Println("recover") } }() panic("error") }
記住defer
要放在panic
執行之前。
三、多個 defer 的執行順序
defer 的作用就是把關鍵字之后的函數執行壓入一個棧中延遲執行,多個defer
的執行順序是后進先出LIFO
:
defer func() { fmt.Println("1") }() defer func() { fmt.Println("2") }() defer func() { fmt.Println("3") }()
輸出順序是 321。
這個特性可以對一個array
實現逆序操作。
四、被 deferred 函數的參數在 defer 時確定
這是defer
的特點,一個函數被defer
時,它的參數在defer
時進行計算確定,即使defer
之后參數發生修改,對已經defer
的函數沒有影響,什么意思?看例子:
func a() { i := 0 defer fmt.Println(i) i++ return }
a 執行輸出的是 0 而不是 1,因為defer
時,i 的值是 0,此時被defer
的函數參數已經進行執行計算並確定了。
再看一個例子:
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { a := 1 b := 2 defer calc("1", a, calc("10", a, b)) a = 0 return }
執行代碼輸出
10 1 2 3 1 1 3 4
defer
函數的參數 第三個參數在defer
時就已經計算完成並確定,第二個參數 a 也是如此,無論之后 a 變量是否修改都不影響。
五、被defer的函數可以讀取和修改帶名稱的返回值
func c() (i int) { defer func() { i++ }() return 1 }
被defer
的函數是在return
之后執行,可以修改帶名稱的返回值,上面的函數 c 返回的是 2。
六、defer、return的執行順序
https://www.cnblogs.com/l199616j/p/15500631.html
參考:https://zhuanlan.zhihu.com/p/60005467