天天看點

【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,我做點事情之後,你再退出本次調用”。

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

        var b int64 = 0

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

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

【defer文法】:

defer 表達式

        defer func() {

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

        }()

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

【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.")

}()

var a int64 = 64

normal(a)

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

【多個順序defer】:

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

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

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

該代碼可以精簡為:

        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."

return s

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語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式并不會在此時被求值”這語句已經了解了,請再看下面的例子:

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

defer func() {

fmt.Println(i)

它的運作結果為:

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),是以列印出:

于是第4個defer表達式被執行,列印i值(這時i值為5),是以列印出:

于是第3個defer表達式被執行,列印i值(這時i值為5),是以列印出:

于是第2個defer表達式被執行,列印i值(這時i值為5),是以列印出:

于是第1個defer表達式被執行,列印i值(這時i值為5),是以列印出:

進而出現該結果 :)

接下來咋玩?

defer func(n int) {

fmt.Println(n)

}(i)

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

當i=0時

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

延遲函數的參數:n

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

當i=1時

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

依次類推,進而最終執行結果為:

defer好玩吧,上面的例子有的來源自于網絡上其他人的部落格。

     本文轉自qingkechina 51CTO部落格,原文連結:http://blog.51cto.com/qingkechina/1895048,如需轉載請自行聯系原作者