1. 程式人生 > 其它 >面試題之golang中的defer

面試題之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 正常結束

牛客網相關題目:體溫異常