1. 程式人生 > >關於Go defer的詳細使用

關於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後面的指標函式和普通函式的呼叫區別。很容易混淆出錯。

未完待續。