为什么这两个for循环变体会给我不同的行为?

huangapple go评论71阅读模式
英文:

Why do these two for loop variations give me different behavior?

问题

我看到你的程序中有一个特定的循环,但它的行为与其他部分不同,我不确定为什么会这样。根据你提供的代码,loop1()loop2()的输出结果不同。你在互联网上查找了一些信息,并引用了一段关于使用range循环的说明。你想知道这段说明的意思以及如何正确使用range循环。

根据你引用的说明,当使用range循环遍历切片时,每次迭代会返回两个值。第一个值是索引,第二个值是该索引处元素的副本。这意味着返回的是元素的副本,但实际上是指向变量cmd的指针。因此,通过对cmd的任何引用最终都会指向数组中的最后一个元素,即update。这也解释了为什么loop1()的输出都是update

至于如何正确使用range循环,如果你需要引用切片中的元素,最好使用索引来访问元素,而不是直接引用迭代变量。这样可以避免引用指针问题,确保每次迭代都使用正确的值,就像loop2()中的做法一样。

关于返回的第二个值(元素的副本),如果你只需要访问元素的值而不需要修改它,可以使用这个副本。但如果你需要修改元素的值,最好使用索引来直接访问和修改切片中的元素。

希望这些解释对你有帮助!如果还有其他问题,请随时提问。

英文:

I'm seeing different behavior in my program that's tied to this particular loop in my program but I'm not sure I understand why it's behaving the way it is.

<!-- language: go -->

//global variable
var cmds = []string {
    &quot;create&quot;,
    &quot;delete&quot;,
    &quot;update&quot;,
}

func loop1() {

	actions := make(map[string]func())

	for _, cmd := range cmds {
		actions[cmd] = func() {
			fmt.Println(cmd)
		}
	}
    for _, action := range actions {
	    action()
    }
}
func loop2() {

	actions := make(map[string]func())

	for i, cmd := range cmds {
		command := cmds[i]
		actions[cmd] = func() {
			fmt.Println(command)
		}
	}
    for _, action := range actions {
	    action()
    }
}

The output for loop1() is

update
update
update

The output for loop2() is

delete
update
create

I went looking on the internet and read the following

> When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index

It says a copy, so does that mean it returns a copy of the string but it's really a pointer to variable cmd? In which case any references to cmd will by the end of the loop all actually reference the last element in the array, e.g. update? Does this mean that elements of an array should always be referenced by their index when using the range method, and what's the use case for using the element it returns since it's always updating the pointer?

答案1

得分: 4

loop1()的问题在于你在actions映射中存储了一个引用了循环变量cmd的函数字面量。这个循环变量只有一个实例,所以当你在循环之后调用actions映射中存储的函数时,所有函数都会引用这个单一的循环变量(因为函数/闭包仍然对它有引用),但是它在执行时的值将是for循环设置的最后一个值,也就是cmds切片中的最后一个值(即"update"),所以你会看到"update"被打印了3次。

一个简单的解决方法是复制这个循环变量,这样每次迭代时,每个函数字面量都会有自己的副本,它与循环变量是"分离的":

func loop1() {
    actions := make(map[string]func())

    for _, cmd := range cmds {
        cmd2 := cmd
        actions[cmd] = func() {
            fmt.Println(cmd2) // 引用分离的副本变量!
        }
    }
    for _, action := range actions {
        action()
    }
}

使用这个方法,loop1()的输出结果为:

update
create
delete

这不是for ... range的问题,而是闭包引用了同一个变量,并且你没有立即使用变量的值,只有在循环之后才使用。当你打印这个变量的值时,所有的打印结果都是相同的,即它的最后一个值。

另请参阅可能的重复问题:https://stackoverflow.com/questions/44044245/golang-register-multiple-routes-using-range-for-loop-slices-map/44045012#44045012

英文:

The problem with loop1() is that you store a function literal in the actions map that references the loop variable cmd. There is only one instance of this loop variable, so when after the loop you call the functions stored in the actions map, all will refer to this single loop variable (which is kept because the functions / closures still have a reference to it), but its value at the time of execution will be the last value set by the for loop, which is the last value in the cmds slice (that is, &quot;update&quot;, so you'll see &quot;update&quot; printed 3 times).

An easy workaround is to make a copy of this loop variable, so each iteration, each function literal will have its own copy, which is "detached" from the loop variable:

func loop1() {
	actions := make(map[string]func())

	for _, cmd := range cmds {
		cmd2 := cmd
		actions[cmd] = func() {
			fmt.Println(cmd2) // Refer to the detached, copy variable!
		}
	}
	for _, action := range actions {
		action()
	}
}

With this, output of loop1() (try it on the Go Playground):

update
create
delete

This it's not an issue of the for ... range, it's because the closures refer to the same variable, and you don't use the value of the variable right away, only after the loop. And when you print the value of this variable, all print the same, last value of it.

Also see this possible duplicate: https://stackoverflow.com/questions/44044245/golang-register-multiple-routes-using-range-for-loop-slices-map/44045012#44045012

huangapple
  • 本文由 发表于 2017年6月23日 15:48:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/44715882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定