理解 Go defer

go 的 defer 語句是用來延遲執行函數的,並且延遲發生在調用函數 return以後數據庫

defer 的做用和執行時機

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

執行 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。

相關文章
相關標籤/搜索