本文最初發表在我的個人博客,查看原文,獲得更好的閱讀體驗
Go的defer
語句用於延遲調用函數,該語句在執行defer
的函數返回之前立即執行,換句話說,defer
將函數推遲到外層函數執行完畢但返回之前執行。這在處理那些必須釋放資源等的情況下非常有用,無論當前函數執行結果如何。
類似於Java中的
finally
或try-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