golang之defer理解
阿新 • • 發佈:2018-12-18
defer
呼叫是一個棧結構
defer會在函式退出前執行,而且滿足後進先出
,類似棧.
直接呼叫deferCommon會輸出:9,8,…,0, done.
func deferCommon() {
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
defer
的作用域是一個函式,不是一個語句塊
事實上上述程式碼會有一個警告,不過使用列印可能不太明顯, 最常見的讀檔案為例子來看看:
func deferLoop() {
filenames := []string{"1.txt", "2.txt" }
for _, filename := range filenames {
fp, err := os.Open(filename)
if err != nil {
log.Printf("open file %s failed, err:%v", filename, err)
continue
}
// Possible resource leak, 'defer' is called in a for loop.
// 這句程式碼會造成迴圈結束後才開始回收資源,而不是執行了一次迴圈就回收一次資源
defer fp.Close()
// 使用fp(file pointer)
}
}
上面這種defer呼叫可能會造成資源無法回收,可以去掉defer直接調fp.Close().
鏈式呼叫
簡單來說,在defer x.m1().m2()
中,m1會直接被呼叫,而m2會在最後被呼叫,
類似:
x.m1()
defer x.m2()
看測試程式碼:
type logger struct{}
func (l *logger) Print(s interface{}) {
fmt.Printf("Log: %v\n", s)
}
type customLogger struct {
l *logger
}
func (f *customLogger) Logger () *logger {
fmt.Println("Logger()")
return f.l
}
func do(f *customLogger) {
// f.Logger()會“直接呼叫”,而f.Print()會“延遲呼叫”
defer f.Logger().Print("done")
fmt.Println("do")
}
// 如果你在一個 defer 語句中鏈式呼叫方法,
// 那麼除了最後一個函式以外其餘函式都會在呼叫時直接執行。
// defer 要求一個函式作為 “引數” 。
// 輸出:
// Logger()
// do
// Log: done
func deferFuncChain() {
f := &customLogger{
l: &logger{},
}
do(f)
}
靜態使用域
在看明白了上面鏈式呼叫的例子後,再進一步,
如果修改上述的do
方法如下, 又會如何(logger及customLogger的定義和方法都不變):
func do2(f *customLogger) (err error) {
defer f.Logger().Print(err)
fmt.Println("do")
// 和直接return fmt.Errorf("ERROR")一樣
// err = fmt.Errorf("ERROR")
// return
return fmt.Errorf("ERROR")
}
// 輸出:
// Logger()
// do
// Log: <nil>
// 最後的Log: <nil>說明,golang是使用的是靜態作用域
func deferFuncChainWithParam() {
f := &customLogger{
l: &logger{},
}
do2(f)
}
重點說下最後那個Log: <nil>
, 個人理解是 因為golang是使用的是靜態作用域。
由於在 defer f.Logger().Print(err) 這句前,
err已經聲明瞭 *func do2(f customLogger) (err error) { , 此時err是nil.
可以給一個靜態作用域的例子:
var a = 1
func test() {
fmt.Println("in test, a is", a)
}
func main() {
var a = 0
fmt.Println("in main, a is", a)
test()
fmt.Println("after test() call, a is", a)
}
上述程式碼輸出為:
in main, a is 0 in test, a is 1 after test() call, a is 0
在我理解,test()引用的是最開始那個a,也就體現了golang使用的是靜態作用域。 順便提一句,js也是使用的靜態作用域。
針對非指標型別呼叫函式
type project struct {
tagline string
}
func (p project) log() {
fmt.Printf("project: %v\n", p)
}
func deferCopy() {
m := project{"test"}
defer m.log()
m.tagline = "test2"
}
輸出結果:
test
如果把func (p project) log() {
改成func (p *project) log() {
, 那麼會輸出test2。
因為非指標型別呼叫log會執行一次拷貝。
總結
- 值接收者呼叫方法會執行拷貝
- 靜態作用域
參考
歡迎補充指正!