学习 defer 时很懵逼,这道不会做!

疑问

他的疑问是下面这道 Go 语言的 defer 题目,大家一起看看:

  1. func main() {
  2. var whatever [6]struct{}
  3. for i := range whatever {
  4. defer func() {
  5. fmt.Println(i)
  6. }()
  7. }
  8. }

请自己先想一下输出的结果答案是什么。

这位小伙伴按自己的理解后,认为应当输出 xx。但最终的输出结果,可能与其思考的有所偏差,一时想不通。

解惑

这段程序的输出结果是:

  1. 5
  2. 5
  3. 5
  4. 5
  5. 5
  6. 5

为什么全是 5,为什么不是 0, 1, 2, 3, 4, 5 这样的输出结果呢?

其根本原因是闭包所导致的,有两点原因:

  • for 循环结束后,局部变量 i 的值已经是 5 了,并且 defer的闭包是直接引用变量的 i。
  • 结合defer 关键字的特性,可得知会在 main 方法主体结束后再执行。

结合上述,最终输出的结果是已经自增完毕的 5。

进一步思考

既然了解了为什么,我们再变形一下。再看看另外一种情况,代码如下:

  1. func main() {
  2. var whatever [6]struct{}
  3. for i := range whatever {
  4. defer func(i int) {
  5. fmt.Println(i)
  6. }(i)
  7. }
  8. }

与第一个案例不同,我们这回把变量 i 传了进去。那么他的输出结果是什么呢?

这段程序的输出结果是:

  1. 5
  2. 4
  3. 3
  4. 2
  5. 1
  6. 0

为什么是 5, 4, 3, 2, 1, 0 呢,为什么不是 0, 1, 2, 3, 4, 5?(难道煎鱼敲错了吗?)

其根本原因在于两点:

  • for 循环时,局部变量 i 已经传入进 defer func 中 ,属于值传递。其值在 defer语句声明时的时候就已经确定下来了。
  • 结合 defer 关键字的特性,是按先进后出的顺序来执行的。

结合上述,最终输出的结果是 5, 4, 3, 2, 1, 0。

下一个疑问

没过一会,这位小伙伴又有了新的感悟。抛出了新的示例问题,如下:

  1. func f1() (r int) {
  2. defer func() {
  3. r++
  4. }()
  5. return 0
  6. }
  7. func f2() (r int) {
  8. t := 5
  9. defer func() {
  10. t = t + 5
  11. }()
  12. return t
  13. }
  14. func f3() (r int) {
  15. defer func(r int) {
  16. r = r + 5
  17. }(r)
  18. return 1
  19. }

主函数:

  1. func main() {
  2. println(f1())
  3. println(f2())
  4. println(f3())
  5. }

请自己先想一下输出的结果答案是什么。

这段程序的输出结果是:

  1. 1
  2. 5
  3. 1

为什么是 1, 5, 1 呢,而不是 0, 10, 5,又或是其他答案?

欢迎大家在下方评论区留言讨论和分享解题的思路,一起思考和进步。