Go中defer细节,避免掉坑
我们先看下面这段代码, 通过代码去解读:
// func 1
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
// func 2
func increaseB() (r int) {
defer func() {
r++
}()
return r
}
大部分人在没有运行测试的情况下完全答对的比较少,有部分人可能知道答案但是不明白其中的原因.所以有必须总结下.
什么是defer
defer
是Go语言提供的一种用于注册延迟调用的机制, 每一次defer
都会把函数压入栈中, 当前函数返回前再把延迟函数取出并执行.
defer
语句并不会马上执行, 而是会进入一个栈, 函数return
前, 会按先进后出(FILO
)的顺序执行. 也就是说最先被定义的defer
语句最后执行.先进后出的原因是后面定义的函数会依赖前面的资源, 自然要先执行;否则,如果前面先执行,那后面的函数的依赖就没有了.
采坑点
使用defer
最容易采坑的地方是和带命名返回参数的函数一起使用时.
defer
语句定义时,对外部变量的引用是有两种方式的, 分别是作为函数参数和作为闭包引用.作为函数参数,则在defer
定义时就把值传递给defer
,并缓存起来;作为闭包引用的话,则会在defer
函数真正调用时根据整个上下文确定当前的值.
要弄明白关键是要理解这条语句:
return xxx
这条语句并不是一个原子指令,经过编译后,变成了三条指令:
1. 返回值 = xxx
2. 调用 defer 函数
3. 空的return
1,3
步才是return
语句真正的命令,第二步是defer
定义的语句, 这里就有可能会操作返回值.
我们来做几个题目:
// 1
func f1() (r int) {
defer func() {
r++
}()
return 0
}
// 2
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
// 3
func f3() (r int) {
defer func(r int) {
r = r + 5
}()
return 1
}
我们试着用上面的拆解方式,给出自己的答案:
第一题拆解过程:
func f1() (r int) {
// 1. 赋值
r = 0
// 2.闭包引用, 返回值被修改
defer func() {
r++
}()
// 3.空的return
return
}
defer
是闭包引用,返回值被修改,所以f1()返回1
第二题拆解过程:
func f2() (r int) {
t := 5
// 1. 赋值
r = t
// 2.闭包引用,但是没有修改返回值`r`
defer func() {
t = t + 5
}()
// 3.空的return
return
}
第二步没有涉及返回值r
的操作,所以返回5
第三题拆解过程:
func f3() (r int) {
// 1. 赋值
r = 1
// 2. r作为函数参数, 不会修改要返回的那个 r 值,defer里面的r是个副本
defer func(r int) {
r = r + 5
}(r)
// 3.空的return
return
}
第二步,r
函数的参数使用,是一份复制,defer
语句里面的r
和外面的r
其实是两个变量,里面变量的改变不会影响外层变量r
,所以不是返回6
,而是返回1
咱们现在回到开头的第二个例子中,increaseB()
函数返回1
, 分解如下:
func increaseB() (r int) {
// 1. 赋值
r = 0
// 2.闭包引用, r++
defer func() {
r++
}()
// 3.空return
return
}
那第一个例子怎么解呢?,我把代码再贴一遍:
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
大家可能会注意到, 函数increaseA()
是匿名返回值, 返回局部变量,同时defer
函数也会操作这个局部变量.对于匿名返回值来说, 可以假定有一个变量存储返回值,比如假定返回值变量为anony
,上面的返回语句可以拆分成以下过程:
anony = i
i++
return
由于i
是整型, 会将值拷贝给anony
,所有defer
语句中修改i
值,对函数返回值不造成影响,所以返回0
.
看完这篇文章,大家应该对defer
有了更深入的了解了吧.
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
