golang中的闭包

小刀田田 · · 532 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

go语言中函数可以作为返回值,可以作为参数,可以作为右值绑定到变量,golan把这些返回值,参数,或变量称为function value,函数指令在编译期间生成,而function value本质上是一个指针,指向一个runtime.funcval结构体,这个结构体里面只有一个地址——函数指令的入口地址。假设有如下代码:

func A(i int){
    i++
    fmt.Println(i)
}

func B(){
    f1:= A
    f1(1)
}
func C(){
    f2:=A
    f2(1)
}

f1和f2都指向同一个函数A(int),其指令入口为addr1,编译阶段编译器会在只读数据段为他分配一个funcval结构体fn指向addr1,而他本身的地址会在执行阶段赋给f1和f2,既然只要通过addr1就可以执行函数A(),为什么还要通过fn这个结构体中转一下呢,这是为闭包准备的。

什么是闭包(closure),用一句话来描述就是,闭包是一个函数和与他绑定的外部环境的集合。闭包在实现上是一个结构体,他存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境中包含该函数的内部绑定符号,及其在外部定义但在函数中引用的自由变量。函数和闭包的不同在于,当捕获闭包的时候,他的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,他也能照样运行。例如下面的例子:

func create() func()int{
    c:=2
    return func()int{
        return c
    }
}
func main(){
    f1:=create()
    f2:=create()
    fmt.Println(f1())
    fmt.Println(f2())
}

函数create() func()int就是一个闭包,它有自由变量c,当create()函数执行结束,f1和f2依然能够正常调用这个闭包函数,获取捕获变量c的值。因为闭包包含捕获变量,所以在执行阶段才创建对应的闭包对象,假设create函数的指令入口为addr1,在堆上分配一个funcval的结构体,fn指向闭包函数入口,同时其捕获列表中捕获一个变量c,然后这个结构体的地址add2被赋值给f1。再次调用create函数,在分配一个funcval结构体,fn指向闭包函数入口addr1,在捕获对应的捕获变量,还是只有一个c,最后将这funcval的起始地址addr2赋值给f2。

func create() []func() int {
    function := make([](func() int), 2, 2)
    
    for i := 0;i < 2;i++ {
        function[i] = func() int {
            return i
        }
        i++
    }
    return function
}

func main() {
    funcs := create()
    fmt.Println(funcs[0]()) \\ 2
    fmt.Println(funcs[1]()) \\ 2
}

捕获变量会发生改变时,捕获列表中保存的是捕获变量的地址,对捕获变量的修改会保持一致。因此,funcs[0]()funcs[1]()两个函数获得的i都是2。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:小刀田田

查看原文:golang中的闭包

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

532 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传