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

时间:2017-06-23 07:48:02

标签: dictionary for-loop go closures slice

我在程序中看到的不同行为与我程序中的这个特定循环有关,但我不确定我理解为什么它的行为方式如此。

//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方法时,数组的元素应始终由其索引引用,以及使用它返回的元素的用例是什么,因为它始终更新指针?

1 个答案:

答案 0 :(得分: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) // Refer to the detached, copy variable!
        }
    }
    for _, action := range actions {
        action()
    }
}

这样,loop1()的输出(在Go Playground上尝试):

update
create
delete

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

另见{x 3}}