Go 的 for range
里的循环变量是共享的,这可能会引起一些问题,以下面的例子为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| type Item struct { ID int64 Name string Amount int }
items := []Item{ { ID: 1, Name: "mushroom", Amount: 2, }, { ID: 2, Name: "apple", Amount: 5, }, { ID: 3, Name: "ore", Amount: 1, }, }
itemMap := make(map[int64]*Item) for _, item := range items { itemMap[item.ID] = &item }
for id, item := range itemMap { fmt.Printf("id: %d item: %#v\n", id, *item) }
|
上面的代码大致的意思是基于 items
创建一个 id
到 *Item
的映射,执行后结果为:
1 2 3
| id: 1 item: main.Item{ID:3, Name:"ore", Amount:1} id: 2 item: main.Item{ID:3, Name:"ore", Amount:1} id: 3 item: main.Item{ID:3, Name:"ore", Amount:1}
|
可以看到结果数据和预期不符,主要的原因就是因为循环变量是共享的,我们可以打印循环变量的地址来验证:
1 2 3
| for _, item := range items { fmt.Printf("%p\n", &item) }
|
结果输出:
1 2 3
| 0xc000124000 0xc000124000 0xc000124000
|
可以看到 item
的地址一直是 0xc000124000
,故可以明白为什么上面的 map
的结果不符合预期。
要避免这种情况,只需要记住:用 for range 在遍历的时候,避免取循环变量的地址作为结果,除非你知道自己在干什么
上面的需求可以通过另一种形式取值来达成:
1 2 3 4 5 6 7 8
| itemMap := make(map[int64]*Item) for offset := range items { itemMap[items[offset].ID] = &items[offset] }
for id, item := range itemMap { fmt.Printf("id: %d item: %#v\n", id, *item) }
|
最终结果:
1 2 3
| id: 1 item: main.Item{ID:1, Name:"mushroom", Amount:2} id: 2 item: main.Item{ID:2, Name:"apple", Amount:5} id: 3 item: main.Item{ID:3, Name:"ore", Amount:1}
|