你了解defer吗?

你了解defer吗?

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func f(n int) (r int) {
defer func() {
r += n
recover()
}()

var f func()

defer f()
f = func() {
r += 2
}
return n + 1
}

func main() {
fmt.Println(f(3))
}

问题:上面的代码会输出什么?为什么?

首先我们先来了解一下go语言中的defer:

​ defer顾名思义,延迟。它是go语言中的一个关键字,主要用在函数或方法前面,作用是用于函数和方法的延迟调用,在语法上,defer与普通的函数调用没有什么区别。

​ 在使用上非常简单,只需要弄清楚以下几点即可:

  1. 延迟的函数的什么时候被调用?
    1. 函数return的时候
    2. 发生panic的时候
  2. 延迟调用的语法规则
    1. defer关键字后面表达式必须是函数或者方法调用
    2. 延迟内容不能被括号括起来
  3. 当一个函数中有多个defer时,他们的执行顺序是先进后出
  4. 在函数执行return的过程可以分为三步:
    1. 设置返回值
    2. 执行defer语句
    3. 将结果返回
  5. defer 定义的延迟函数的参数在 defer 语句出时就已经确定下来了

​ 知道了这些,上面的题目久很好理解了。

​ 正确答案是 7

​ 当然,很有可能你的答案和正确答案一样,但分析不一定正确,所以接着往下看。

​ 这里只对函数f进行讲解:

  1. 首先使用defer关键字注册了一个匿名函数,然后这个匿名函数在函数f返回时执行。在这个匿名函数里,使用了recover(),这意味着它可以恢复panic。
  2. 接着定义了一个变量f,类型为func(),这里由于只声明了,但是没有定义,故变量f是一个nil函数。
  3. 然后使用defer关键字将f变量注册成延迟函数,这个延迟函数在函数f返回时会执行,但这个匿名函数是一个nil函数,因此在执行这个延迟函数时会触发panic
  4. 接下来是对变量f的定义
  5. return n + 1此时,返回值变量r = n + 1,接着执行defer注册的延迟函数,因为defer函数的执行顺序是先进后出的,故先执行变量f,但由于这里注册的是一个nil函数,因此触发panic,接着执行最开始注册的匿名函数,此时r = n + 1 + n,遇到了recover(),所以恢复了panic,将r的值返回
  6. 最后返回给主函数的值r = n + 1 + n = 7

你了解defer吗?
http://example.com/2023/04/18/Go每日一题/你了解defer吗?/
作者
Feng Tao
发布于
2023年4月18日
更新于
2023年4月21日
许可协议