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

您所在的位置:网站首页 GO语言append切片删除元素的坑 go语言 for循环中进行append操作的坑

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

2023-06-21 10:32| 来源: 网络整理| 查看: 265

问题描述

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

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3