天天看點

Go語言defer語句

select

語句一樣,Go語言中的

defer

語句也非常獨特,而且比前者有過之而無不及。

defer

語句僅能被放置在函數或方法中。它由關鍵字

defer

和一個調用表達式組成。注意,這裡的調用表達式所表示的既不能是對Go語言内建函數的調用也不能是對Go語言标準庫代碼包

unsafe

中的那些函數的調用。實際上,滿足上述條件的調用表達式被稱為表達式語句。請看下面的示例:

func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}
      

    函數

readFile

的功能是讀出指定檔案或目錄(以下統稱為檔案)本身的内容并将其傳回,同時當有錯誤發生時立即向調用方報告。其中,

os

ioutil

(導入路徑是

io/ioutil

)代表的都是Go語言标準庫中的代碼包。請注意這個函數中的倒數第二條語句。我們在打開指定檔案且未發現有錯誤發生之後,緊跟了一條

defer

語句。其中攜帶的表達式語句表示的是對被打開檔案的關閉操作。注意,當這條

defer

語句被執行的時候,其中的這條表達式語句并不會被立即執行。它的确切的執行時機是在其所屬的函數(這裡是

readFile

)的執行即将結束的那個時刻。也就是說,在

readFile

函數真正結束執行的前一刻,

file.Close()

才會被執行。這也是

defer

語句被如此命名的原因。我們在結合上下文之後就可以看出,語句

defer file.Close()

的含義是在打開檔案并讀取其内容後及時地關閉它。該語句可以保證在

readFile

函數将結果傳回給調用方之前,那個檔案或目錄一定會被關閉。這實際上是一種非常便捷和有效的保險措施。

    更為關鍵的是,無論

readFile

函數正常地傳回了結果還是由于在其執行期間有運作時恐慌發生而被剝奪了流程控制權,其中的

file.Close()

都會在該函數即将退出那一刻被執行。這就更進一步地保證了資源的及時釋放。

    注意,當一個函數中存在多個

defer

語句時,它們攜帶的表達式語句的執行順序一定是它們的出現順序的倒序。下面的示例可以很好的證明這一點:

func deferIt() {
    defer func() {
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}
      

deferIt

函數的執行會使标準輸出上列印出

4321

。請大家猜測下面這個函數被執行時向标準輸出列印的内容,并真正執行它以驗證自己的猜測。最後論證一下自己的猜測為什麼是對或者錯的。

func deferIt2() {
    for i := 1; i < 5; i++ {
        defer fmt.Print(i)
    }
}      

    最後,對于

defer

語句,我還有兩個特别提示:

    1. 

defer

攜帶的表達式語句代表的是對某個函數或方法的調用。這個調用可能會有參數傳入,比如:

fmt.Print(i + 1)

。如果代表傳入參數的是一個表達式,那麼在

defer

語句被執行的時候該表達式就會被求值了。注意,這與被攜帶的表達式語句的執行時機是不同的。請揣測下面這段代碼的執行:

func deferIt3() {
    f := func(i int) int {
        fmt.Printf("%d ",i)
        return i * 10
    }
    for i := 1; i < 5; i++ {
        defer fmt.Printf("%d ", f(i))
    }
}
      

    它在被執行之後,标準輸出上列印出

1 2 3 4 40 30 20 10

 。

    2. 如果

defer

攜帶的表達式語句代表的是對匿名函數的調用,那麼我們就一定要非常警惕。請看下面的示例:

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}           

deferIt4

函數在被執行之後标出輸出上會出現

5555

,而不是

4321

。原因是

defer

語句攜帶的表達式語句中的那個匿名函數包含了對外部(确切地說,是該

defer

語句之外)的變量的使用。注意,等到這個匿名函數要被執行(且會被執行4次)的時候,包含該

defer

語句的那條

for

語句已經執行完畢了。此時的變量

i

的值已經變為了

5

。是以該匿名函數中的列印函數隻會列印出

5

。正确的用法是:把要使用的外部變量作為參數傳入到匿名函數中。修正後的

deferIt4

函數如下:

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func(n int) {
            fmt.Print(n)
        }(i)
    }
}      
轉載自慕課網(www.imooc.com),具體位址:https://www.imooc.com/code/8246