2014-11-01 21 views
2

不应该编译器捕获for...range循环变量作为本地分配的闭包变量吗?Go中的捕获闭包(用于循环变量)

长的版本:

这引起了我的一些混乱in C#也和我试图去理解它;那为什么它在C#中修复了foreach(原因是:循环变量不能修改在循环体内部)以及不修复它的原因C#for循环(原因:循环变量可以修改循环)。

现在(我)在Go for...range循环似乎在C#很像foreach循环,但尽管我们不能改变这些变量(如kfor k, v := range m { ... }v);我们仍然必须首先将它们复制到一些本地关闭,以使它们按预期行事。

这是什么原因? (我怀疑这是因为Go以同样的方式对待任何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,9十次而不是显示预期的结果,如1,1,2,2,...(当然地图不一定在Go中排序,因此我们可能会看到3,3是最后一对值的十倍;而不是10,10作为最后一对值的十倍)。代码// (B)处的代码lab2也是如此,因为我们试图捕获内部作用域内的外部变量(我只是为了尝试这个)而捕获外部变量。在lab3处注释代码// (C)一切工作正常,你会十岁了数对那里像1,12,2,....

我试图用关闭+功能作为替代元组小号在Go。

+1

你能否让你的例子更简洁明了?也许是因为这是半夜的事实,但我不能看到你的期望和你的例子中发生了什么。 – 2014-11-01 21:14:44

+0

我已经描述了代码和我用这种方式编写它的目的,并遇到了这个问题。 – 2014-11-01 21:38:37

+1

这是一个常见问题:https://golang.org/doc/faq#closures_and_goroutines – dyoo 2014-11-01 23:07:34

回答

3

你想要关闭变量或值吗?例如,

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

功能文本是封闭件:它们可以指VARI ables定义在 周围的功能。然后这些变量在 周围函数和函数文字之间共享,并且它们在 之后仍然可以存活。

Go FAQ: What happens with closures running as goroutines?

至V的当前值绑定到每个封闭,因为它被启动,一个 必须修改内环来创建一个新的变量每次迭代。 一种方法是将变量作为参数传递给闭包。

更容易的是创建一个新的变量,使用声明 风格,看起来可能很奇怪,但在Go中运行良好。

+0

closure over'for'循环中的变量;在'for ... range'循环中关闭值。 – 2014-11-01 23:15:29

+1

“i:= i”的替代方案我见过Go的人使用的是让内部函数以'i'作为参数。至于它是否应该以这种方式工作,就像各种各样的设计问题一样,这是有争议的,但是当你购买语言生态系统时,你必须与你所拥有的一起工作。 – twotwotwo 2014-11-01 23:29:52

+1

@KavehShahbazian:您的选择。看到我修改后的答案。在你的例子中使用'k,v:= k,v'代表'range'。 – peterSO 2014-11-01 23:36:44