Golang defer与闭包
tbghg

引入

先来看俩程序

1
2
3
4
5
6
7
8
9
10
package main
import "fmt"

func main() {
var whatever [5]struct{}
for i := range whatever {
defer fmt.Println(i)
}
}
// 结果:4 3 2 1 0
1
2
3
4
5
6
7
8
9
10
11
// 闭包状态下
package main
import "fmt"

func main() {
var whatever [5]struct{}
for i := range whatever {
defer func() { fmt.Println(i) }()
}
}
// 结果:4 4 4 4 4

这俩个程序区别在于一个直接调用输出,一个闭包输出,但结果却天差地别,下面深入了解下defer与闭包的关系

(PS:答案写在后记里了 :)

defer介绍

defer特性

  1. 关键字 defer 用于注册延迟调用。
  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
  3. 多个defer语句,按先进后出的方式执行。
  4. defer语句中的变量,在defer声明时就决定了。

defer用途

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

defer执行顺序

定义defer的时,会先将defer后面的调用的函数的参数入栈,在return右侧的参数或函数值计算完成调用defer,此时defer的函数参数出栈执行,defer执行完成后执行return

例子

出处

例题一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

func add(x, y int) (z int) {
defer func() {
println(z+9) // 输出: 212
}()

z = x + y
return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return)
}

func main() {
println(add1(1, 2)) // 输出: 203
}
// 结果:212 203
1
2
3
4
5
6
7
8
9
10
11
12
package main

func add2(x, y int) (z int) {
defer println(z + 9) // 输出: 9
z = x + y
return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return)
}

func main() {
println(add2(1, 2)) // 输出: 203
}
// 结果:9 203

例题二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

func main() {
fmt.Println(a())
}

func a() int {
var i int
defer add(i) //延迟调用add(),在程序计数器走到defer时,会把defer右边最外层函数的参数计算完毕,也就是此时的i是多少就是多少
i += 100
return i
}

func add(i int) {
i += 1
fmt.Println(i)
}
// 结果:1 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

func main() {
fmt.Println(b())
}

func b() int {
var i int
defer func() {
add(i) //可以理解为传的是i的指针,{}里面的函数体入栈,i此时不计算,最后是什么就是什么
}()
i += 100
return i //return 之前运行defer,此时的defer里面的i是100,调用add变为101
}

func add(i int) {
i += 1
fmt.Println(i)
}

在定义defer的时候,就要将defer后面的函数参数等入栈,等到return之前的时候出栈执行,a方法中是将i的拷贝直接入栈,b方法中通过一个闭包调用,实际上将i的指针传递给闭包,闭包读取值拷贝给add。

后记

回到一开始给出的两个程序,首先我们应该清楚for-range循环中i的地址是保持不变的,在非闭包的情况下,将值压入栈中进行保存,所以最后输出顺序为4 3 2 1 0,但在闭包条件下,右侧函数参数为空,i保存的为for中i的地址,由于i的值一直在改变,所以最后调用时打印出来的值均为最后的值4,即:4 4 4 4 4

参考文章:

 评论
评论插件加载失败
正在加载评论插件