你以为面试中的 defer 是在考 defer 吗?并不是,其实是在考 函数变量的作用域

以下这是 go语言爱好者 97 期的一道题目。要求很简单, 代码执行 i, j 的值分别是什么。

func Test_Demo(t *testing.T) {
i := 10
j := hello(&i)
fmt.Println(i, j)
}

func hello(i *int) int {
defer func() {
*i = 19
}()
return *i
}

这道题虽然代码少, 但是考点还是蛮多的

  1. 核心: 函数变量作用域

  2. defer 执行时间

  3. 闭包

  4. 指针

知识点

这里面所有的内容都可以在 Effective Go 中解决

贪婪算法

什么是贪婪算法, 就是找到局部最优解, 合并后就是全局最优解。

怎么找局部最优解, 就是要 对事情进行抽象,掌握事情的本质 。

defer 延迟执行

FILOreturn
openclose
defer

因此 defer 有什么好考的, 而且实际场景代码也不会那样写(违反了可读性的这一基本之准则)。

所以通常面试中有 defer 的问题都不是在考 defer , 只不过是披上了 defer 的狼皮。

函数及返回值

其实 go 中关于函数返回花样还是挺多的。

func NamedResult(i, j int) (x int)return

感觉和 golang 本身的代码可读性的的理念有一点冲突。就像为什么不支持三元运算符一样。其实这样本身也没有什么, 就是一两个 死记硬背 的知识点而已。

defer闭包指针

如果对 函数变量的作用域 理解不清楚的话, 就容易掉坑。

package main

// 命名结果
func NamedResult(i, j int) (x int) {
x = i + j
// 默认返回
return
}

// 匿名结果
func UnnamedResult(i, j int) int {
// 指定返回
return i + j
}

我们开启汇编, 查看一下函数过程

go tool compile -N -l -S  main.go

从汇编结果可以看到:

UnnamedResult~r2NamedResultxRET(SP)MOVQ    AX, "".~r2+24(SP)

既然如此, 我们就将所有函数的写法全部统一, 不再区分 命名的、 匿名的 , 默认的, 指定的

  1. 命名返回值

  2. return 指定结果

func ReformResult(i, j int) (r2 int) {
r2 = i + j
return r2
}

这样看起来, 整个函数就清晰的多了。

实战练习一下

根据之前所说, 我们这里来对函数做一下整形手术。

func Test_reformDemo(t *testing.T) {
i := 10
j := reformHello(&i)
fmt.Println(i, j) // 19 10
}

// hello 原函数
func hello(i *int) int {
defer func() {
*i = 19
}()
return *i
}

// reformHello 整形函数
// reform 1. 匿名变命名
func reformHello(i *int) (_x int) {
// reform 2. return 拆分
// reform 2.1 显式赋值
_x = *i // _x=10

// reform 3. defer 在返回前执行
func() { *i = 19 }() // *i=19

// reform 2.2 显式返回
return _x // _x=10
}