做者:林冠宏 / 指尖下的幽靈html
掘金:juejin.im/user/587f0d…git
博客:www.cnblogs.com/linguanh/github
GitHub : github.com/af913337456…數組
騰訊雲專欄: cloud.tencent.com/developer/u…閉包
蟲洞區塊鏈專欄:www.chongdongshequ.com/article/153…函數
defer
是Go
語言中一個很重要的關鍵詞。本文主要以簡短的手法
列舉出,它在不一樣的多種常見代碼片斷中,所體現出來的不同的效果。從筆試的角度來看,能夠說是覆蓋了絕大部分題型。區塊鏈
此外,在本文以前,還有本人另外一篇一樣使用例子的形式
對 channel
數據類型作直觀講解
的文章。ui
Golang, 以17個簡短代碼片斷,切底弄懂 channel 基礎spa
defer
調用的函數,且爲非閉包函數狀況defer
調用的函數,即便不使用閉包函數狀況defer
調用的函數,且非閉包函數狀況defer
調用閉包函數,且內調用外部非傳參進來的變量的狀況defer
調用閉包函數,若內部使用了傳參參數的值的狀況defer
所調用的非閉包函數,參數若是是函數的狀況defer
不影響 return
的值defer
的影響return
或 panic
或 執行完畢
後被調用棧
的形式。先進後出,先定義的後被調用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) {
// index 有一個新地址指向它
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
的值下面兩個例子的結論是:指針
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
複製代碼