文章目录
defer规则
defer语句用于延迟函数的调用,每次defer都会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。 为了方便描述,我们把创建defer的函数称为主函数,defer语句后面的函数称为延迟函数。
Golang官方博客里总结了defer的行为规则,只有三条,我们围绕这三条进行说明。
- 规则一:延迟函数的参数在defer语句出现时已经确定了
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
//输出结果为0
defer语句中的fmt.Println(i)中的参数i值在defer出现之前就已经确定了,本质上是将i的值就行值拷贝,后面对变量i的修改不会影响到fmt.Println()的执行,最终打印还是“0”
- 规则二:延迟函数按照先进后出的方式入栈
这个很好理解,定义defer类似于入栈操作,执行defer类似于出栈操作。 - 规则三:延迟函数可能操作主函数的具名返回值
定义defer的函数,即主函数可能有返回值,返回值有没有名字没有关系,defer所作用的函数,即延迟函数可能会 影响到返回值。
函数返回过程
:关键字return并不是一个原子操作,实际上return只是代理了汇编指令ret,即将跳转程序执行。
比如语句 return i
,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正 是跳转前,所以说defer执行时还是有机会操作返回值的。
例如下面的demo
func deferFuncReturn() (result int) {
i := 1
defer func() {
result++
}()
return i
}
该函数的return语句可以拆分为下面两行
result = i
return
而延迟函数的执行正是在return之前,所以加上defer之后的执行可以拆分为3行:
result = i
result ++
return
所以上面函数返回2,只要理解了return的返回机制,那么所有关于延迟函数的问题就会变得简单了。
3.1主函数拥有匿名返回值,返回字面值
一个主函数拥有一个匿名的返回值,返回时使用字面值,比如返回”1”、”2”、”Hello”这样的值,这种情况下defer 语句是无法操作返回值的。
一个返回字面值的函数,如下所示:
func foo() int {
var i int
defer func() {
i++
}()
return 1
}
上面的return语句,直接把1写入栈中作为返回值,延迟函数无法操作该返回值,所以就无法影响返回值。
3.2主函数拥有匿名返回值,返回变量
一个主函数拥有一个匿名的返回值,返回使用本地或全局变量,这种情况下defer语句可以引用到返回值,但不会改变返回值。
一个返回字面值的函数,如下所示:
func foo() int {
var i int
defer func() {
i++
}()
return i
}
上面的函数,返回一个局部变量,同时defer函数也会操作这个局部变量。对于匿名返回值来说,可以假定仍然有一 个变量存储返回值,假定返回值变量为”anony”,上面的返回语句可以拆分成以下过程:
anony = i
i++
return
由于i是整型,会将值拷贝给anony,所以defer语句中修改i值,对函数返回值不造成影响,返回的结果为0
3.3主函数拥有具名返回值
主函声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如 果defer语句操作该返回值,可能会改变返回结果。
一个影响函返回值的例子:
func foo3() (ret int) {
defer func() {
ret++
}()
return 0
}
上面函数拆解出3部:
ret = 0
ret ++
return
defer的实现原理
请参考:Go语言的设计与实现