前言
用golang也用了挺长时间了,我对 defer
这个设计还比较满意,而且对其使用也产生了一些依赖,在使用其他编程语言写的入迷的时候,经常写出来 unknow type defer
的操作。
项目中,我用defer,一般是在 io 的时候使用,开启文件后,紧跟一个关闭,现在已经成为我的一种习惯了。
最近面试的时候,被问到了defer一些不常用的用法,虽然我都答出来了,但是答的过程有点曲折。我能把defer的大白话说出来,但是还是缺少总结语言的能力。
所以今天我想把 defer 用法的大白话,用代码让自己记住。
正文
defer 与 return 的先后顺序
先return,后执行defer,
defer 修改 return 的返回值
defer 可以修改有变量名的return值
- “有变量名的return值”,指的是在声明函数时,所声明的返回值。
为什么是有变量值的return?
调用函数时,程序会为函数创建一块固定的区域,以存放 返回值,假设这块区域为 *p
,
函数return后,调用者会读取栈中的 *p
这一块内存区域。
使用 func f() (i int)
的方式声明函数,相当于为 *p
这片区域声明了一个变量名称 i
,在defer中修改i的值,就相当于修改这片内存区域的值,所以 defer 可以修改函数的返回值。
若使用 func f() int
的方式声明函数,存放返回值的这片内存区域 *p
没有任何变量名称,最后的 return b
操作,只是将b的值复制给 *p
这片区域,return后,若在defer中修改b的值,也跟函数返回值 *p
没有任何关系了,所以 defer 无法修改没有声明变量名称的 返回值。
func f() (i int) {
i = 100
defer func() {
i += 1
}()
return
}
func main() {
fmt.Println(f()) // result 101
}
defer 链式调用
defer 遇到链式调用时,会先通过计算得到最后一个要执行的方法(函数),保留这个方法(函数)的指针、参数(值复制)。
type T struct{}
func (t T) f(n int) T {
fmt.Println(n)
return t
}
func main() {
var t T
defer t.f(1).f(2)
fmt.Println(3)
}
// 打印结果
// 1 f(1)不是最后一个要执行的方法,所以先被defer计算了
// 3
// 2 f(2)被压入defer栈中,main()函数执行完毕以后才被执行
上述的 main() 函数,等同于:
var t T
tempT := t.f(1) //f(1)不是最后一个要执行的方法,所以先被defer计算了
defer tempT.f(2)
fmt.Print(3)
defer 参数值复制
defer 有参数的函数在压入 defer 栈时,会一并将此时参数的值,复制一份副本。defer函数执行时,函数实际的入参为当时保存的副本。
func f(n int) {
defer fmt.Println(n)
n += 100
}
func main() {
f(1)
}
// 打印结果: 1
defer 匿名函数
匿名函数可以直接使用匿名函数外的变量,匿名函数中的 i ,与外部的 i 从内存地址的视角来看,是同一个 i。
func main() {
i := 1
defer func() {
fmt.PrintLn(i)
}()
i += 100
}
// 打印结果:101
文章评论