【问题标题】:Why do these two for loop variations give me different behavior?为什么这两个 for 循环变体会给我不同的行为?
【发布时间】:2017-06-23 07:48:01
【问题描述】:

我在我的程序中看到了与我的程序中的这个特定循环相关的不同行为,但我不确定我是否理解它为什么会这样。

//global variable
var cmds = []string {
    "create",
    "delete",
    "update",
}

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()
    }
}

loop1() 的输出是

update
update
update

loop2() 的输出是

delete
update
create

我去查看internet并阅读以下内容

在切片上进行测距时,每次迭代都会返回两个值。第一个是索引,第二个是该索引处元素的副本

它说的是一个副本,那么这是否意味着它返回了一个字符串的副本,但它实际上是一个指向变量cmd 的指针?在这种情况下,对cmd 的任何引用都将在循环结束时实际引用数组中的最后一个元素,例如update?这是否意味着在使用range 方法时,数组的元素应该始终由它们的索引引用,以及使用它返回的元素的用例是什么,因为它总是在更新指针?

【问题讨论】:

标签: dictionary for-loop go closures slice


【解决方案1】:

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

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

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()
    }
}

有了这个,loop1() 的输出(在Go Playground 上试试):

update
create
delete

这不是for ... range 的问题,这是因为闭包引用同一个变量,并且您不会立即使用变量的值,只有在循环之后。当你打印这个变量的值时,所有的都打印相同的,最后一个值。

另请参阅此可能的重复项:Golang: Register multiple routes using range for loop slices/map

【讨论】:

  • 好吧,这与我们使用闭包这一事实有关,而与 range 操作无关
  • @sreya 是的,这不是for ... range 的问题,这是因为闭包引用了同一个变量,当你打印这个变量的值时,都打印相同的最后一个值它。
  • 或者为了更简洁的代码,只需将变量作为参数传递给闭包:actions[cmd] = func(cmd string) { fmt.Println(cmd) } (cmd)
  • @Adrian 一般是的,但是在这种情况下,命令是一个没有参数的函数类型,所以这样使用起来更不方便。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-24
  • 1970-01-01
  • 2012-01-17
  • 1970-01-01
相关资源
最近更新 更多