Golang:“闭包(closure)”到底包了什么?
golang 闭包
Golang中闭包的理解
什么是闭包呢?摘用Wikipedia上的一句定义:
a closure is a record storing a function together with an environment.
闭包是由函数和与其相关的引用环境组合而成的实体 。
因此闭包的核心就是:函数和环境。其实这里就已经可以回答本文题目的问题:闭包究竟包了什么?答案是:函数和环境。但是相信部分看官们到这里依然不清楚:什么函数?什么环境?
函数,指的是在闭包实际实现的时候,往往通过调用一个外部函数返回其内部函数来实现的。内部函数可能是内部实名函数、匿名函数或者一段lambda表达式。用户得到一个闭包,也等同于得到了这个内部函数,每次执行这个闭包就等同于执行内部函数。
环境,Wikipedia上说是与其(函数)相关的引用环境,可以说解释地很精准了。具体地说,在实际中引用环境是指外部函数的环境,闭包保存/记录了它产生时的外部函数的所有环境。但是这段话对于尚未理解闭包的同学来说依旧是不友好的,听完还是懵懂的。这里尝试做个更实用性的解释:
- 如果外部函数的所有变量可见性都是local的,即生命周期在外部函数结束时也结束的,那么闭包的环境也是封闭的。
- 反之,那么闭包其实不再封闭,全局可见的变量的修改,也会对闭包内的这个变量造成影响。
ps: 应用上就是如果闭包引用的变量是指针类型,则外部改变此变量,闭包内同样能感知到变化。
【总结】:
- 闭包是环境和变量的结合体
- go中常使用匿名函数且匿名函数体内引用外部变量形式,生成闭包
- 闭包内的变量具有延迟绑定特点,在真正执行时才获取到值
- 使用go +匿名函数的形式启动新goutine,如果也使用了闭包,同样具有变量延迟绑定的特点
- 结合4和5,只要函数不是立即执行的,函数内引用的外部变量,在执行时都有可能和声明时不同,可以统一理解为延迟执行,defer语句也有这个特点。
以下是几个例子:
// foo1 内部隐藏变量*int类型变量,函数外的*int变化会影响函数内
func foo1(x *int) func() {
return func() {
*x = *x + 1
fmt.Printf("foo1 val = %d\n", *x)
}
}
// foo2 内部隐藏x变量,但由于go函数传参是值复制,调用foo2时x是一个值的拷贝,因此函数外实参的变化不会再影响生成的闭包函数
func foo2(x int) func() {
return func() {
x = x + 1
fmt.Printf("foo2 val = %d\n", x)
}
}
//foo3 直接打印range遍历的val,val变量的地址不变
func foo3() {
values := []int{1, 2, 3, 5}
for _, val := range values {
fmt.Printf("foo3 val=%d\n", val)
}
}
func show(v int) {
fmt.Printf("foo4 val=%d\n", v)
}
// foo4 使用goroutine将val作为参数,而将val作为参数时,传递的就是每次遍历的值,不会延迟绑定
func foo4() {
values := []int{1, 2, 3, 5}
for _, val := range values {
go show(val)
}
time.Sleep(3 * time.Second)
}
// foo5 使用goroutine但没有传参,而是用闭包的形式,这时候延迟绑定,val的值只有在计算时才获取
func foo5() {
values := []int{1, 2, 3, 5}
for _, val := range values {
go func() {
fmt.Printf("foo5 val=%d\n", val) /var变量的地址不变,延迟绑定最后打印的值相同都是5
}()
}
time.Sleep(3 * time.Second)
}
// foo6 同foo5相同
var foo6Chan = make(chan int, 10)
func foo6() {
for val := range foo6Chan {
//go func() {
// fmt.Printf("foo6 val=%d\n", val) // 和foo5的slice一样,val变量的地址不变,延迟绑定导致最后打印的值相同
//}()
fmt.Printf("foo6 val=%d\n", val)
}
time.Sleep(3 * time.Second)
}
func foo7(x int) []func() {
var fs []func()
values := []int{1, 2, 3, 5}
for _, val := range values {
fs = append(fs, func(){
// 这里匿名函数内部引用外部的变量x和val,x不变,但val在变化
fmt.Printf("foo7 val=%d\n", x+val)
})
}
return fs
}
func main() {
i := 0
f := foo1(&i)
f() // 1
f() // 2
i = 100
f() // 101
f() // 102
i2 := 0
f2 := foo2(i2)
f2() // 1
f2() // 2
i2 = 100
f2() // 3
f2() // 4
foo3() // 1 2 3 5
foo4() // 1 5 2 3
foo5() // 5 5 5 5
foo6chan <- 1
foo6chan <- 2
foo6chan <- 3
foo6chan <- 4
foo6chan <- 5
close(foo6chan)
foo6()
fs := foo7(1)
for _, f := range fs {
f()
}
}