Golang, 以 9 個簡短程式碼片段,切底弄懂 defer 的使用特點
defer
是Go
語言中一個很重要的關鍵詞。本文主要以簡短的手法
列舉出,它在不同的多種常見程式碼片段中,所體現出來的不一樣的效果。從筆試的角度來看,可以說是覆蓋了絕大部分題型。
此外,在本文之前,還有本人另一篇同樣使用例子的形式
對channel
資料型別做直觀講解
的文章。
Golang, 以17個簡短程式碼片段,切底弄懂 channel 基礎
目錄
- defer 的主要特點
-
非引用傳參給
defer
呼叫的函式,且為非閉包函式情況 -
傳遞引用給
defer
呼叫的函式,即使不使用閉包函式情況 -
傳遞值給
defer
呼叫的函式,且非閉包函式情況 -
defer
呼叫閉包函式,且內呼叫外部非傳參進來的變數的情況 -
defer
呼叫閉包函式,若內部使用了傳參引數的值的情況 -
defer
所呼叫的非揹包函式,引數如果是函式的情況 -
defer
不影響return
的值 -
閉包函式對
defer
的影響
defer 的主要特點
- 延遲呼叫
-
所在的函式中,它在
return
或panic
或執行完畢
後被呼叫 -
多個 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
的值
下面兩個例子的結論是:
- 無論 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 複製程式碼