去年輸出了一系列golang的編碼文章,但總感覺有話沒講,但又不清楚具體是什麼,是以本文以随筆為主。
我們知道函數的調用其實就是一個入棧和出棧的動作:
main() --> normal()
如果用這個表示調用,那麼在堆棧中就是把函數normal()的入口位址push,當函數normal()執行完畢後,堆棧pop函數normal()位址,同時傳回到main()的調用處。
試想當執行函數normal()時出現異常時,會有什麼情況發生呢?
package main
import (
"fmt"
)
func normal(a int64) int64 {
var b int64 = 0
return a / b
}
func main() {
var a int64 = 64
fmt.Println("a/b= ", normal(a))
你肯定想,這會抛錯呀~
是的,U are right,會抛出panic: runtime error: integer divide by zero.
進一步想,如果var b int64 = 0不是簡單的指派,而是一塊記憶體的配置設定,不幸的是,剛配置設定完記憶體就抛異常了,那麼該記憶體就永遠沒有被釋放的機會。
如何解決?其它語言有:try、catch、finally等關鍵字。
golang采用了defer關鍵字,該關鍵字用于告訴程式:“wait,wait,我做點事情之後,你再退出本次調用”。
defer fmt.Println("wait, wait for me.")
var b int64 = 0
修改normal()函數,在函數體内增加defer再運作,就會發現在抛異常之前把“wait, wait for me.”列印出來了。
這表示在函數normal()被調用之後,64/0遇到了問題,此時golang會抛出panic前,defer說等等,等我列印點東西後,你再抛。
【defer文法】:
defer 表達式
defer func() {
fmt.Println("panic will be throwen.")
}()
當表達式是一個匿名函數時,一定要記得後面追加(),這表示是一個表達式 :)
【defer使用場景】:
defer一般使用在函數體開始,或者緊跟着申請資源的語句後面
不建議把defer放到函數體的後面。修改一下上面的示例:
func normal(a int64) {
var b int64 = 0
fmt.Println("a/b= ", a/b)
defer func() {
fmt.Println("panic will be throwen.")
}()
var a int64 = 64
normal(a)
此時的defer已無意義,是以"panic will be throwen."不會被列印出來。
【多個順序defer】:
被調用函數中若有多個順序defer,則先會出現“先定義後執行”現象
defer fmt.Println("0")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
fmt.Println("Test multi defers")
執行結果為:
Test multi defers
4
3
2
1
想想這很自然,從堆棧來看,越是後面定義的defer越是處于堆棧的棧頂。
該代碼可以精簡為:
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
fmt.Println("Test multi defers")
【defer表達式中存在函數調用】:
defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式并不會在此時被求值。
感覺這句話比較繞口,不好難了解?先看一個例子:
func history(date string) string { // 列印"2016 will be history",并傳回"2017"字元串
s := date + " will be history."
fmt.Println(s)
return "2017"
func future(date string) string { // 列印"2017 will be coming",并傳回"2017 will be coming"字元串
s := date + " will be coming."
return s
defer future(history("2016"))
fmt.Println("It's the Spring Festival now.")
對照着defer future(history("2016"))了解一下“傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式并不會在此時被求值”。
延遲函數:future()
延遲函數的參數:history("2016")
由于延遲函數的參數會被求值,即history("2016")會被執行,是以會先指印出“2016 will be history”,同時延遲函數變為future("2017"),它要求被延遲執行。
進而該程式執行結果為:
2016 will be history.
It's the Spring Festival now.
2017 will be coming.
感覺“defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式并不會在此時被求值”這語句已經了解了,請再看下面的例子:
for i := 0; i < 5; i++ {
defer func() {
fmt.Println(i)
它的運作結果為:
5
是不是有點懵逼了?
對照着defer func(){
fmt.Println(i)
}()
了解一下“傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式并不會在此時被求值”
延遲函數表達式:
func() {
fmt.Println(i)
}()
在defer語句被執行時,該表達式并不會被求值,即被執行,i值你自己玩吧,是以等循環完成之後i值變為5,再列印出“Test multi defers”,函數馬上要return時,這5個defer分别說:“wait, wait for me.”。
于是第5個defer表達式被執行,列印i值(這時i值為5),是以列印出:
于是第4個defer表達式被執行,列印i值(這時i值為5),是以列印出:
于是第3個defer表達式被執行,列印i值(這時i值為5),是以列印出:
于是第2個defer表達式被執行,列印i值(這時i值為5),是以列印出:
于是第1個defer表達式被執行,列印i值(這時i值為5),是以列印出:
進而出現該結果 :)
接下來咋玩?
defer func(n int) {
fmt.Println(n)
}(i)
再了解"defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式并不會在此時被求值"一下:
當i=0時
延遲函數調用表達式:func(n int) { fmt.Println(n) }(i)
延遲函數的參數:n
延遲函數調用表達式不會被求值,但延遲函數的參數i會被求值,是以n值變為0
當i=1時
延遲函數調用表達式不會被求值,但延遲函數的參數i會被求值,是以n值變為1
依次類推,進而最終執行結果為:
defer好玩吧,上面的例子有的來源自于網絡上其他人的部落格。
本文轉自qingkechina 51CTO部落格,原文連結:http://blog.51cto.com/qingkechina/1895048,如需轉載請自行聯系原作者