去年輸出了一系列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,我做點事情之後,你再退出本次調用”。
func normal(a int64) int64 {
defer fmt.Println("wait, wait for me.")
var b int64 = 0
return a / b
}
修改normal()函數,在函數體內增加defer再運行,就會發現在拋異常之前把“wait, wait for me.”打印出來了。
這表示在函數normal()被調用之後,64/0遇到了問題,此時golang會拋出panic前,defer說等等,等我打印點東西后,你再拋。
【defer語法】:
defer 表達式
func normal(a int64) int64 {
defer func() {
fmt.Println("panic will be throwen.")
}()
var b int64 = 0
return a / b
}
當表達式是一個匿名函數時,一定要記得後面追加(),這表示是一個表達式 :)
【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.")
}()
}
func main() {
var a int64 = 64
normal(a)
}
此時的defer已無意義,所以"panic will be throwen."不會被打印出來。
【多個順序defer】:
被調用函數中若有多個順序defer,則先會出現“先定義後執行”現象
func main() {
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
0
想想這很自然,從堆棧來看,越是後面定義的defer越是處於堆棧的棧頂。
該代碼可以精簡爲:
func main() {
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."
fmt.Println(s)
return s
}
func main() {
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語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值”這語句已經理解了,請再看下面的例子:
func main() {
for i := 0; i < 5; i++ {
defer func() {
fmt.Println(i)
}()
}
fmt.Println("Test multi defers")
}
它的運行結果爲:
Test multi defers
5
5
5
5
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),所以打印出:
5
於是第4個defer表達式被執行,打印i值(這時i值爲5),所以打印出:
5
於是第3個defer表達式被執行,打印i值(這時i值爲5),所以打印出:
5
於是第2個defer表達式被執行,打印i值(這時i值爲5),所以打印出:
5
於是第1個defer表達式被執行,打印i值(這時i值爲5),所以打印出:
5
從而出現該結果 :)
接下來咋玩?
func main() {
for i := 0; i < 5; i++ {
defer func(n int) {
fmt.Println(n)
}(i)
}
fmt.Println("Test multi defers")
}
再理解"defer語句被執行的時候,傳遞給延遲函數的參數都會被求值,但是延遲函數調用表達式並不會在此時被求值"一下:
當i=0時
延遲函數調用表達式:func(n int) { fmt.Println(n) }(i)
延遲函數的參數:n
延遲函數調用表達式不會被求值,但延遲函數的參數i會被求值,所以n值變爲0
當i=1時
延遲函數調用表達式:func(n int) { fmt.Println(n) }(i)
延遲函數的參數:n
延遲函數調用表達式不會被求值,但延遲函數的參數i會被求值,所以n值變爲1
依次類推,從而最終執行結果爲:
Test multi defers
4
3
2
1
0
defer好玩吧,上面的例子有的來源自於網絡上其他人的博客。