go 的 defer 語句是用來
延遲執行函數
的,並且延遲發生在調用函數 return
以後數據庫
go 的 defer 語句是用來延遲執行函數
的,並且延遲發生在調用函數 return
以後,好比bash
func a() int {
defer b()
return 0
}
複製代碼
b 的執行是發生在 return 0 以後,注意 defer 的語法,關鍵字 defer 以後是函數的調用。函數
因爲 defer 的延遲特性,defer 經常使用在函數調用結束以後清理相關的資源,好比ui
f, _ := os.Open(filename)
defer f.Close()
複製代碼
文件資源的釋放會在函數調用結束以後藉助 defer 自動執行,不須要時刻記住哪裏的資源須要釋放,打開和釋放必須相對應。spa
用一個例子深入詮釋一下 defer 帶來的便利和簡潔。code
代碼的主要目的是打開一個文件,而後複製內容到另外一個新的文件中,沒有 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 實現:string
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 在什麼地方執行。it
鑑於 defer 的這種做用,defer 經常使用來釋放數據庫鏈接,文件打開句柄等釋放資源的操做。io
被 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 的執行順序是後進先出 LIFO :
defer func() { fmt.Println("1") }()
defer func() { fmt.Println("2") }()
defer func() { fmt.Println("3") }()
複製代碼
輸出順序是 321。這個特性能夠對一個 array 實現逆序操做。
這是 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。