问题描述
在写代码时遇到了一个bug,最终定位后发现是在for range中对slice进行append地址操作,最终的结果slice中的值全部一样且为for range中的最后一个,将问题简化如下。
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for _, num := range nums {
res = append(res, &num)
fmt.Println("num:", num)
}
for _, r := range res {
fmt.Println("res:", *r)
}
}
运行结果如下:
num: 1
num: 2
num: 3
num: 4
num: 5
res: 5
res: 5
res: 5
res: 5
res: 5
每次循转中num的值是正常的,但是由append构造的res中,全是nums的最后一个值。
问题出现原因
最终总结出原因是在for range语句中,创建了变量num且只被创建了一次。即num有自己的空间内存且地址在for循环过程中不变,循环过程中每次将nums中对应的值和num进行值传递。
如下代码将num的地址和res中实际的内容打印出来:
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for _, num := range nums {
res = append(res, &num)
fmt.Println("num:", num, "&num:", &num)
}
fmt.Println("res:", res)
}
运行结果如下:
num: 1 &num: 0xc0000b2008
num: 2 &num: 0xc0000b2008
num: 3 &num: 0xc0000b2008
num: 4 &num: 0xc0000b2008
num: 5 &num: 0xc0000b2008
res: [0xc0000b2008 0xc0000b2008 0xc0000b2008 0xc0000b2008 0xc0000b2008]
可以看出变量num的值随循环在变但地址不变,印证了上述说法。即res中实际append了5次变量num的地址,只要对num的值进行更改,那么res中所有的值都会更改,所以出现了res中的值全部等于nums中最后一个值的情况。
解决方法
思路很明显,目的就是让append进res中的变量是不同的变量即可,目前能想到有两个方法:
- 在for循环中每次再定义一个新的变量num_temp,将num的值传给num_temp,之后append该变量即可。代码如下:
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for _, num := range nums {
numTemp := num // 创建一个新的临时变量
res = append(res, &numTemp)
fmt.Println("num:", num, "&num:", &num)
fmt.Println("numTemp:", numTemp, "&numTemp:", &numTemp)
}
fmt.Println("res:", res)
for _, r := range res {
fmt.Println("res:", *r)
}
}
运行结果:
num: 1 &num: 0xc0000b2008
numTemp: 1 &numTemp: 0xc0000b2010
num: 2 &num: 0xc0000b2008
numTemp: 2 &numTemp: 0xc0000b2030
num: 3 &num: 0xc0000b2008
numTemp: 3 &numTemp: 0xc0000b2038
num: 4 &num: 0xc0000b2008
numTemp: 4 &numTemp: 0xc0000b2040
num: 5 &num: 0xc0000b2008
numTemp: 5 &numTemp: 0xc0000b2048
res: [0xc0000b2010 0xc0000b2030 0xc0000b2038 0xc0000b2040 0xc0000b2048]
res: 1
res: 2
res: 3
res: 4
res: 5
- 不使用for range的形式,直接用索引来对nums取值。代码如下:
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for i := 0; i < len(nums); i++ {
res = append(res, &nums[i])
fmt.Println("nums:", nums[i], "&nums:", &nums[i])
}
fmt.Println("res:", res)
for _, r := range res {
fmt.Println("res:", *r)
}
}
运行结果:
nums: 1 &nums: 0xc00001c180
nums: 2 &nums: 0xc00001c188
nums: 3 &nums: 0xc00001c190
nums: 4 &nums: 0xc00001c198
nums: 5 &nums: 0xc00001c1a0
res: [0xc00001c180 0xc00001c188 0xc00001c190 0xc00001c198 0xc00001c1a0]
res: 1
res: 2
res: 3
res: 4
res: 5
需要注意第二种方法节省了空间但是本质上nums和res两个slice使用了一个底层数组,修改任意一个slice的数据会导致两个slice内容都会更改。