defer
:在函數A內用defer關鍵字調用的函數B會在在函數A return
後執行。golang
先看一個基礎的例子,瞭解一下defer的效果shell
func main() { fmt.Println("in main func:", foo()) } func foo() int { i := 0 defer fmt.Println("in defer :", i) i = 1000 fmt.Println("in foo:", i) return i+24 }
這段代碼運行後會打印出數據庫
in foo: 1000 in defer : 0 in main func: 1024
變量i
初始化爲0
,defer
指定fmt.Println
函數延遲到return
後執行,最後main
函數調用foo
打印返回值。函數
函數中會申明使用不少變量資源,函數結束時,咱們一般會對它們作一些處理:銷燬、釋放(例如數據庫連接、文件句柄、流)。spa
通常狀況下,咱們會在return
語句以前處理這些事情。設計
可是,若是函數中包含多個return
,這些處理咱們須要在每一個return
以前都操做一次,實際工做中常常出現遺漏,代碼維護時也很麻煩。指針
例如,在不用defer
的時候,代碼可能會這樣寫:code
func foo(i int) int { if i > 100 { fmt.Println("不是期待的數字") i = 0 return i } if i < 50 { fmt.Println("不是期待的數字") i = 0 return i } return i }
使用defer後,代碼能夠這樣寫對象
func foo(i int) int { defer func() { if i == 0 { fmt.Println("不是期待的數字") } }() if i > 100 { i = 0 return i } if i < 50 { i = 0 return i } return i }
defer
在同一個函數中可使用屢次。blog
多個defer
指定的函數執行順序是"先進後出"。
爲何呢 ?
能夠這樣理解:defer
關鍵字會使其如下的代碼先執行後再執行它指定的函數,包括其下的defer
語句也會比其先執行,依此類推。
這個順序很是必要,由於在函數中,後面定義的對象可能依賴前面的對象,不然若是先出現的defer
執行了,極可能形成後面的defer
執行的時候出現異常。
因此,Go語言設計defer的時候是按先進後出的順序執行的。
例子:
func foo() { i := 0 defer func() { i-- fmt.Println("第一個defer", i) }() i++ fmt.Println("+1後的i:", i) defer func() { i-- fmt.Println("第二個defer", i) }() i++ fmt.Println("再+1後的i:", i) defer func() { i-- fmt.Println("第三個defer", i) }() i++ fmt.Println("再+1後的i:", i) }
運行後能夠看到
+1後的i: 1 再+1後的i: 2 再+1後的i: 3 第三個defer 2 第二個defer 1 第一個defer 0
這個過程能夠看出函數執行後,先進後出執行defer
並逐步處理變量的過程。
網上有一些總結是說:defer指定的函數的參數在 defer 時肯定,但,這只是一個總結,真正的緣由是, Go語言除了map、slice、chan都是值傳遞。
改造一下上面這個例子
func foo() { i := 0 defer func(k int) { fmt.Println("第一個defer", k) }(i) i++ fmt.Println("+1後的i:", i) defer func(k int) { fmt.Println("第二個defer", k) }(i) i++ fmt.Println("再+1後的i:", i) defer func(k int) { fmt.Println("第三個defer", k) }(i) i++ fmt.Println("再+1後的i:", i) }
獲得的結果
+1後的i: 1 再+1後的i: 2 再+1後的i: 3 第三個defer 2 第二個defer 1 第一個defer 0
可能會有人以爲有一點出乎預料,i
在return時不是已經被計算到3了嗎?,爲何延遲執行的defer指定的函數裏的i
不是3
呢?
defer關鍵字指定的函數是在return
後執行的,這很容易讓人想象在return
後調用函數。
可是,defer指定的函數是在當前行就調用了的,只是延遲到return
後執行,而不等同於「移動」到return
後執行,所以調用時傳遞的是當前的參數的值。
那麼若是但願defer
指定的的函數參數的值是通過後面的代碼處理過的,能夠傳遞指針參數給defer
指定的函數。
改造一下代碼:
func foo() { i := 0 defer func(k *int) { fmt.Println("第一個defer", *k) }(&i) i++ fmt.Println("+1後的i:", i) defer func(k *int) { fmt.Println("第二個defer", *k) }(&i) i++ fmt.Println("再+1後的i:", i) defer func(k *int) { fmt.Println("第三個defer", *k) }(&i) i++ fmt.Println("再+1後的i:", i) }
運行後獲得
+1後的i: 1 再+1後的i: 2 再+1後的i: 3 第三個defer 3 第二個defer 3 第一個defer 3
在開頭的第一個例子中能夠看到,defer
是在foo
執行完,main
裏打印返回值以前執行的,可是沒有影響到main
裏的打印結果。
這仍是由於相同的原則 Go語言除了map、slice、chan都是值傳遞
比較一下foo1
和foo2
兩個函數的結果:
func main() { fmt.Println("foo1 return :", foo1()) fmt.Println("foot return :", foo2()) } func foo1() int { i := 0 defer func() { i = 1 }() return i } func foo2() map[string]string { m := map[string]string{} defer func() { m["a"] = "b" }() return m }
運行後,打印出
foo1 return : 0 foot return : map[a:b]
兩個函數不一樣之處在於的返回值的類型,foo1中,int類型return後,defer不會影響返回結果,可是在foo2中map類型是引用傳遞,因此defer會改變返回結果。
這說明,在return時,除了map、slice、chan
,其餘類型return
時是將值拷貝到一個臨時變量空間,所以,defer
指定的函數內對函數內的變量的操做不會影響返回結果的。
還有一種狀況,給函數返回值申明變量名,,這時,變量空間是在函數執行前申明出來,return
時只是返回這個變量空間的內容,所以defer
可以改變返回值。
例如,改造一下foo1
函數,給它的返回值申明一個變量名i
:
func foo1() (i int) { i = 0 defer func() { i = 1 }() return i }
再運行,能夠看到 :
foo1 return : 1
返回值被defer
指定的函數修改了。
在Go語言裏,defer
有一個經典的使用場景就是recover
.
在函數執行過程當中,有可能在不少地方都會出現panic
,panic
後若是不調用recover
,程序會退出,爲了避免讓程序退出,咱們須要在panic
後調用recover
,但,panic
後的代碼不會執行,recover
是不可能在panic
後調用,然而panic
所在的函數內defer
指定的函數能夠執行,因此recover
只能在defer
指定的函數中被調用,而且只須要在1個defer
指定的函數中處理。
例如:
func panicfunc() { defer func() { fmt.Println("before recover") recover() fmt.Println("after recover") }() fmt.Println("before panic") panic(0) fmt.Println("after panic") }
運行後,打印出:
before panic before recover after recover
return
和panic/recover
場景下使用defer