引入
先来看俩程序
package main
import "fmt"
func main() {
var whatever [5]struct{}
for i := range whatever {
defer fmt.Println(i)
}
}
// 结果:4 3 2 1 0
// 闭包状态下
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与闭包的关系
defer介绍
defer特性
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 前才被执。因此,可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行。
- defer语句中的变量,在defer声明时就决定了。
defer用途
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
defer执行顺序
定义defer的时,会先将defer后面的调用的函数的参数入栈,在return右侧的参数或函数值计算完成后调用defer,此时defer的函数参数出栈执行,defer执行完成后执行return
例子
例题一
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
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
例题二
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
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
参考文章: