2017-06-23 103 views
1

我在我的程序中看到了与我的程序中的特定循环绑定的不同行为,但我不确定我是否理解为什么它的行为方式如此。为什么这两个for循环变体给我不同的行为?

//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和阅读下列

当rangi在切片上,每次迭代返回两个值。第一个是指数,第二个是指数

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

+0

的可能的复制[捕获闭幕(用于循环变量)在围棋](https://stackoverflow.com/questions/26692844/captured-closure-for-loop-variable -in-go) – Adrian

回答

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的问题,这是因为封锁指的是同一个变量,并且您不使用变量的值就,只有在l之后接力。而当你打印这个变量的值时,所有打印的都是相同的最后一个值。

也看到这个可能重复:Golang: Register multiple routes using range for loop slices/map

+0

好的,所以它与我们使用闭包不是必然与'range'操作有关的事实 – sreya

+0

@sreya是的,这不是'for ...的问题...范围“,这是因为闭包指向同一个变量,并且当您打印此变量的值时,所有打印都会输出相同的最后一个值。 – icza

+0

或者对于更干净的代码,只需将该变量作为参数传递给闭包即可:'actions [cmd] = func(cmd string){fmt.Println(cmd)}(cmd)' – Adrian