Go编译器不应该将for...range循环变量捕获为本地分配的闭包变量吗?
for...range
长版:
这也引起了我对C#的困惑,并且我试图理解它。这就是为什么它在C#5.0中已得到修复foreach(原因:循环变量 不能 在循环体内改变)以及未在C#for循环中对其进行修复的原因(原因:循环变量 可以 在循环体内改变)。
foreach
for
现在(对我而言)for...range,Go中的循环看起来很像foreachC#中的循环,但是尽管事实是我们无法更改这些变量(例如k和v中for k, v := range m { ... })。仍然我们必须首先将它们复制到某些本地闭包中,以使其表现出预期的效果。
k
v
for k, v := range m { ... }
这背后的原因是什么?(我怀疑这是因为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,1,2,2....
lab1
// (A)
range
9,9
1,1
2,2
3,3
10,10
// (B)
lab2
lab3
// (C)
我试图用 闭包+函数 代替Go中的 tuple 。
您是否希望对变量或值进行闭包?例如,
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() }
输出:
可变回路 3 3 3 价值循环 0 1个 2 可变范围 2 2 2 价值范围 0 1个 2
参考文献:
Go编程语言规范 函数文字 函数文字是闭包:它们可以引用周围函数中定义的变量。然后,这些变量在周围的函数和函数文字之间共享,并且只要可以访问它们就可以保留。 常见问题解答:以goroutines身份运行闭包会发生什么? 要将v的当前值绑定到每个闭包启动时,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为参数传递给闭包。 甚至更容易的是使用声明样式创建一个新变量,该声明样式可能看起来很奇怪,但在Go中可以正常工作。
Go编程语言规范
函数文字
函数文字是闭包:它们可以引用周围函数中定义的变量。然后,这些变量在周围的函数和函数文字之间共享,并且只要可以访问它们就可以保留。
常见问题解答:以goroutines身份运行闭包会发生什么?
要将v的当前值绑定到每个闭包启动时,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为参数传递给闭包。
甚至更容易的是使用声明样式创建一个新变量,该声明样式可能看起来很奇怪,但在Go中可以正常工作。