defer面试题,你能答对几题?

35 阅读2分钟

以下三道题都是defer关键字相关知识,首先在不运行代码的情况下给出返回值。

原文链接

题目

题目1

func f1() (result int) {
    defer func() {
        result++
    }()
    return 0
}

题目2

func f2() (r int) {
     t := 5
     defer func() {
       t = t + 5
     }()
     return t
}

题目3

func f3() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

答案

第一题:输出结果为1

第二题:输出结果为5

第三题:输出结果为1

看一下你答对了几题,是不是和你心中所想不一样?

接下来详细去分析在go语言中defer关键字和return关键字的执行顺序以及上面的答案为什么是这样。

defer

defer是go语言里面提供的关键字,用于声明一个延迟函数,一般用于资源的释放,例如文件资源和网络连接等,标记了defer的语句一般在return语句之前执行,如果有多个defer语句,则遵循栈的调用规则,越后面的语句越先执行。

代码示例:

f,err := os.Open(filename)
if err != nil {
		log.Println("open file error: ", err)
}
defer f.Close()

上面简单概括了defer的执行顺序和机制,但为什么前面三道题的答案和我们想的都不一样呢?

defer在return之前执行这个肯定的,在官方文档中也有说到,那么可能存在问题的就是return语句,这也是最重要的一点,return语句不是原子操作!

这是什么意思呢?就是说return语句实际上是分为两步完成的,第一步给返回值赋值,第二步返回值。那么defer语句就可能在赋值和返回值之间修改返回值,使最终的函数返回值与你想象的不一致。

可以把return拆分来写,就会更加清楚的明白这个原理。

func f1() (result int) {
	return = 0
	defer ...
	return result
}

答案解析

我们可以把三个题目的代码分别拆开来写,就能够明白为什么。

题目1

func f1() (result int) {
     result = 0    // 先给result赋值
     func() {      // defer插入到赋值和返回之间,修改了result的值
         result++
     }()
     return        // 最后返回的就是被修改了的值
}

题目2

func f2() (result int) {
     tmp := 5
     result = tmp    // 赋值
     func() {        // defer被插入到赋值与返回之间执行,但是并没有改动到result
         tmp = tmp + 5
     }
     return          // 最后返回的return就是5
}

题目3

func f3() (result int) {
     result = 1                // 给返回值赋值
     func(result int) {        // 这里改的result是传值传进去的result,不会改变前面赋值的result
          result = result + 5
     }(result)
     return                    // 最后return的是1
}

结论

defer确实是在return前面调用的,但是由于return并不是一个原子操作,defer语句的执行是在return的赋值和返回之间,所以如果defer语句涉及到了修改返回值,那么就会改变最后return的值。