各自努力,最高处见!加油!
go语言闭包
什么是闭包?
闭包的概念:是可以包含*(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于*变量包含在代码块中,所以这些*变量以及它们引用的对象没有被释放)为*变量提供绑定的计算环境(作用域)。
简单来说就是包含在代码块中的*变量和引用对象在作用域结束时并没有被释放,已经与被执行的代码块绑定到一起。 Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。
所以:闭包=函数+引用环境
认识闭包
package main
import (
"fmt"
)
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f1 := squares()
f2 := squares()
fmt.Println("first call f1:", f1())
fmt.Println("second call f1:", f1())
fmt.Println("first call f2:", f2())
fmt.Println("second call f2:", f2())
}
通过上面一段代码来认识一下闭包。
squares()函数的返回值是一个函数,返回的这个函数就是一个闭包。这个函数里面使用了squares()函数中定义的x变量。
根据闭包的概念,这个x变量不会被销毁,一直存在。当在main函数中第一次调用 f1() 的时候,x++,值为1.第二次调用f1()的时候,x++值为2.
那为什么调用f2()返回的数据又从1开始了呢?
f1跟f2引用的是不同的环境,在调用x++时修改的不是同一个x,因此两个函数的第一次输出都是1。函数squares每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。
上例的x变量存放在哪里?
func f() *Cursor {
var c Cursor
c.X = 500
noinline()
return &c
}
Cursor是一个结构体,这种写法在C语言中是不允许的,因为变量c是在栈上分配的,当函数f返回后c的空间就失效了。但是,在Go语言规范中有说明,这种写法在Go语言中合法的。语言会自动地识别出这种情况并在堆上分配c的内存,而不是函数f的栈上。
所以在闭包环境中,引用的变量内存是在堆中分配。返回闭包并不是单纯返回一个函数,而是返回一个结构体,记录下函数返回地址和引用的环境中的变量地址。
type Closure struct {
F func()()
i *int
}
理解闭包和普通函数调用的区别
代码示例1
package main
import (
"fmt"
)
func main() {
a := []int{1, 2, 3}
for _, value := range a {
fmt.Println(value)
defer p(value)
}
}
func p(value int) {
fmt.Println(value)
}
运行结果
1
2
3
3
2
1
这个例子是普通的函数调用,每次调用p()时完成value的复制,等到返回的时候再执行。
对defer的理解: defer修饰的函数会将传入它的参数拷贝保存在自己的内存区域,等到函数返回时,才开始执行。由于defer是后进先出,所以执行变成3,2,1
代码示例2——为什么返回的三个值都是3呢?
package main
import (
"fmt"
)
func main() {
a := []int{1, 2, 3}
for _, value := range a {
fmt.Println(value)
defer func() {
fmt.Println(value)
}()
}
}
运行结果:
1
2
3
3
3
3
理解for…range的用法
在Go的for…range循环中,Go始终使用值拷贝的方式代替被遍历的元素本身,简单来说,就是for…range中那个value,是一个值拷贝,而不是元素本身。也是说value是个局部变量,只是把元素赋值给该变量而已。
理解闭包非传递参数外部变量值传的是引用
闭包里的非传递参数外部变量值是传引用的,也就是闭包是地址引用。在闭包函数里那个value就是外部不是闭包函数自己的参数,所以是相当于引用了外部的变量。
所以:闭包是通过地址引用来引用环境中的变量value,因此每次只是把value的地址拷贝了一份儿,就这样拷贝了三次。而执行到最后时value值为3,所以打印了3次value地址指向的值,所以是3,3,3。
转载自:https://www.cnblogs.com/ralap7/p/9195677.html
用自己理解的方式做了修改
回到我自己遇到的问题
对于以下代码,描述正确的是:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
intSlice := []int{1, 2, 3, 4, 5}
wg.Add(len(intSlice))
ans1, ans2 := 0, 0
for _, v := range intSlice {
vv := v
go func() {
defer wg.Done()
ans1 += v
ans2 += vv
}()
}
wg.Wait()
fmt.Printf("ans1:%v,ans2:%v", ans1, ans2)
return
}
我说一下自己的理解:go出去的每一个协程都可以理解成一个闭包,闭包绑定的是当前v的地址。v是for…range结构的接收变量,for函数在不断执行,v的值也在不断变化,所以协程中+=v并不是加一个固定的数字,具体的值需要视协程与主协程的时间而定。实测显示ans1的值一般都在20以上。
在for循环执行过程中,每次循环都创建一个局部变量vv,值为v的拷贝。闭包绑定这个局部变量vv的地址(这个vv本来的作用域只在for循环内,当次循环结束就应该被销毁,下次循环重新创建,因为这个变量被闭包绑定了,所以这个变量保存到了堆中,它的值一次为1,2,3,4,5).所以ans2的值为15。