去年輸出了一系列golang的編碼文章,但總感受有話沒講,但又不清楚具體是什麼,因此本文以隨筆爲主。
golang
咱們知道函數的調用其實就是一個入棧和出棧的動做:
網絡
main() --> normal()ide
若是用這個表示調用,那麼在堆棧中就是把函數normal()的入口地址push,當函數normal()執行完畢後,堆棧pop函數normal()地址,同時返回到main()的調用處。函數
試想當執行函數normal()時出現異常時,會有什麼狀況發生呢?編碼
package mainspa
import (orm
"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好玩吧,上面的例子有的來源自於網絡上其餘人的博客。