在Go中捕获闭包(用于循环变量)

时间:2014-11-01 20:20:47

标签: for-loop go closures

不应该将编译器捕获for...range循环变量作为本地分配的闭包变量吗?

长版:

这也让我感到困惑in C#而且我试图理解它;为什么它在C#5.0中被修复foreach(原因:循环变量不能在循环体内发生变化)以及不在C#for循环中修复它的原因(原因:循环变量可以在循环体内发生变化)。

现在(对我来说)Go中的for...range循环似乎与C#中的foreach循环非常相似,但尽管事实上我们无法改变这些变量(例如k和{{ 1}}在v);我们仍然需要先将它们复制到一些本地闭包中,以使它们按预期运行。

这背后的原因是什么? (我怀疑它是因为Go以同样的方式处理任何for k, v := range m { ... }循环;但我不确定。)

以下是一些检查所述行为的代码:

for

func main() { lab1() // captured closure is not what is expected fmt.Println(" ") lab2() // captured closure is not what is expected fmt.Println(" ") lab3() // captured closure behaves ok fmt.Println(" ") } func lab3() { m := make(map[int32]int32) var i int32 for i = 1; i <= 10; i++ { m[i] = i } l := [](func() (int32, int32)){} for k, v := range m { kLocal, vLocal := k, v // (C) captures just the right values assigned to k and v l = append(l, func() (int32, int32) { return kLocal, vLocal }) } for _, x := range l { k, v := x() fmt.Println(k, v) } } func lab2() { m := make(map[int32]int32) var i int32 for i = 1; i <= 10; i++ { m[i] = i } l := [](func() (int32, int32)){} for k, v := range m { l = append(l, func() (int32, int32) { kLocal, vLocal := k, v // (B) captures just the last values assigned to k and v from the range return kLocal, vLocal }) } for _, x := range l { k, v := x() fmt.Println(k, v) } } func lab1() { m := make(map[int32]int32) var i int32 for i = 1; i <= 10; i++ { m[i] = i } l := [](func() (int32, int32)){} for k, v := range m { l = append(l, func() (int32, int32) { return k, v }) // (A) captures just the last values assigned to k and v from the range } for _, x := range l { k, v := x() fmt.Println(k, v) } } 所示,在评论lab1,我们只得到// (A)的最后一个值;输出就像打印range十次而不是显示预期结果,如9,91,1,...(并且当然地图不一定在Go中排序,所以我们可能会看到{ {1}}最后一对值的十倍;而不是最后一对值的2,2十倍)。对3,310,10处的评论代码也是如此,这是预期的,因为我们正在尝试捕获内部范围内的外部变量(我把这个也只是为了尝试)。在// (B)评论lab2处的代码中,一切正常,您会看到十对数字,例如lab3// (C),....

我试图用闭包+功能替代Go中的元组

1 个答案:

答案 0 :(得分:4)

你想要对变量或值进行闭包吗?例如,

package main

import "fmt"

func VariableLoop() {
    f := make([]func(), 3)
    for i := 0; i < 3; i++ {
        // closure over variable i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("VariableLoop")
    for _, f := range f {
        f()
    }
}

func ValueLoop() {
    f := make([]func(), 3)
    for i := 0; i < 3; i++ {
        i := i
        // closure over value of i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("ValueLoop")
    for _, f := range f {
        f()
    }
}

func VariableRange() {
    f := make([]func(), 3)
    for i := range f {
        // closure over variable i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("VariableRange")
    for _, f := range f {
        f()
    }
}

func ValueRange() {
    f := make([]func(), 3)
    for i := range f {
        i := i
        // closure over value of i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("ValueRange")
    for _, f := range f {
        f()
    }
}

func main() {
    VariableLoop()
    ValueLoop()
    VariableRange()
    ValueRange()
}

输出:

VariableLoop
3
3
3
ValueLoop
0
1
2
VariableRange
2
2
2
ValueRange
0
1
2

参考文献:

  

The Go Programming Language Specification

     

Function literals

     

函数文字是闭包:它们可以引用在中定义的变量   周围的功能。然后在这些变量之间共享这些变量   周围函数和函数文字,它们存活下来   只要他们可以访问。

     

Go FAQ: What happens with closures running as goroutines?

     

在启动时将v的当前值绑定到每个闭包,一个   必须修改内部循环以在每次迭代时创建一个新变量。   一种方法是将变量作为参数传递给闭包。

     

更简单的方法就是使用声明创建一个新变量   可能看似奇怪但在Go中工作正常的风格。