面试题

这是Go Quiz系列中关于defer的第3篇,这道题目来源于真实的互联网项目里,也是Go初学者容易犯的错误之一。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

func test1() int {
	var i = 0
	defer func() {
		i = 10
	}()
	return i
}

func test2() (result int) {
	defer func() {
		result *= 10
	}()
	return 6
}

func test3() (result int) {
	result = 8
	defer func() {
		result *= 10
	}()
	return
}

func main() {
	result1 := test1()
	result2 := test2()
	result3 := test3()
	fmt.Println(result1, result2, result3)
}
  • A: 0 6 8
  • B: 0 60 80
  • C: 10 6 80
  • D: 10 60 8
  • E: 编译报错

这道题主要考察以下知识点:

  • 在被defer的函数里对返回值做修改在什么情况下会生效?

解析

我们来看下官方文档里的规定:

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.

重点是这句:

That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller.

Go的规定是:如果在函数A里执行了 defer B(xx),函数A显示地通过return语句来返回时,会先把返回值赋值给A的返回值参数,然后执行被defer的函数B,最后才真正地返回给函数A的调用者。

对于test1函数,执行return i时,先把i的值赋值给test1的返回值,defer语句里对i的赋值并不会改变函数test1的返回值,test1函数返回0。

对于test2函数,执行return i时,先把i的值赋值给test2的命名返回值result,defer语句里对result的修改会改变函数test2的返回值,test2函数返回60。

对于test3函数,虽然return后面没有具体的值,但是编译器不会报错,执行return时,先执行被defer的函数,在被defer的函数里对result做了修改,result的结果变为80,最后test3函数return返回的时候返回80。

所以答案是B。

所以想要对return返回的值做修改,必须使用命名返回值(Named Return Value)

加餐

可以回顾Go quiz系列中关于defer的另外2道题目,加深对defer的理解。

题目1:Go Quiz: 从Go面试题看defer语义的底层原理和注意事项

题目2:Go Quiz: 从Go面试题看defer的注意事项第2篇

开源地址

文章和示例代码开源地址在GitHub: https://github.com/jincheng9/go-tutorial

公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。

个人网站:https://jincheng9.github.io/

知乎:https://www.zhihu.com/people/thucuhkwuji

References