背景
defer
在golang中属于关键词,主要用于资源释放,会在函数返回之前被调用,但其中也包含了很多的坑。下面我们通过几个常见例子来进行相关的讲解。
例子
// e.g.1
func f1() (r int) {
defer func() {
r = r + 5
}()
return 1
}
// e.g.2
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
// e.g.3
func f3() (r int) {
t := 5
defer func(r int) {
r = r + 5
}(r)
return t
}
// e.g.4
func f4() {
t := 5
defer fmt.Println("t1 =", t)
t = 4
defer fmt.Println("t2 =", t)
}
我们先不直接说出其中运行的结果,我们都简单的针对上面的例子在心中计算一下,并想想是为什么,下面我在写出其中的结果.
原理讲解
以下为我们执行的实际结果
func main() {
fmt.Println("f1() =", f1())
fmt.Println("f2() =", f2())
fmt.Println("f3() =", f3())
f4()
// Outputs:
// f1() = 6
// f2() = 5
// f3() = 5
// t2 = 4
// t1 = 5
return
}
上面的结果和你心中想到的是否一致?哈哈,如果一致的话相信你是懂defer的运行机制的,可以绕过本文了。有不懂或没弄对的地方的话,就继续往下看,我来一一给你讲解。
defer规则
我们来直接说一下defer的三个简单规则,详见官方文档
- 规则1-延迟函数的参数在defer语句出现时就已经确定下来了
- 规则2-延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
- 规则3-延迟函数可能操作主函数的具名返回值
针对规则1,意思是defer后面的函数一在定义完以后,如果函数有传入值,则在那一刻就把值给固定下来了,这个规则能很好的解释上面提到的f4(),打印的t1=5,t2=4
针对规则2,意思是说defer采用的是FILO(先进后出)的规则,用这个规则也能解释f4()里面的打印顺序问题,先打印t2再打印t1
针对规则3,意思是主函数可能有返回值,而这个defer后面的函数可能会影响到主函数的返回值,这里我们就使用规则3来对上面的另外三个例子进行一一说明,f1()里面return 1,哦,我再顺便说一个利于你理解记忆的小诀窍,在return的时候首先赋值,然后执行defer,最后返回,也就是先r=1,然后执行defer里面r = r + 5,最后才是返回的r,打印f1() = 6,解释通了吧?哈哈;f2()里面带有一点迷惑性,但是我们直接利用上面的规则拆分,return的时候是先执行的r = t,也就是r=5,而defer执行的操作只是对t操作,和r没有半毛钱的关系,这样打印f2() = 5也就能解释了;f3()里面同上面规则也一样,先赋值,但是有一点是defer里面函数通过值传递,在值传递的时候已经是进行了值的拷贝操作,并没有把返回的值给更改掉,或者换个思路,把f3()里面的defer函数r换成其他变量,也许一眼就看懂了;
小结
- defer定义的延迟函数参数在defer语句出时就已经确定下来了
- defer定义顺序与实际执行顺序相反
- return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转申请资源后立即使用defer关闭资源是好习惯
文章评论