1、什么是闭包
闭包=函数+引用环境。当函数a()的内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包。 闭包的作用就是在a()执行完并返回b()后,闭包使得垃圾回收机制GC不会收回a()所占用的资源,因为a()的内部函数b()的执行需要依赖a()中的变量i。
2、闭包例子
2.1 简单例子
试着想一想以下程序执行会输出什么
func a() func() int {
i := 0
b := func() int {
i++
return i
}
return b
}
func main() {
var f = a()
fmt.Println(f())
fmt.Println(f())
}
答案是1、2
在main中 执行var f = a()
函数a返回函数b,f引用了函数b的实例。执行fmt.Println(f())
,执行函数b,而函数b内部依赖函数a的变量i,导致了GC不会回收函数a所占用的资源,变量i不在函数a的栈上,而是逃逸到堆上,所以在连续执行fmt.Println(f())
,操作的都是同一个变量i
ps:使用命令go build -gcflags "-N -l -m" example/closure
可以查看内存分配
这个呢?
func a() func() int {
i := 0
b := func() int {
i++
return i
}
return b
}
func main() {
fmt.Println(a()())
fmt.Println(a()())
}
答案是1、1
在main中执行a()()返回的函数直接调用,因此两次a()返回的不是同一个实例(即不是同一个闭包),不操作同一个i
2.2 循环闭包
以下程序会输出什么
func main() {
var f func()
for i := 0; i < 3; i++ {
f = func() {
fmt.Println(i)
}
}
f()
}
本以为会输出2,但答案是3
匿名函数执行依赖for中的局部变量i,外界变量f引用匿名函数,因此形成了闭包,i逃逸到堆上,循环结束后i=3
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for j := 0; j < 3; j++ {
funcs[j]()
}
}
输出3、3、3
原因跟上一个例子一样
那怎么解决呢?
声明新变量,并操作新变量
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
n := i
funcs = append(funcs, func() {
fmt.Println(n)
})
}
for j := 0; j < 3; j++ {
funcs[j]()
}
}
创建新匿名函数并传入i
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
func(i int) {
funcs = append(funcs, func() {
fmt.Println(i)
})
}(i)
}
for j := 0; j < 3; j++ {
funcs[j]()
}
}
3、总结
- 闭包=函数+相关引用环境
- 闭包会导致闭包中使用的函数中的变量逃逸到堆上
- 解决循环闭包的两种方法