關於Go defer的詳細使用
先拋磚引玉defer的延遲呼叫:
defer特性:
1. 關鍵字 defer 用於註冊延遲呼叫。 2. 這些呼叫直到 return 前才被執。因此,可以用來做資源清理。 3. 多個defer語句,按先進後出的方式執行。 4. defer語句中的變數,在defer宣告時就決定了。
defer用途:
1. 關閉檔案控制代碼 2. 鎖資源釋放 3. 資料庫連線釋放
好,廢話不多說,例項加深理解,我們先看看一段程式碼
package main import "fmt" func main() { var users [5]struct{} for i := range users { defer fmt.Println(i) } }
輸出:4 3 2 1 0 ,defer 是先進後出,這個輸出沒啥好說的。
我們把上面的程式碼改下:
defer 換上閉包:
package main import "fmt" func main() { var users [5]struct{} for i := range users { defer func() { fmt.Println(i) }() } }
輸出:4 4 4 4 4,很多人也包括我。預期的結果不是 4 3 2 1 0 嗎?官網對defer 閉包的使用大致是這個意思:
函式正常執行,由於閉包用到的變數 i 在執行的時候已經變成4,所以輸出全都是4。那麼 如何正常輸出逾期的 4 3 2 1 0 呢?
不用閉包,換成函式:
package main import "fmt" func main() { var users [5]struct{} for i := range users { defer Print(i) } } func Print(i int) { fmt.Println(i) }
函式正常延遲輸出:4 3 2 1 0。
我們再舉一個可能一不小心會犯錯的例子:
defer呼叫引用結構體函式
package main import "fmt" type Users struct { name string } func (t *Users) GetName() { // 注意這裡是 * 傳地址 引用Users fmt.Println(t.name) } func main() { list := []Users{{"喬峰"}, {"慕容復"}, {"清風揚"}} for _, t := range list { defer t.GetName() } }
輸出:清風揚 清風揚 清風揚。
這個輸出並不會像我們預計的輸出:清風揚 慕容復 喬峰
可是按照前面的go defer函式中的使用說明,應該輸出清風揚 慕容復 喬峰才對啊?
那我們換一種方式來呼叫一下
package main import "fmt" type Users struct { name string } func (t *Users) GetName() { // 注意這裡是 * 傳地址 引用Users fmt.Println(t.name) } func GetName(t Users) { // 定義一個函式,名稱自定義 t.GetName() // 呼叫結構體USers的方法GetName } func main() { list := []Users{{"喬峰"}, {"慕容復"}, {"清風揚"}} for _, t := range list { defer GetName(t) } }
輸出:清風揚 慕容復 喬峰。
這個時候輸出的就是所謂"預期"滴了
當然,如果你不想多寫一個函式,也很簡單,可以像下面這樣(改2處),同樣會輸出清風揚 慕容復 喬峰
package main import "fmt" type Users struct { name string } func (t *Users) GetName() { // 注意這裡是 * 傳地址 引用Users fmt.Println(t.name) } func GetName(t Users) { // 定義一個函式,名稱自定義 t.GetName() // 呼叫結構體USers的方法GetName } func main() { list := []Users{{"喬峰"}, {"慕容復"}, {"清風揚"}} for _, t := range list { t2 := t // 定義新變數t2 t賦值給t2 defer t2.GetName() } }
輸出:清風揚 慕容復 喬峰。
通過以上例子,結合
我們可以得出下面的結論:
defer後面的語句在執行的時候,函式呼叫的引數會被儲存起來,但是不執行。也就是複製了一份。但是並沒有說struct這裡的*指標如何處理,
通過這個例子可以看出go語言並沒有把這個明確寫出來的this指標(比如這裡的* Users)當作引數來看待。到這裡有滴朋友會說。看似多此一舉的宣告,
直接去掉指標呼叫 t *Users改成 t Users 不就行了?
package main import "fmt" type Users struct { name string } func (t Users) GetName() { // 注意這裡是 * 傳地址 引用Users fmt.Println(t.name) } func main() { list := []Users{{"喬峰"}, {"慕容復"}, {"清風揚"}} for _, t := range list { defer t.GetName() } }
輸出:清風揚 慕容復 喬峰。這就回歸到上面的 defer 函式非引用呼叫的示例了。所以這裡我們要注意defer後面的指標函式和普通函式的呼叫區別。很容易混淆出錯。
未完待續。