golang的闭包和defer

1,388 阅读3分钟

闭包:

函数+引用环境变量=闭包

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函数调用该可以读取和重新赋值函数的命名返回参数(因为:闭包里引用了不作为参数传递进去的值,都是引用传递)