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

Hi,你們好,我是明哥。java

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

個人在線博客:http://golang.iswbm.com
個人 Github:github.com/iswbm/GolangCodingTimegit


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

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

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

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用法


相關文章
相關標籤/搜索