【go語言】wait,wait for me

airland發表於2021-09-09

去年輸出了一系列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好玩吧,上面的例子有的來源自於網路上其他人的部落格。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/964/viewspace-2818270/,如需轉載,請註明出處,否則將追究法律責任。

相關文章