Golang 在使用slice中的一个大坑
问题描述
我们都知道,slice是引用类型,按理来说slice作为函数参数传递,在函数内修改slice的时候应该修改的是原slice的值。但是看下面这个例子:
1 | package main |
切片s作为函数参数传递到modifySlice函数中,并进行了append操作,然而打印的结果显示,slice并没有发生改变:
1 | before:[1 2 3] |
问题分析
Go的slice类型中包含了一个array指针以及len和cap两个int类型的成员。
Go中的参数传递实际都是值传递,将slice作为参数传递时,函数中会创建一个slice参数的副本,这个副本同样也包含array,len,cap这三个成员。
副本中的array指针与原slice指向同一个地址,所以当修改副本slice的元素时,原slice的元素值也会被修改。但是如果修改的是副本slice的len和cap时,原slice的len和cap仍保持不变。
如果在操作副本时由于扩容操作导致重新分配了副本slice的array内存地址,那么之后对副本slice的操作则完全无法影响到原slice,包括slice中的元素。
Go圣经里也有类似的描述:
为了正确地使用slice,必须记住,虽然底层数组的元素是间接引用的,但是slice的指针、长度和容量不是。
通常情况下,我们不清楚一次append调用会不会导致一次新的内存分配,所以我们不能假设原始情况的slice和调用append后的slice指向同一个底层数组,也无法证明它们就指向不同的底层数组。同样,我们也无法假设旧slice上对元素的操作会或者不会影响新的slice元素。
因此上述解决方法为传递切片地址
1 | package main |
1 | before: [1 2 3] |
另外一个坑
由于go语言的特性如果不特别说明创建的切片本质上都是指向同一个内存空间
如果想要赋值的切片与原来切片不相关,需要另外开辟空间,这里用到copy函数,开辟独立空间