12. Go 語言流程控制:defer 延遲語句

Hi,大家好,我是明哥。

在自己學習 Golang 的這段時間裏,我寫了詳細的學習筆記放在我的個人微信公衆號 《Go編程時光》,對於 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長。

我的在線博客:http://golang.iswbm.com 我的 Github:github.com/iswbm/GolangCodingTime


Go裏的流程控制方法還是挺豐富,整理了下有如下這麼多種:

  • if - else 條件語句
  • switch - case 選擇語句
  • for - range 循環語句
  • goto 無條件跳轉語句
  • defer 延遲執行

今天是最後一篇講控制流程了,內容是 defer 延遲語句,這個在其他編程語言裏好像沒有見到。應該是屬於 Go 語言裏的獨有的關鍵字,但即使如此,閱讀後這篇文章後,你可以發現 defer 在其他編程語言裏的影子。

1. 延遲調用

defer 的用法很簡單,只要在後面跟一個函數的調用,就能實現將這個 xxx 函數的調用延遲到當前函數執行完後再執行。

defer xxx() 

這是一個很簡單的例子,可以很快幫助你理解 defer 的使用效果。

import "fmt"

func myfunc() {
    fmt.Println("B")
}

func main() {
    defer myfunc()
    fmt.Println("A")
}

輸出如下

A
B

當然了,對於上面這個例子可以簡寫爲成如下,輸出結果是一致的

import "fmt"

func main() {
    defer fmt.Println("B")
    fmt.Println("A")
}

2. 即時求值的變量快照

使用 defer 只是延時調用函數,此時傳遞給函數裏的變量,不應該受到後續程序的影響。

比如這邊的例子

import "fmt"

func main() {
    name := "go"
    defer fmt.Println(name) // 輸出: go

    name = "python"
    fmt.Println(name)      // 輸出: python
}

輸出如下,可見給 name 重新賦值爲 python,後續調用 defer 的時候,仍然使用未重新賦值的變量值,就好在 defer 這裏,給所有的這是做了一個快照一樣。

python
go

3. 多個defer 反序調用

當我們在一個函數裏使用了 多個defer,那麼這些defer 的執行函數是如何的呢?

做個試驗就知道了

import "fmt"

func main() {
    name := "go"
    defer fmt.Println(name) // 輸出: go

    name = "python"
    defer fmt.Println(name) // 輸出: python

    name = "java"
    fmt.Println(name)
}

輸出如下,可見 多個defer 是反序調用的,有點類似棧一樣,後進先出。

java
python
go

3. defer 與 return 孰先孰後

至此,defer 還算是挺好理解的。在一般的使用上,是沒有問題了。

在這裏提一個稍微複雜一點的問題,defer 和 return 到底是哪個先調用?

使用下面這段代碼,可以很容易的觀察出來

import "fmt"

var name string = "go"

func myfunc() string {
    defer func() {
        name = "python"
    }()

    fmt.Printf("myfunc 函數裏的name:%s\n", name)
    return name
}

func main() {
    myname := myfunc()
    fmt.Printf("main 函數裏的name: %s\n", name)
    fmt.Println("main 函數裏的myname: ", myname)
}

輸出如下

myfunc 函數裏的name:go
main 函數裏的name: python
main 函數裏的myname:  go

來一起理解一下這段代碼,第一行很直觀,name 此時還是全局變量,值還是go

第二行也不難理解,在 defer 裏改變了這個全局變量,此時name的值已經變成了 python

重點在第三行,爲什麼輸出的是 go ?

解釋只有一個,那就是 defer 是return 後才調用的。所以在執行 defer 前,myname 已經被賦值成 go 了。

4. 爲什麼要有 defer?

看完上面的例子後,不知道你是否和我一樣,對這個defer的使用效果感到熟悉?貌似在 Python 也見過類似的用法。

雖然 Python 中沒有 defer ,但是它有 with 上下文管理器。我們知道在 Python 中可以使用 defer 實現對資源的管理。最常用的例子就是文件的打開關閉。

你可能會有疑問,這也沒什麼意義呀,我把這個放在 defer 執行的函數放在 return 那裏執行不就好了。

固然可以,但是當一個函數裏有多個 return 時,你得多調用好多次這個函數,代碼就臃腫起來了。

若是沒有 defer,你可以寫出這樣的代碼

func f() {
    r := getResource()  //0,獲取資源
    ......
    if ... {
        r.release()  //1,釋放資源
        return
    }
    ......
    if ... {
        r.release()  //2,釋放資源
        return
    }
    ......
    if ... {
        r.release()  //3,釋放資源
        return
    }
    ......
    r.release()     //4,釋放資源
    return
}

使用了 defer 後,代碼就顯得簡單直接,不管你在何處 return,都會執行 defer 後的函數。

func f() {
    r := getResource()  //0,獲取資源

    defer r.release()  //1,釋放資源
    ......
    if ... {
        ...
        return
    }
    ......
    if ... {
        ...
        return
    }
    ......
    if ... {
        ...
        return
    }
    ......
    return
}

系列導讀

01. 開發環境的搭建(Goland & VS Code)

02. 學習五種變量創建的方法

03. 詳解數據類型:**整形與浮點型**

04. 詳解數據類型:byte、rune與string

05. 詳解數據類型:數組與切片

06. 詳解數據類型:字典與布爾類型

07. 詳解數據類型:指針

08. 面向對象編程:結構體與繼承

09. 一篇文章理解 Go 裏的函數

10. Go語言流程控制:if-else 條件語句

11. Go語言流程控制:switch-case 選擇語句

12. Go語言流程控制:for 循環語句

13. Go語言流程控制:goto 無條件跳轉

14. Go語言流程控制:defer 延遲調用

15. 面向對象編程:接口與多態

16. 關鍵字:make 和 new 的區別?

17. 一篇文章理解 Go 裏的語句塊與作用域

18. 學習 Go 協程:goroutine

19. 學習 Go 協程:詳解信道/通道

20. 幾個信道死鎖經典錯誤案例詳解

21. 學習 Go 協程:WaitGroup

22. 學習 Go 協程:互斥鎖和讀寫鎖

23. Go 裏的異常處理:panic 和 recover

24. 超詳細解讀 Go Modules 前世今生及入門使用

25. Go 語言中關於包導入必學的 8 個知識點

26. 如何開源自己寫的模塊給別人用?

27. 說說 Go 語言中的類型斷言?

28. 這五點帶你理解Go語言的select用法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章