Golang, 以 9 個簡短代碼片斷,弄懂 defer 的使用特色

做者:林冠宏 / 指尖下的幽靈html

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8git

博客:http://www.cnblogs.com/linguanh/github

GitHub : https://github.com/af913337456/數組

騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activities閉包

蟲洞區塊鏈專欄:https://www.chongdongshequ.com/article/1536563643883.html函數


前序

deferGo語言中一個很重要的關鍵詞。本文主要以簡短的手法列舉出,它在不一樣的多種常見代碼片斷中,所體現出來的不同的效果。從筆試的角度來看,能夠說是覆蓋了絕大部分題型。區塊鏈

此外,在本文以前,還有本人另外一篇一樣使用例子的形式channel 數據類型作直觀講解的文章。指針

Golang, 以17個簡短代碼片斷,切底弄懂 channel 基礎code

目錄

  • defer 的主要特色
  • 非引用傳參給defer調用的函數,且爲非閉包函數狀況
  • 傳遞引用給defer調用的函數,即便不使用閉包函數狀況
  • 傳遞值給defer調用的函數,且非閉包函數狀況
  • defer調用閉包函數,且內調用外部非傳參進來的變量的狀況
  • defer調用閉包函數,若內部使用了傳參參數的值的狀況
  • defer所調用的非閉包函數,參數若是是函數的狀況
  • defer 不影響 return的值
  • 閉包函數對 defer 的影響

defer 的主要特色

  • 延遲調用
  • 所在的函數中,它在 returnpanic執行完畢 後被調用
  • 多個 defer,它們的被調用順序,爲的形式。先進後出,先定義的後被調用
func Test_1(t *testing.T) {
    // defer 的調用順序。由下到上,爲 棧的形式。先進後出
    defer0()   // ↑
    defer1()   // |
    defer2()   // |
    defer3()   // |
    //defer4() // |
    defer5()   // |
    defer6()   // |  
    defer7()   // |
    defer8()   // |  從下往上
}

非引用傳參給defer調用的函數,且爲非閉包函數,值不會受後面的改變影響

func defer0() {
    a := 3  // a 做爲演示的參數
    defer fmt.Println(a) // 非引用傳參,非閉包函數中,a 的值 不會 受後面的改變影響
    a = a + 2
}
// 控制檯輸出 3

傳遞引用給defer調用的函數,即便不使用閉包函數,值也受後面的改變影響

func myPrintln(point *int)  {
    fmt.Println(*point) // 輸出引用所指向的值
}
func defer1() {
    a := 3
    // &a 是 a 的引用。內存中的形式: 0x .... ---> 3
    defer myPrintln(&a) // 傳遞引用給函數,即便不使用閉包函數,值 會 受後面的改變影響
    a = a + 2
}
// 控制檯輸出 5

傳遞值給defer調用的函數,且非閉包函數,值不會受後面的改變影響

func p(a int)  {
    fmt.Println(a)
}

func defer2() {
    a := 3
    defer p(a) // 傳遞值給函數,且非閉包函數,值 不會 受後面的改變影響
    a = a + 2
}
// 控制檯輸出: 3

defer調用閉包函數,且內調用外部非傳參進來的變量,值受後面的改變影響

// 閉包函數內,事實是該值的引用
func defer3() {
    a := 3
    defer func() {
        fmt.Println(a) // 閉包函數內調用外部非傳參進來的變量,事實是該值的引用,值 會 受後面的改變影響
    }()
    a = a + 2  // 3 + 2 = 5
}
// 控制檯輸出: 5
// defer4 會拋出數組越界錯誤。
func defer4() {
    a := []int{1,2,3}
    for i:=0;i<len(a);i++ {
        // 同 defer3 的閉包形式。由於 i 是外部變量,沒用經過傳參的形式調用。在閉包內,是引用。
        // 值 會 受 ++ 改變影響。致使最終 i 是3, a[3] 越界
        defer func() {
            fmt.Println(a[i])
        }()
    }
}
// 結果:數組越界錯誤

defer調用閉包函數,若內部使用了傳參參數的值。使用的是值

func defer5() {
    a := []int{1,2,3}
    for i:=0;i<len(a);i++ {
        // 閉包函數內部使用傳參參數的值。內部的值爲傳參的值。
        defer func(index int) {
            fmt.Println(a[index]) // index == i
        }(i)
        // 後進先出,3 2 1
    }
}
// 控制檯輸出: 
//     3
//     2
//     1

defer所調用的非閉包函數,參數若是是函數,會按順序先執行(函數參數)

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}
func defer6()  {
    a := 1
    b := 2
    // calc 充當了函數中的函數參數。即便在 defer 的函數中,它做爲函數參數,定義的時候也會首先調用函數進行求值
    // 按照正常的順序,calc("10", a, b) 首先被調用求值。calc("122", a, b) 排第二被調用
    defer calc("1", a, calc("10", a, b))
    defer calc("12",a, calc("122", a, b))
}
// 控制檯輸出:
/**
10 1 2 3   // 第一個函數參數
122 1 2 3  // 第二個函數參數
12 1 3 4   // 倒數第一個 calc
1 1 3 4    // 倒數第二個 calc
*/

defer 不影響 return的值

下面兩個例子的結論是:htm

  • 不管 defer 內部調用傳遞的是值仍是引用。都不會改變 return 的返回結果。返回值的肯定,比 defer 早
func defer7() int {
    a := 2
    defer func() {
        a = a + 2
    }()
    return a
}
// 控制檯輸出:2
func add(i *int)  {
    *i = *i + 2
}

func defer8() int {
    a := 2
    defer add(&a)
    return a
}
// 控制檯輸出:2

原理:

例如:return a,此行代碼通過編譯後,會被拆分爲:
    1. 返回值 = a
    2. 調用 defer 函數
    3. return

閉包函數對 defer 的影響

函數中,值傳遞引用傳遞它們的區別是比較簡單的,爲基礎的 C 語言指針知識。

而對於爲何 defer 修飾的揹包函數,若是函數內部不是使用傳參的參數時,它所能起到的引用修改做用。原理以下:

a := 2
func() {
    fmt.Println(a)
}()
a = a + 3
// 內存
閉包外:
    1. a 實例化
    2. a地址 ---> 2
閉包內:
    1. a 地址被傳遞進來
    2. a地址 ---> 2
    3. a = a + 3
    4. 輸出 5

相關文章
相關標籤/搜索