英文:
I need to assign a variable to itself when iterating for the closure context to keep the correct value
问题
在不使用 i := i 的情况下,我得到了一个错误的结果 (3, 3, 5, 9, 7, 15)。而使用 i := i 后,我得到了正确的结果 (0, 0, 3, 3, 6, 10)。移除这个赋值语句类似于在循环结束时获取 i 的值。为什么会这样呢?
package main
import "fmt"
type Handler interface {
Handle(v int)
}
type Elem struct {
Handler Handler
}
var elems []*Elem
type handlerFunc func(v int)
func (h handlerFunc) Handle(v int) { h(v) }
func main() {
newElem := func(fn handlerFunc) {
elem := &Elem{Handler: handlerFunc(fn)}
elems = append(elems, elem)
}
for i := 0; i < 3; i++ {
i := i // *** 为什么要这样? ***
newElem(func(v int) { fmt.Printf("%d, ", i+v) })
newElem(func(v int) { fmt.Printf("%d, ", i*v) })
}
for n, e := range elems {
if e.Handler != nil {
e.Handler.Handle(n)
}
}
fmt.Printf("\n")
}
这段代码中的 i := i
是一个很有趣的现象。在每次循环迭代时,都会创建一个新的 i
变量,并将其初始化为当前迭代的值。这样做的目的是为了在闭包函数中捕获正确的 i
值,而不是在循环结束后捕获最后一次迭代的值。如果不使用 i := i
,闭包函数中的 i
将会捕获循环结束时的 i
值,导致输出结果不正确。通过使用 i := i
,我们可以确保闭包函数中的 i
值与当前迭代的值保持一致,从而得到正确的结果。
英文:
Without i := i, I get an incorrect result (3, 3, 5, 9, 7, 15). With it, I get (0, 0, 3, 3, 6, 10), which is correct. Removing the assignment is similar to getting the value of i at the end of the loop. Why?
package main
import "fmt"
type Handler interface {
Handle(v int)
}
type Elem struct {
Handler Handler
}
var elems []*Elem
type handlerFunc func(v int)
func (h handlerFunc) Handle(v int) { h(v) }
func main() {
newElem := func(fn handlerFunc) {
elem := &Elem{Handler: handlerFunc(fn)}
elems = append(elems, elem)
}
for i := 0; i < 3; i++ {
i := i // *** Why? ***
newElem(func(v int) { fmt.Printf("%d, ", i+v) })
newElem(func(v int) { fmt.Printf("%d, ", i*v) })
}
for n, e := range elems {
if e.Handler != nil {
e.Handler.Handle(n)
}
}
fmt.Printf("\n")
}
答案1
得分: 6
最简单的可视化方法是将函数的输出更改为:
newElem(func(v int) { fmt.Printf("加 %d - %d+%d\n", i+v, i, v) })
newElem(func(v int) { fmt.Printf("乘 %d - %d*%d\n", i*v, i , v) })
通过这个更改,输出变为:
加 3 - 3+0
乘 3 - 3*1
加 5 - 3+2
乘 9 - 3*3
加 7 - 3+4
乘 15 - 3*5
所以,你可以看到,在所有情况下,i
的值都是 3。这是因为你在 i
变量周围创建了闭包,所以函数在运行时会使用 i
的当前值,而函数实际使用 i
时,i
的值已经是 3。
如果你将代码更改如下,你还可以再次看到这一点:
http://play.golang.org/p/FRhr0n2oi7
在循环内部的赋值 i := i
修复了问题的原因是,你在循环的作用域内创建了一个新的变量 i
,它仍然被函数所闭包,但它的值永远不会改变。每次循环的新迭代都会创建一个新的 i
,因此之前的 i
不会改变值。
虽然这篇文档描述了在处理 goroutine 和闭包时的常见错误,但它应该对问题和潜在解决方案有所启示。
https://github.com/golang/go/wiki/CommonMistakes
英文:
The easiest way to visualize what is going on is to change the output of your functions to:
newElem(func(v int) { fmt.Printf("plus %d - %d+%d\n", i+v, i, v) })
newElem(func(v int) { fmt.Printf("times %d - %d*%d\n", i*v, i , v) })
With this change the output becomes:
plus 3 - 3+0
times 3 - 3*1
plus 5 - 3+2
times 9 - 3*3
plus 7 - 3+4
times 15 - 3*5
So, as you can see, i
is 3 in all cases. This is because you are creating closures around the i
variable, so the functions will use the current value of i
when they run, which is 3 by the time the functions actually use i
.
You can again see this if you change your code like the following:
http://play.golang.org/p/FRhr0n2oi7
The reason the assignment i := i
inside your loop fixes the problem is because you are creating a new variable i
inside the scope of your loop which is still closed over by the functions, but doesn't ever change. Each new iteration of the loop creates a new i
so no previous i
changes value.
Although this document describes a common mistake when dealing with goroutines and closures, it should shed a bit more light on the issue and potential solutions.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论