Go語言學習 十一 defer語句

本文最初發表在我的個人博客,查看原文,獲得更好的閱讀體驗


Go的defer語句用於延遲調用函數,該語句在執行defer的函數返回之前立即執行,換句話說,defer將函數推遲到外層函數執行完畢但返回之前執行。這在處理那些必須釋放資源等的情況下非常有用,無論當前函數執行結果如何。

類似於Java中的finallytry-with-resources

一 defer語句

語法:
defer function | method

defer只能後跟函數或方法。對內置函數的調用受限於表達式的限制。

表達式的限制:
表達式上下文中不允許使用以下內置函數:
append cap complex imag len make new real
unsafe.Alignof unsafe.Offsetof unsafe.Sizeof

示例:

package main

import (
	"fmt"
)

func main() {
	defer fmt.Println("world") // 該行將在hello之後打印

	fmt.Println("hello")
}

另外,如果延遲函數值的計算結果爲nil,則在執行延遲函數時會發生恐慌,而不是在執行defer語句時。

如果被推遲的函數是一個函數字面量,且包裹着它的函數有命名的結果參數在其訪問範圍內,延遲函數可能會在那些結果參數返回之前訪問並修改它們。如果被推遲的函數有返回值,它們將在返回時被丟棄。
示例:

lock(l)
defer unlock(l)  // unlock將在外層函數返回前執行

// 外層函數返回前會打印出3 2 1 0 
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f 返回 42
func f() (result int) {
	defer func() {
		// result在被下邊的return設置爲6之後又在此被訪問並修改
		result *= 7
	}()
	return 6
}

釋放互斥鎖或關閉文件的例子:

// Contents 以字符串形式返回文件內容。
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // f.Close 將在完成時運行。

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append稍後討論
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // 如果在此返回,f 將被關閉
        }
    }
    return string(result), nil // 如果在此返回,f 將被關閉
}

推遲對類似Close之類函數的調用有兩點好處。首先,能保證不會忘記關閉文件;其次,這意味着“關閉”位於“打開”附近,這比將其放在函數結尾要清晰得多。

推遲函數的參數(如果函數是一個方法,則包括接收器)在defer執行時就會計算,而不是在調用執行時計算。除了避免擔心變量在函數執行時被改變值之外,還意味着單個延遲調用可以推遲多個函數的執行,例如:

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

1 斐波那契閉包的defer版本

藉助defer的特性,我們還可以這樣實現上一篇文章中的斐波那契閉包:

package main

import "fmt"

func fibonacci() func() int {
	a, b := 0, 1
	return func() int {
		defer func() {
			a, b = b, a+b
		}()
		return a
	}
}

func main() {
	f := fibonacci()

	for i := 0; i < 15; i++ {
		fmt.Println(f())
	}
}

二 defer棧

推遲的函數調用會被壓入一個棧中。當外層函數返回時,被推遲的函數以後進先出(LIFO)的順序執行,所以上個小節中的代碼執行完打印的結果爲4 3 2 1 0
一個更合理的例子是通過程序跟蹤函數執行的簡單方法:

func trace(s string)   { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

// Use them like this:
func a() {
    trace("a")
    defer untrace("a")
    // do something....
}

我們可以通過利用defer執行時計算延遲函數的參數這個特點來做一些更有意義的事情。跟蹤例程可以設置反跟蹤例程的參數。如下:

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

上述代碼會輸出:

entering: b
in b
entering: a
in a
leaving: a
leaving: b

更多關於 defer 語句的信息,請閱讀此博文

參考:
https://golang.org/doc/effective_go.html#defer
https://golang.org/ref/spec#Defer_statements

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