【go語言】wait,wait for me

去年輸出了一系列golang的編碼文章,但總感覺有話沒講,但又不清楚具體是什麼,所以本文以隨筆爲主。



我們知道函數的調用其實就是一個入棧和出棧的動作:

main() --> normal()

如果用這個表示調用,那麼在堆棧中就是把函數normal()的入口地址push,當函數normal()執行完畢後,堆棧pop函數normal()地址,同時返回到main()的調用處。


試想當執行函數normal()時出現異常時,會有什麼情況發生呢?

package main


import (

    "fmt"

)


func normal(a int64) int64 {

        var b int64 = 0

        return a / b

}


func main() {

        var a int64 = 64

        fmt.Println("a/b= ", normal(a))

}

你肯定想,這會拋錯呀~

是的,U are right,會拋出panic: runtime error: integer divide by zero.


進一步想,如果var b int64 = 0不是簡單的賦值,而是一塊內存的分配,不幸的是,剛分配完內存就拋異常了,那麼該內存就永遠沒有被釋放的機會。


如何解決?其它語言有:try、catch、finally等關鍵字。

golang採用了defer關鍵字,該關鍵字用於告訴程序:“wait,wait,我做點事情之後,你再退出本次調用”。



func normal(a int64) int64 {

        defer fmt.Println("wait, wait for me.")

        var b int64 = 0

        return a / b

}

修改normal()函數,在函數體內增加defer再運行,就會發現在拋異常之前把“wait, wait for me.”打印出來了。


這表示在函數normal()被調用之後,64/0遇到了問題,此時golang會拋出panic前,defer說等等,等我打印點東西后,你再拋。



【defer語法】:

defer 表達式


func normal(a int64) int64 {

        defer func() {

                 fmt.Println("panic will be throwen.")

        }()

        var b int64 = 0

        return a / b

}

當表達式是一個匿名函數時,一定要記得後面追加(),這表示是一個表達式 :)



【defer使用場景】:

defer一般使用在函數體開始,或者緊跟着申請資源的語句後面

不建議把defer放到函數體的後面。修改一下上面的示例:

func normal(a int64) {

var b int64 = 0

fmt.Println("a/b= ", a/b)

defer func() {

fmt.Println("panic will be throwen.")

}()

}


func main() {

var a int64 = 64

normal(a)

}

此時的defer已無意義,所以"panic will be throwen."不會被打印出來。




【多個順序defer】:

被調用函數中若有多個順序defer,則先會出現“先定義後執行”現象

func main() {

defer fmt.Println("0")

defer fmt.Println("1")

defer fmt.Println("2")

defer fmt.Println("3")

defer fmt.Println("4")

fmt.Println("Test multi defers")

}

執行結果爲:

Test multi defers

4

3

2

1

0

想想這很自然,從堆棧來看,越是後面定義的defer越是處於堆棧的棧頂。


該代碼可以精簡爲:

func main() {

        for i := 0; i < 5; i++ {

              defer fmt.Println(i)

        }

        fmt.Println("Test multi defers")

}



【defer表達式中存在函數調用】:

defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值


感覺這句話比較繞口,不好難理解?先看一個例子:

func history(date string) string {  // 打印"2016 will be history",並返回"2017"字符串

s := date + " will be history."

fmt.Println(s)

return "2017"

}


func future(date string) string {  // 打印"2017 will be coming",並返回"2017 will be coming"字符串

s := date + " will be coming."

fmt.Println(s)

return s

}


func main() {

defer future(history("2016"))

fmt.Println("It's the Spring Festival now.")

}

對照着defer future(history("2016"))理解一下“傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值”。

  • 延遲函數:future()

  • 延遲函數的參數:history("2016")

由於延遲函數的參數會被求值,即history("2016")會被執行,所以會先指印出“2016 will be history”,同時延遲函數變爲future("2017"),它要求被延遲執行。

從而該程序執行結果爲:

2016 will be history.

It's the Spring Festival now.

2017 will be coming.


感覺“defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值”這語句已經理解了,請再看下面的例子:

func main() {

for i := 0; i < 5; i++ {

defer func() {

fmt.Println(i)

}()

}

fmt.Println("Test multi defers")

}

它的運行結果爲:

Test multi defers

5

5

5

5

5

是不是有點懵逼了?

對照着defer func(){

                    fmt.Println(i)

          }()

理解一下“傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值

  • 延遲函數表達式:

       func() {

               fmt.Println(i)

       }()

在defer語句被執行時,該表達式並不會被求值,即被執行,i值你自己玩吧,所以等循環完成之後i值變爲5,再打印出“Test multi defers”,函數馬上要return時,這5個defer分別說:“wait, wait for me.”。

於是第5個defer表達式被執行,打印i值(這時i值爲5),所以打印出:

5

於是第4個defer表達式被執行,打印i值(這時i值爲5),所以打印出:

5

於是第3個defer表達式被執行,打印i值(這時i值爲5),所以打印出:

5

於是第2個defer表達式被執行,打印i值(這時i值爲5),所以打印出:

5

於是第1個defer表達式被執行,打印i值(這時i值爲5),所以打印出:

5

從而出現該結果 :)



接下來咋玩?

func main() {

for i := 0; i < 5; i++ {

defer func(n int) {

fmt.Println(n)

}(i)

}

fmt.Println("Test multi defers")

}

再理解"defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值"一下:

當i=0時

  • 延遲函數調用表達式:func(n int) { fmt.Println(n) }(i)

  • 延遲函數的參數:n

延遲函數調用表達式不會被求值,但延遲函數的參數i會被求值,所以n值變爲0


當i=1時

  • 延遲函數調用表達式:func(n int) { fmt.Println(n) }(i)

  • 延遲函數的參數:n

延遲函數調用表達式不會被求值,但延遲函數的參數i會被求值,所以n值變爲1


依次類推,從而最終執行結果爲:

Test multi defers

4

3

2

1

0



defer好玩吧,上面的例子有的來源自於網絡上其他人的博客。

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