备注:本文大量的引用http://c.biancheng.net/view/59.html非原创,写的原因是为了方便自己总结一下使用。

一、介绍

维基百科讲,闭包(Closure),是引用了自由变量的函数。
go语言中,闭包是引用了自由变量的匿名函数,被引用的自由变量和匿名函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:

匿名函数 + 自由变量与引用环境 = 闭包

同一个匿名函数与不同引用环境组合,可以形成不同的实例,如下图所示。
image.png
图:闭包与函数引用

一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有”记忆性“,函数是编译期静态的概念,而闭包是运行期动态的概念。

二、闭包的作用域

闭包对它作用域上部的变量可以进行修改,修改引用的变量会进行实际修改。
下面我们准备了一个闭包,组成有 自由变量str以及匿名函数foo

// 准备一个字符串
str := "hello world"
// 创建一个匿名函数
foo := func() {
   
    // 匿名函数中访问str
    str = "hello hihi"
}
// 调用匿名函数
foo() 

输出位 hello hihi
原因是 str的 作用域范围为这里的整个代码块。

foo := func() {
       // 匿名函数中访问str
    str = "hello hihi"
}

这里面的str,为这个外面的代码块的str,所以顺序执行,会修改str的值。

三、闭包的情况下,如果在同一个作用域,后面的值变了,goroutine里面的只有变量值也会变

func TestName(t *testing.T) {
    a := "111"
    go func() {
        time.Sleep(time.Second * 1)
        fmt.Println(a)
    }()
    a = "222"
    time.Sleep(time.Second * 2)
}

输出:”222“
如果是同一个自由变量在并发的情况下,需要特别注意这点。

四、并发与for循环的场景下,闭包作用域

func TestFor(t *testing.T) {
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Second * 1)
}

输出:

=== RUN   TestFor
10
10
10
10
10
10
10
10
10
4
--- PASS: TestFor (1.00s)

在这里闭包的组成有 自由变量i以及匿名的函数 func()以及并发原语go
首先我们看一下自由变量i的作用域范围,为整个for循环的过程。
虽然这里开了10个goroutine并发协程,但他们其实都共用 同一个 自由变量i
在打印执行的时候,此时的自由变量i为此时所在的for循环中的自由变量i值。


海生
104 声望32 粉丝

与黑夜里,追求那一抹萤火。