闭包:
函数+引用环境变量=闭包
go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或删除,在闭包中可以继续使用这个自由变量;其本质就是对上层变量的引用;由于函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,函数是编译期静态的概念,而闭包是运行期动态的概念
func outer(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func main() {
f := outer(10)
fmt.Println(f(11)) // 21
fmt.Pringln(f(12)) // 33
}
当初始化一个闭包实例 f 时,f 为 func(int) int 类型,自由变量 x 逃逸到堆上,即使离开了自由变量的环境,也不会被释放或者删除,闭包中可以继续使用func main() { str := []string{“a”, “b”, “c”} for _, v :=range str{ go func(){ fmt.Println(v) }() }}结果:ccc原因:闭包里引用了不作为参数传递进去的值,都是引用传递,Println其实引用了v的地址然后解引用,将值打印出来,等到goroutine执行println时,v所指向的值已经是c了;如果要正确打印,在定义闭包时要定义一个参数,将b作为参数传递进去go func(v string) { fmt.Println(v)}(v)此时结果顺序还可能不是 a, b, c等顺序,因为goroutine执行顺序有go runtime调度器决定myMap := make(map[int]*int)for idx, v := range []int{1, 2, 3, 4} { myMap[idx] = &v}for k, val := range myMapy { fmt.Println(“key=“, v, “val=“, *val)}key= 3 val= 4
key= 4 val= 4
key= 0 val= 4
key= 1 val= 4
key= 2 val= 4
原因:for循环引入了新的词法块,循环变量v在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值,以v为例,后续的迭代会不断更新v的值,循环结束时该内存地址存储的值为4;defer: 注册延时机制
当执行defer语句时,函数调用不会马上发生,语言层会把defer注册的函数以及变量拷贝到defer栈中保存,直到return前才执行defer中函数调用,需要注意,拷贝的是那一时刻函数的值和参数值。注册之后在修改函数值或参数值是不生效的;即使defer注册延迟函数的那一刻,函数参数的值已经确定,后续变化不会影响已经拷贝存储好的函数值
三条规则:
- defer的函数在压栈时也会保存参数,并非在执行时取值
- defer函数调用顺序是后进先出
- defer函数调用该可以读取和重新赋值函数的命名返回参数(因为:闭包里引用了不作为参数传递进去的值,都是引用传递)