Golang 中的 defer
面試常問之 defer() 的執行次序
情形 1
package main
func main() {
defer print(123)
defer_call()
defer print(789) //panic之後的代碼不會被執行
print("不會執行到這裏")
}
func defer_call() {
defer func() {
print("打印前")
}()
defer func() {
print("打印中")
}()
defer print("打印後")
panic("觸發異常")
defer print(666) //IDE會有提示: Unreachable code
}
結果爲:
打印後打印中打印前123panic: 觸發異常
goroutine 1 [running]:
main.defer_call()
/Users/shuangcui/explore/panicandrecover.go:19 +0xe5
main.main()
/Users/shuangcui/explore/panicandrecover.go:6 +0x51
可見:
-
panic 之後的 defer() 不會被執行
-
panic 之前的 defer(), 按照先進後出的次序執行, 最後輸出 panic 信息
(defer 機制底層, 是用鏈表實現的一個棧)
再如:
func main() {
fmt.Println(123)
defer fmt.Println(999)
subfunc()
}
func subfunc() {
defer fmt.Println(888)
for i := 0; i > 10; i++ {
fmt.Println("當前i爲:", i)
panic("have a bug")
}
defer fmt.Println(456)
}
結果爲:
123
456
888
999
defer 會延遲到當前函數執行 return 命令前被執行, 多個 defer 之間按 LIFO 先進後出順序執行
情形 2 (在 defer 內打印 defer 之外的主方法裏操作的變量)
package main
import "fmt"
func main() {
foo()
}
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)
i = i + 666
fmt.Println("+666後的i爲:", i)
}
輸出爲:
+1後的i: 1
再+1後的i: 2
再再+1後的i: 3
+666後的i爲: 669
第三個defer 669
第二個defer 669
第一個defer 669
情形 3 (在 defer 內外操作同一變量)
package main
import "fmt"
func main() {
foo()
}
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)
i = i + 666
fmt.Println("+666後的i爲:", i)
}
輸出爲:
+1後的i: 1
再+1後的i: 2
再再+1後的i: 3
+666後的i爲: 669
第三個defer 668
第二個defer 667
第一個defer 666
情形 4! (發生了參數傳遞!---
傳遞參數給 defer 後面的函數, defer 內外同時操作該參數)
package main
import "fmt"
func main() {
foo2()
}
func foo2() {
i := 0
defer func(k int) {
//k--
fmt.Println("第一個defer", k)
}(i)
i++
fmt.Println("+1後的i:", i)
defer func(k int) {
//k--
fmt.Println("第二個defer", k)
}(i)
i++
fmt.Println("再+1後的i:", i)
defer func(k int) {
//k--
fmt.Println("第三個defer", k)
}(i)
i++
fmt.Println("再再+1後的i:", i)
i = i + 666
fmt.Println("+666後的i爲:", i)
}
輸出爲:
+1後的i: 1
再+1後的i: 2
再再+1後的i: 3
+666後的i爲: 669
第三個defer 2
第二個defer 1
第一個defer 0
如果取消三處k--
的註釋, 輸出爲:
+1後的i: 1
再+1後的i: 2
再再+1後的i: 3
+666後的i爲: 669
第三個defer 1
第二個defer 0
第一個defer -1
等同於:
package main
import "fmt"
func main() {
foo3()
}
func foo3() {
i := 0
defer f1(i)
i++
fmt.Println("+1後的i:", i)
defer f2(i)
i++
fmt.Println("再+1後的i:", i)
defer f3(i)
i++
fmt.Println("再再+1後的i:", i)
i = i + 666
fmt.Println("+666後的i爲:", i)
}
func f1(k int) {
k--
fmt.Println("第一個defer", k)
}
func f2(k int) {
k--
fmt.Println("第二個defer", k)
}
func f3(k int) {
k--
fmt.Println("第三個defer", k)
}
defer 指定的函數的參數在 defer 時確定,更深層次的原因是 Go 語言都是值傳遞。
情形 5! (傳遞指針參數!---
傳遞參數給 defer 後面的函數, defer 內外同時操作該參數)
package main
import "fmt"
func main() {
foo5()
}
func foo5() {
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)
i = i + 666
fmt.Println("+666後的i爲:", i)
}
輸出爲:
+1後的i: 1
再+1後的i: 2
再再+1後的i: 3
+666後的i爲: 669
第三個defer 669
第二個defer 669
第一個defer 669
作如下修改:
package main
import "fmt"
func main() {
foo5()
}
func foo5() {
i := 0
defer func(k *int) {
(*k)--
fmt.Println("第一個defer", *k)
}(&i)
i++
fmt.Println("+1後的i:", i)
defer func(k *int) {
(*k)--
fmt.Println("第二個defer", *k)
}(&i)
i++
fmt.Println("再+1後的i:", i)
defer func(k *int) {
(*k)--
fmt.Println("第三個defer", *k)
}(&i)
i++
fmt.Println("再再+1後的i:", i)
i = i + 666
fmt.Println("+666後的i爲:", i)
}
輸出爲:
+1後的i: 1
再+1後的i: 2
再再+1後的i: 3
+666後的i爲: 669
第三個defer 668
第二個defer 667
第一個defer 666
總結一下
即
-
如果傳參進 defer 後面的函數 (無論是閉包
func(){}(i)
方式還是子方法f(i)
方式, 或是直接跟如 fmt.Println(i)),defer 回溯時均以當時傳參時 i 的值去計算 -
反之, defer 回溯時, 以最後 i 的值帶入計算;(參考下面的例子).
參考:
Go 面試題答案與解析 [1]
幾種寫法之間的歸類與區別
package main
import "fmt"
func main() {
rs := foo6()
fmt.Println("in main func:", rs)
}
func foo6() int {
i := 0
defer fmt.Println("in defer :", i)
//defer func() {
// 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
如果改爲:
package main
import "fmt"
func main() {
rs := foo6()
fmt.Println("in main func:", rs)
}
func foo6() int {
i := 0
//defer fmt.Println("in defer :", i)
defer func() {
fmt.Println("in defer :", i)
}()
i = 1000
fmt.Println("in foo:", i)
return i+24
}
輸出爲:
in foo: 1000
in defer : 1000
in main func: 1024
也可見,
defer fmt.Println("in defer :", i)
相當於
defer func(k int) {
fmt.Println(k)
}(i)
或
func f(k int){
fmt.Println(k)
}
這時的參數, 都是傳遞時的值
而如
defer func() {
fmt.Println("in defer :", i)
}()
這時的參數, 爲最後 return 之前那一刻的值
defer 會影響返回值嗎?
函數的 return value 不是原子操作, 在編譯器中實際會被分解爲兩部分:返回值賦值
和 return
。而 defer 剛好被插入到末尾的 return 前執行 (即 defer 介於二者之間)。故可以在 defer 函數中修改返回值
package main
import (
"fmt"
)
func main() {
fmt.Println(doubleScore(0)) //0
fmt.Println(doubleScore(20.0)) //40
fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (rs float32) {
defer func() {
if rs < 1 || rs >= 100 {
//將影響返回值
rs = source
}
}()
rs = source * 2
return
//或者
//return source * 2
}
輸出爲:
0
40
50
再如:
func main() {
fmt.Println("foo return :", foo2())
}
func foo() map[string]string {
m := map[string]string{}
defer func() {
m["a"] = "b"
}()
return m
}
輸出爲:
foo return : map[a:b]
又如:
package main
import "fmt"
func main() {
fmt.Println("foo return :", foo())
}
func foo() int {
i := 0
defer func() {
i = 10086
}()
return i + 5
}
輸出爲:
foo return : 5
若作如下修改:
func foo() (i int) {
i = 0
defer func() {
i = 10086
}()
return i + 5
}
則返回爲:
foo return : 10086
return 之後的語句先執行,defer 後的語句後執行
將return value
拆解爲兩步: 確定 value 值, 然後 return.. 即如果 return 後面是個方法或者複雜表達式, 且有某個值 i, 會先計算. 完成後 defer 再執行, 如果 defer 裏面也有對 i 的改動, 是可以影響返回值的
(給函數返回值申明變量名, 這時, 變量的內存空間空間是在函數執行前就開闢出來的,且該變量的作用域爲整個函數,return 時只是返回這個變量的內存空間的內容,因此 defer 能夠改變返回值)
defer 不影響返回值,除非是 map、slice 和 chan 這三種引用類型,或者返回值定義了變量名
參考:
Golang 研學:如何掌握並用好 defer[2]-- 存疑 ("引用傳遞" 那裏明顯錯誤)
參考資料
[1]
Go 面試題答案與解析: https://yushuangqi.com/blog/2017/golang-mian-shi-ti-da-an-yujie-xi.html
[2]
Golang 研學:如何掌握並用好 defer: https://segmentfault.com/a/1190000019063371#comment-area
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/yxYMTsvMAeeLcAoJVWn4fA