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

可見:

(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

總結一下

參考:

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]-- 存疑 ("引用傳遞" 那裏明顯錯誤)

Golang 中的 Defer 必掌握的 7 知識點

參考資料

[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