Golang defer

在Golang使用defer常常会迷惑于以下两个问题

  • defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的;
  • defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合预期的结果;

defer的调用时机是在当前函数返回之前调用的,执行顺序是先入后出。

预计算参数

defer也是传参的。比如说以下代码就会输入0,而不是最终的1

n := 0
defer fmt.Println(n)
n = 1

其原因在于defer会立刻拷贝defer函数中引用的外部参数,也就是说运行到defer fmt.Println(n)时,n的值便已经在defer函数里面啦

即使defer的是个有参方法,也会拷贝到参数

func TestDeferPassParmFunc(t *testing.T) {
	n := 0
	defer deferPassParam(n)
	n = 1
}

func deferPassParam(r int) {
	fmt.Println(r)
}

// output:
// 0

如果想避免这种情况我们可以defer匿名函数或者指针参数函数

func TestDeferPassParmFunc(t *testing.T) {
	n := 0
	defer deferPassParam(&n)
	n = 1
}

func deferPassParam(r *int) {
	fmt.Println(*r)
}

// output:
// 1

常见defer现象

连续defer,先入后出

按照先入后出执行

defer fmt.Println("one")
defer fmt.Println("two")
defer fmt.Println("three")

// output:
// three
// two
// one
defer声明,变量确认

变量在defer声明时便已经确认,即使后面变量发生改变,defer实际执行时也与声明时保持一致。

n := 0
defer fmt.Println(n)
n = 1

// output:
// 0
defer闭包,参数可修改

对于有名返回值,被defer的函数是闭包,且该函数为命名返回值,那么defer是可以访问返回值,并修改

func namedReturn() (r int) {
	defer func() {
		r = 1
	}()
	return 2
}

// output
// 1

当然,如果被defer的函数不是闭包或匿名,但是只要传入有名返回值的指针,其实还是可以的

func namedReturnWithDeferNamedFunc() (r int) {
	defer namedFuncForDer(&r)
	return 2
}

func namedFuncForDer(r *int) {
	*r = 1
}

// output
// 1

除了命名返回值这种特殊参数外,其他普通参数其他也可访问修改

// 无参匿名
n := 1
defer func() {
  fmt.Println(n)
}()
n = n + 1
return n
// output:
// 2

// 有参匿名
n := 1
defer func(x int) {
  fmt.Println(n)
}(n)
n = n + 1
return n
// output:
// 2

总结

  • defer按照栈顺序,先定义的后执行
  • defer闭包会在defer执行时,获取或改变闭包内变量(但不改变非命名返回值)
  • defer匿名函数也会获取或者改变闭包内变量(但不改变非命名返回值)
  • defer普通函数,在非指针参数情况下,与defer声明时变量值一致

Ref

  1. https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值