golang中的defer使用的注意事项

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语言的设计与实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值