Slice中的坑
slice 介绍
像其他语言一样,都是有数组,数组是具备固定类型和固定长度的,因此使用起来不是很方便,因此go引入了silce,表示一个拥有相同元素类型的可变长度的序列。
数组和slice是紧密关联的,slice是一种轻量级的数据结构,可以用来访问数组的部分或者全部元素。slice中有三个属性:指针、长度和容量
1 | type SliceHeader struct { |
- Data 是
指向数组的指针 - 是当前切片的长度
- Cap是当前切片的容量,即Data数组的大小
其中Data是一片连续的内存空间的数组
初始化
Go中的初始化包含三种方式
- 通过下标的方式获取数组或者切片的一部分
- 使用字面量初始化新的切片
- 使用关键字
make创建切片make([]T, length, capacity) 其中容量capacity是可选参数 也可以写成 make([]type, len)
1 | arr[0:3] or slice[0:3] |
slice 中的坑
引起坑的原因
- 函数是值传递,
- slice结构中数据部分是 指向数组的指针
- 打印slice受len控制
- 如果发生扩容,会产生新的数组
直接通过一些案例来学习下
案例1
1 | //情况一 |
输出 1,2,3
分析
- go中函数是值传递
- 打印受len影响

此时slice的 len=3,cap=4,在执行TestSlice(slice)之后,外部slice传参到函数值,由于slice中持有的是数组的指针,且并没有发生扩容,所以这里两个slice指向的是同一个数组
后面打印的slice是 len为3,因此,打印结果是1,2,3,如果此时强行修改len=4,那么打印结果为1,2,3,4
1 | func main() { |
案例2
1 | func main() { |
这个case跟案例一道理一样,并未出现扩容,执行TestSlice之后,两个slice数据部分仍然指向统一数据 10,2,3,4,打印slice跟len有关(len=3),因此
输出结果 10,2,3
案例3
1 | //情况三 |
这个case的不同之处是涉及到了 扩容操作,导致数据部分指向了不同的数组
输出结果 1,2,3
slice 初始化时候 指定了容量为3,slice = append(slice,1,2,3) 之后 len=3,之后执行了TestSlice之后,len其实到了4,超过容量了,因此需要执行扩容操作
扩容逻辑:
1、根据策略申请一个更大的数组空间(slice容量的扩容规则:当原slice的cap小于1024时,新slice的cap变为原来的2倍;原slice的cap大于1024时,新slice变为原来的1.25倍)
2、copy 旧数组中的数据到新数组
3、添加新增的数据
4、将数组的指针复制给slice

扩容操作之后 通过 值传递进来的原slice 其实没有变化了,此时扩容之后两个slice数据部分分别指向不同的数组
slice正确使用
由于函数的参数是按值传递的,而 slice不仅保存了底层数组的引用,还保存了 slice所引用的范围。这就导致了上面的情况,底层数组改变了,而 slice没有改变。
- 传递 slice的指针给函数
- 将修改后的 slice返回并重新赋值
- 最好的解决办法是不要在多个函数里修改 slice
在案例2中,在不扩容情况下 其实数据部分数组指针共用的,因此在函数内修改会影响到外部入参的slice,有时候我们希望在函数里修改slice但是又不希望影响外部数据怎么办?这时候就需要使用 cpoy函数复制要修改的slice到一个新的slice中去(这样底层数据数组就不相同了)
函数修改如下
1 |
|






