踩踩Go语言range坑

Sep 13, 2019

       go只提供了一种循环方式,即for循环,在使用时可以像c那样使用,也可以通过for range方式遍历容器类型如数组、切片和映射。但是在使用for range时,如果使用不当,就会出现一些问题,导致程序运行行为不如预期。比如,下面的示例程序将遍历一个切片,并将切片的值当成映射的键和值存入,切片类型是一个int型,映射的类型是键为int型,值为*int,即值是一个地址。

下面我们就来看下一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"fmt"
)

func main() {

t_slice := [] int {0,1,2,3}
t_map := make(map[int] *int)

for key,value := range t_slice{
t_map[key] = &value
}

fmt.Println("循环打印 t_map 对应值")
prtMap(t_map)
}
func prtMap(myMap map[int]*int) {
for key,value := range myMap{
fmt.Printf("map[%v] = %v\n",key,*value)
}
}

我们期望输出结果:

1
2
3
4
5
循环打印 t_map 键值
map[2] = 2
map[3] = 3
map[0] = 0
map[1] = 1

然而实际输出结果:

1
2
3
4
5
循环打印 t_map 键值
map[2] = 3
map[3] = 3
map[0] = 3
map[1] = 3

结果问题分析:
       但是由输出可以知道,映射的值都相同且都是3。其实可以猜测映射的值都是同一个地址,遍历到切片的最后一个元素3时,将3写入了该地址,所以导致映射所有值都相同。其实真实原因也是如此,因为for range创建了每个元素的副本,而不是直接返回每个元素的引用,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误,在迭代时,返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以值的地址总是相同的,导致结果不如预期。
从上面结果我们可以猜想到,range指向的都是同一个指针。通过Println我们可以验证下我们的猜想:

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
import (
"fmt"
)

func main() {

t_slice := [] int {0,1,2,3}
t_map := make(map[int] *int)

for key,value := range t_slice{
t_map[key] = &value
}

fmt.Println("循环打印 t_map 对应值")
fmt.Println(t_map)
//prtMap(t_map)
}
func prtMap(myMap map[int]*int) {
for key,value := range myMap{
fmt.Printf("map[%v] = %v\n",key,*value)
}
}

```
输出结果
```$xslt
map[0:0xc000014078 1:0xc000014078 2:0xc000014078 3:0xc000014078]

由此我们可以看到,我们的猜想是正确的。

解决方案

修改方式也很简单,我们只需要声明一个中间变量,保存value,并且复制给map即可

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
package main

import (
"fmt"
)

func main() {

t_slice := [] int {0,1,2,3}
t_map := make(map[int] *int)

for key,value := range t_slice{
n := value
t_map[key] = &n
}

fmt.Println("直接打印 t_mp")
fmt.Println(t_map)
fmt.Println("循环打印 t_map 键值")
prtMap(t_map)
}
func prtMap(myMap map[int]*int) {
for key,value := range myMap{
fmt.Printf("map[%v] = %v\n",key,*value)
}
}

输出结果:

1
2
3
4
5
6
7
直接打印 t_mp
map[0:0xc000014078 1:0xc000014080 2:0xc000014088 3:0xc000014090]
循环打印 t_map 键值
map[0] = 0
map[1] = 1
map[2] = 2
map[3] = 3