本文主要讨论 slice 扩容时对原数组的影响。
slice 是 Go 语言中的动态数组,具体的使用方法可以参考:Go by Example: Slices。
slice 有两种创建方式,一种是直接创建,如:
1 | s := make([]int, 4) |
另一种是从数组中创建,如:
1 | arr := [4]int{1, 2, 3, 4} |
第一种是比较简单的场景,如果是从数组中分片创建的,那有一个场景是需要特殊注意的:扩容
。
还是上面这段代码,先看一下它在内存里的结构是怎么样的。
slice 有三个属性字段:长度、容量和指向数组的指针。如果是直接创建的,数组则在创建时新建;如果是从数组中创建的,slice 中的数组指针就指向原数组。那么这就关系到一个问题:修改 slice 时,也会修改原数组吗?
答案好像很显然:会!因为 slice 其实没有额外分配空间,修改 slice 的元素就是修改其数组指针指向的空间嘛,就是原数组嘛。这个答案当然是对的,但是这里有特殊情况,那么就是 slice 扩容。
这里要分两种情况:
- slice 扩容后,还没有触及到原数组的容量,那么 slice 中的数组指针依然指向原数组。
- slice 扩容后,超过了原数组的容量,那么 Go 会开辟一块新的内存,把原数组拷贝进去,slice 中的数组指针指向新数组。
写代码验证。
1 | arr := [4]int{0, 2, 0, 0} |
从代码和结果输出里就能很清晰地看到,在 slice 扩容没有超过原数组容量(也就是 4)时,所有对 slice 的操作,其实都是在原数组上原地修改的。一旦 slice 扩容超过了原数组的容量,那么 slice 指向的就是一个新数组了,对它的修改也就不会在原数组上生效了。