面試題之golang中的defer
一、題目
趨勢科技一面golang軟開實習
面試官:說一下你對defer的理解和使用注意事項
二、defer示例
1、defer執行順序
多個defer出現,前後執行呈棧的關係,先進後出,程式流程中前面的defer比後面的defer呼叫的晚。另外defer後邊只能跟函式。
package main import "fmt" func main() { defer func1() defer func2() defer func3() } func func1() { fmt.Println("A") } func func2() { fmt.Println("B") } func func3() { fmt.Println("C") }
執行結果
C
B
A
2、defer與return
return之後的語句先執行,defer後的語句後執行
package main import "fmt" func deferFunc() { fmt.Println("defer func called") } func returnFunc() { fmt.Println("return func called") } func returnAndDefer() int { // 後執行 defer deferFunc() // 先執行 return returnFunc() } func main() { returnAndDefer() }
執行結果
return func called
defer func called
3、defer與無命名返回值函式
如果函式的返回值是無名的(不帶命名返回值),則go語言會在執行return的時候會執行一個類似建立一個臨時變數作為儲存return值的動作。
package main import "fmt" //無命名返回值 func test() int { var i int defer func() { i++ //作為閉包引用的話,則會在defer函式執行時根據整個上下文確定當前的值。i=2 fmt.Println("defer1", i) }() defer func() { i++ //作為閉包引用的話,則會在defer函式執行時根據整個上下文確定當前的值。i=1 fmt.Println("defer2", i) }() // 先執行return i, 把i的值給到一個臨時變數,作為函式返回值 return i } func main() { // defer 和 return之間的順序是先返回值, i=0,後defer fmt.Println("test: ", test()) }
執行結果
defer2 1
defer1 2
test: 0
執行順序為return語句->defer2->defer1->返回值。defer2先於defer1執行
因此執行邏輯可以看做:
return先執行,負責把結果寫入返回值中,接著多個defer按照先進後出的順序開始呼叫執行一些收尾工作,最後函式攜帶這個返回值退出。
一般認為函式中執行到return,就直接函式生命週期結束,return的返回值就是函式返回值。但是由於defer語句的存在,return執行可以看做分為了兩個步驟:
- 賦值:由於返回值沒有命名,所以預設指定了一個臨時變數,比如tmp:=i
- 返回:真正函式返回的是tmp,而後續defer語句對i的修改,不會影響到tmp
4、defer與命名返回值函式
命名返回值的函式,由於返回值在函式定義的時候已經將該變數進行定義,在執行return的時候會先執行返回值儲存操作,而後續的defer函式會改變這個返回值,雖然defer是在return之後執行的,但是由於使用的函式定義的這個變數,所以執行defer操作後對該變數的修改,進而最終會影響到函式返回值。
package main
import "fmt"
func test() (i int) { //返回值命名i
defer func() {
i++
fmt.Println("defer1", i)
}()
defer func() {
i++
fmt.Println("defer2", i)
}()
return i
}
func main() {
fmt.Println("test:", test())
}
執行結果:
defer2 1
defer1 2
test: 2
defer1,defer2的時候都可以修改變數i
5、defer與panic
正常情況下,defer遇到return或者函式執行流程到達函式體末尾會將進入棧的defer出棧並以此執行,同樣遇到panic語句也是。
遇到panic的時候,會遍歷並將已經進棧的defer出棧並執行,但是對於程式流程中panic之後的defer就不會進棧。在defer出棧執行的過程中,遇到recover則停止panic,如果沒有recover捕獲panic,則執行完所以defer之後,丟擲panic資訊。
package main
import (
"fmt"
)
func test() {
defer func() { fmt.Println("defer: panic 之前0, 不捕獲") }()
defer func() {
fmt.Println("defer: panic 之前1, 捕獲異常")
// 捕獲異常資訊
if err := recover(); err != nil {
// 輸出panic中的錯誤資訊
fmt.Println(err.(string))
}
}()
// 正常進棧
defer func() { fmt.Println("defer: panic 之前2, 不捕獲") }()
//觸發defer出棧
panic("觸發異常")
// 由於在panic之後,不會在執行
defer func() {
fmt.Println("defer: panic 之後, 永遠執行不到")
}()
}
func main() {
test()
// 由於存在recover捕獲panic,main函式流程則正常執行
fmt.Println("main 正常結束")
}
執行結果
defer: panic 之前2, 不捕獲
defer: panic 之前1, 捕獲異常
觸發異常
defer: panic 之前0, 不捕獲
main 正常結束
牛客網相關題目:體溫異常