以下三道题都是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的值。