go语言 for循环中进行append操作的坑

问题描述

在写代码时遇到了一个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内容都会更改。

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值