下面程式分別輸出什麼?
func f1()
}func f2() ()}}
func f3() (i)}}
func f4() int ()
return t
}func f5() (r int) ()
return 0
}func f6() (r int) ()
return t
}func f7() (r int) ®
return 1
}同樣的,我們先不急著作答,我們先來看看defer的講解。
defer是什麼及用途
defer是什麼?
defer 就像它的字面意思一樣,就是延遲執行的意思,但是需要注意的是 defer 只能作用於函式,像變數賦值defer i = 10這種編譯是會報錯的。
defer函式的執行順序
被 defer 的函式會放入乙個棧中,所以是先進後出的執行順序,而被 defer 的函式在 return 之後執行。
清理釋放資源
當我們開啟乙個檔案時,用完之後我們需要 close 這個檔案,否則會導致檔案描述符洩露,不用 defer 的**一般是這樣寫的:
func copyfile(dstname, srcname string) (written int64, err error)
dst, err := os.create(dstname)
if err != nil
written, err = io.copy(dst, src)
dst.close()
src.close()
return
}
上面**檔案都close了,但這裡有乙個問題:如果os.open(srcname)成功了,然後在os.create(dstname)發錯誤了,這時的 src 卻沒有 close ,這樣就會導致src的檔案描述符洩露了。
修復這個問題也很簡單,我們在os.create(dstname)發生錯誤的時候,將src close掉就行了。但是問題又來了如果是多個檔案同時開啟,那這段**將會非常的臃腫,而且會很容易的漏掉一些檔案的 close。
使用defer可以完美的解決這個問題。
func copyfile(dstname, srcname string) (written int64, err error)
defer src.close()
dst, err := os.create(dstname)
if err != nil
defer dst.close()
return io.copy(dst, src)
}
上面**中,只需在檔案操作成功的時候呼叫 defer close 就可以了,而且檔案的 open 和 close 放在一起也不容易漏掉。
執行recover
被 defer 的函式在 return 之後執行,這個時機點正好可以捕獲函式丟擲的 panic,因而defer 的另乙個重要用途就是執行 recover ,而 recover 也只有在 defer 中才會起作用。
func test()
}()panic(「error」)
}另外需要注意的是,recover 要放在 panic 點的前面,一般放在函式的起始的位置就可以了。
defer與return的關係
go 的函式返回值是通過堆疊返回的,這也是實現了多返回值的方法。看以下**:
// foo.go
package main
func foo() (int, int)
func main()
生成的彙編**如下:
$ go build -gcflags 『-l』 -o foo foo.go
$ go tool objdump -s 「main.foo」 foo
text main.foo(sb) /users/kltao/code/go/src/example/foo.go
bar.go:6 0x104ea70 48c744240801000000 movq $0x1, 0x8(sp)
bar.go:6 0x104ea79 48c744241002000000 movq $0x2, 0x10(sp)
bar.go:6 0x104ea82 c3 ret
也就是說 return 語句不是原子操作,它被拆成了兩步
rval = *** // 返回值賦值給rval
ret // 函式返回
而 defer 語句就是在這兩條語句之間執行,也就是
rval = *** // 返回值賦值給rval
defer_func // 執行defer函式
ret // 函式返回
上面的題目中,還涉及到另外乙個知識點,那就是閉包。
簡單來說,go 語言中的閉包就是在函式內引用函式體之外的資料,這樣就會產生一種結果,雖然資料定義是在函式外,但是在函式內部運算元據也會對資料產生影響。看下面的例子:
func foo()
fmt.println(i) // 輸出2
}上面的 i 就是乙個閉包引用,當匿名函式執行時,i 也會被修改。
題目解析
下面統一將rval稱為函式最終return的變數值
func f1()
}// 因為defer的呼叫是先進後出的順序
// 所以輸出:5, 4, 3, 2, 1
func f2() ()}}
// 上面說到,i是乙個閉包引用
// 所以當執行defer時,i已經是5了
// 所以輸出:5,5,5,5,5
func f3() (i)}}
// go的函式引數是值拷貝,所以這是普通的函式傳值
// 所以輸出:5,4,3,2,1
func f4() int ()
return t
}// 注意:f4函式的返回值是沒有宣告變數的
// 所以t雖然是閉包引用,但返回值rval不是閉包引用
// 可以拆解為
// rval = t
// t++
// return rval
// 所以輸出是5
func f5() (r int) ()
return 0
}// 注意:f5函式的返回值是有宣告變數的
// 所以返回值r是閉包引用
// 可以拆解為
// r = 0
// rval = r
// r++
// return rval
// 所以輸出:1
func f6() (r int) ()
return t
}// 這裡t雖然是閉包引用,但返回值r不是閉包引用
// 可以拆解為
// r = t
// rval = r
// t = t + 5
// return rval
// 所以輸出:5
func f7() (r int) ®
return 1
}// 因為匿名函式的引數也是r,所以相當於是
// 匿名函式的引數r = r + 5,不影響外部
// 所以輸出:1
做這種defer的題,需要注意返回值是否為閉包引用。
總結謹記defer和return執行的順序
注意返回值是否為閉包引用
今天的分享就到這裡,不知道大家有沒有學會呢,記得收藏或者記到小本本上哦,希望大家都能找到自己喜歡合適的工作哦,小猿圈-it自學人的小圈子。
小猿圈分享6個 JavaScript 小技巧(下)
nee necountry us state new yourk 複製 6.物件 6.1 使用解構刪除不必要屬性 有時候你不希望保留某些物件屬性,也許是因為它們包含敏感資訊或僅僅是太大了 just too big 你可能會列舉整個物件然後刪除它們,但實際上只需要簡單的將這些無用屬性賦值給變數,然後把...
python小猿 小猿圈python學習 內建函式
python的len為什麼你可以直接用?肯定是直譯器啟動時就定義好了 每個函式的作用我都幫你標好了 abs 求絕對值 all return true if bool x is true for all values x in the iterable.if the iterable is empty...
小猿圈IT自學分享 自學程式設計需要克服的困難
隨著網際網路行業的蓬勃發展,越來越多的年輕人選擇改行做程式設計師,有些人選擇報名學習班,也有一些人選擇自學,因為現在網上的學習資料太多了,但是自學的話也不是那麼簡單的,需要客服很多困難,也有很多阻擋學習的因素,下邊就是小猿圈給大家總結的幾點自學程式設計需要克服的困難 1.充足的時間 如果你正在自學程...