不应该将编译器捕获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,9
,1,1
,...(并且当然地图不一定在Go中排序,所以我们可能会看到{ {1}}最后一对值的十倍;而不是最后一对值的2,2
十倍)。对3,3
处10,10
处的评论代码也是如此,这是预期的,因为我们正在尝试捕获内部范围内的外部变量(我把这个也只是为了尝试)。在// (B)
评论lab2
处的代码中,一切正常,您会看到十对数字,例如lab3
,// (C)
,....
我试图用闭包+功能替代Go中的元组。
答案 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
函数文字是闭包:它们可以引用在中定义的变量 周围的功能。然后在这些变量之间共享这些变量 周围函数和函数文字,它们存活下来 只要他们可以访问。
Go FAQ: What happens with closures running as goroutines?
在启动时将v的当前值绑定到每个闭包,一个 必须修改内部循环以在每次迭代时创建一个新变量。 一种方法是将变量作为参数传递给闭包。
更简单的方法就是使用声明创建一个新变量 可能看似奇怪但在Go中工作正常的风格。