Go defer中改变return的值会生效吗


直接上代码

func test() int {
   result = 123
   defer func() {
      result = 456
   }()
   return result
}

func main() {
   fmt.Println(test())
}

结果

123

修改之后的代码

func test() (result int) {
   result = 123
   defer func() {
      result = 456
   }()
   return result
}

func main() {
   fmt.Println(test())
}

结果

456

再看下面这个例子

func test() (result int) {
   result = 123
   defer func() {
      fmt.Println("aaa")
      result = 456
   }()
   return func() int {
      fmt.Println("bbb")
      return result
   }()
}

func main() {
   fmt.Println(test())
}

结果

bbb
aaa
456

defer与return哪个先执行

这个问题主要是defer 与return哪个先执行。很容易理解如果一个函数中有多个defer,它是栈的形式保存的,执行的时候先从栈顶执行,即后面定义的defer会先被执行,并且defer是在return执行之后才执行的。

因为defer是在return 执行之后才执行的,所以第三个例子中先打印bbb后打印aaa很好理解。

第二个例子和第三个例子test函数返回456也好理解,因为defer可以改变返回值中定义的变量。虽然return已经返回了,defer还是可以改变它。

第一个例子,defer改变的不是返回值中定义的变量,而是局部变量,这个时候return已经执行了,defer改变局部变量没有用。在defer中改变局部变量的值没有效果。第一个例子return result是值拷贝,即将result的值拷贝一份并返回,因此defer改变result并不会影响返回值。

第二、第三例子中return返回的result不是值拷贝,因为result是在返回值中定义的变量,所以return返回的直接是那个变量,这个时候没有值拷贝

再看看下面这个例子

func test() *int {
   result := 123
   defer func() {
      result = 456
   }()
   return &result
}

func main() {
   fmt.Println(*test())
}

结果

456

这个时候defer改变局部变量result又生效了,这是为什么?是因为return 返回的是局部变量的地址,而不是局部变量的只拷贝。因此在defer中修改局部遍历会影响返回结果。

总结

上面描述可能有点绕,需要亲自实验一下,仔细理解才能真正搞懂。下面总结一下我的理解:

return 返回有2种方式:

  • 值拷贝:将局部变量的值拷贝到返回值上。return 直接返回局部变量的值(不是局部变量的引用)
  • 非值拷贝:即return 返回值的时候没有发生值拷贝,有两种情况:
    • 将返回值中定义的变量返回。
    • 将局部变量的引用返回。

非值拷贝的情况下,defer修改返回值是生效的。

return 的执行其实用两步骤:1.先将return的结果赋值到返回值上;2.再将返回值赋值作为函数的结果赋值给调用者。

defer的执行是在return的两步骤中间执行的。所以return如果发生了值拷贝则defer不会改变返回结果;如果return没有发生值拷贝则defer会改变返回结果。

参考

[1]面试Go 被defer的几个盲区坑了


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录