golang基礎–細說defer

failymao發表於2018-07-11

defer 匿名函式特性

  • 執行方式類似其它語言中的解構函式,在函式體執行結束後按照呼叫順序的相反順序逐個執行

    //執行順序相反
    package main
    import "fmt"
    
    func main() {
        fmt.Println("a")
        defer fmt.Println("b")
        defer fmt.Println("c")
    }
    /*輸出
    a
    c
    b
    */
  • 即使函式發生嚴重的錯誤也會執行,類似於try…except
  • 常用於 資源清理,檔案關閉,解鎖以及記錄時間等操作

  • 支援匿名函式的呼叫
  • 通過於匿名函式配合可在return之後修改函式計算的結果
    -如果函式體內某個變數作為defer時匿名函式的引數,則在定義defer時即已經獲得了拷貝,否則 則時引用某個變數的地址

    //支援匿名函式
    package main
    import "fmt"
    
    func main() {
        for i := 0; i < 3; i++ {
            defer func() { //函式體內的變數傳遞到defer匿名函式
                fmt.Println(i) //此時引用的時變數i的地址
            }()
        }
    }
    
    /*輸出
    3
    3
    3
    */
  • Go沒有異常機制,但有panic/recover模式來處理錯誤
  • Panic可以在任何地方引發

    panic錯誤機制

    //panic 錯誤機制,遇到panic語句後,後面不會再執行
    package main
    import "fmt"
    func main() {
        A()
        B()
        C()
    }
    
    func A() {
        fmt.Println("func a")
    }
    
    func B() {
        panic("Panic B")
    }
    func C() {
        fmt.Println("func")
    }
    
    /*輸出
    A()-->  func a
    B()---> panic: Panic B
    ---------------
        goroutine 1 [running]:
        main.B()
    C()     C:/Users/faily/Desktop/workspace/src/defer1.go:17 +0x40
        main.main()
            C:/Users/faily/Desktop/workspace/src/defer1.go:8 +0x2c
        exit status 2
        exit status 1
    */
  • defer,配合recover及匿名函式處理程式出現的嚴重錯誤(panic語句),調過程式錯誤,繼續執行,類似於python語言中 try...except,finally語句.

    //defer,recover機制,處理panic引發的機制
    package main
    import "fmt"
    
    func main() {
        A()
        B()
        C()
    }
    
    func A() {
        fmt.Println("func a")
    }
    func B() {
        defer func() {                          //defer函式放在panic之前
            if err := recover(); err != nil {       //註冊recover函式(判斷是否觸發panic錯誤),並判斷
                fmt.Println("Recover in B")  //如果程式出現panic,並且err不為nil(真實存在)
            } 
        }()                             //記住,defer的匿名函式大括號後要加上()                                
        panic("Panic B")                   //跳過程式錯誤,繼續後面的執行。
    
    }
    func C() {
        fmt.Println("func C")
    
    }
    
    /*輸出
    A()-->  func a
    B()-->  Recover in B
    C()-->  func C
    */ 

    – defer,匿名函式,變數傳遞,陣列array,for迴圈綜合

    執行以下程式碼,並分析輸出結果

    package main
    import "fmt"
    func main() {
    var fs = [4]func(){}  //定義一個變數fs,型別為一個陣列,陣列元素的型別是匿名函式func
    for i := 0; i < 4; i++ {
        defer fmt.Println("defer i=", i) //defer,遵循的是先進後出,所以for迴圈,最後執行這一句,i作為變數,正常傳遞
        defer func() { fmt.Println("defer closure i=", i) }()   // i從外部傳遞進defer匿名函式中,作為變數而非匿名函式引數,此時引用的是i的記憶體地址(只會引用i的最後值)
        fs[i] = func() { fmt.Println("closure i=", i) }   //為fs陣列索引賦值,i傳遞進匿名函式並沒有作為引數,所以i引用的是i的記憶體地址(只會引用i的最後值)
        fmt.Println(i)
    }
    for n, f := range fs {                // for 迴圈陣列,執行每一個元素(匿名函式)
        fmt.Println(n)
        f()
    }
    }
    /*輸出
    n=0
        closure i= 4        
    n=1
        closure i= 4
    n=2
        closure i= 4
    n=3
        closure i= 4
    ------------------------
    defer closure i= 4      
    defer i= 3              
    defer closure i= 4
    defer i= 2
    defer closure i= 4
    defer i= 1
    defer closure i= 4
    defer i= 0
    */

    分析結果,詳見程式碼註釋

相關文章