Go中共享底层数组的两个切片append后结果不符预期原因求解
认知错误点
你对切片的核心认知偏差有三点:
- Go的切片是值传递,不是引用传递:你把切片传给
incr函数时,传的是切片结构体(由底层数组指针、长度len、容量cap三个字段组成)的完整副本。你在函数内部对副本的指针、长度、容量做的任何修改,都完全不会影响外部的原始切片。 - 你误以为
s2追加元素3时还和s1共享底层数组:字面量初始化的s1 := []int{1,2}长度和容量都是2,没有多余空间,给s2追加元素时会触发扩容,直接分配新的底层数组,此时s2和s1已经彻底解绑,后续修改互不影响。 - 你误以为
incr函数内的修改会同步到外部切片:incr接收s1的副本后,执行append时同样会因为容量不足触发扩容,所有修改都作用在新分配的底层数组上,外部s1完全感知不到。
代码实际运行逻辑
步骤1:初始化切片
s1 := []int{1, 2} s2 := s1
此时s1和s2共享底层数组A[1,2],二者的结构体都是ptr=数组A地址, len=2, cap=2。
步骤2:给s2追加元素3
s2 = append(s2, 3)
s2当前长度等于容量,没有多余空间存储新元素,触发扩容:
- 申请新的底层数组B,容量翻倍为4(Go切片小于1024字节时默认扩容策略为翻倍)
- 将数组A的
[1,2]复制到数组B的前两位,第三位赋值为3 - 更新
s2的结构体为ptr=数组B地址, len=3, cap=4
此时s1仍然指向数组A,和s2再无关联。
步骤3:调用incr(s1)
将s1的副本传入incr函数,函数内的参数s初始结构体为ptr=数组A地址, len=2, cap=2:
- 执行
s = append(s, 0):容量不足触发扩容,申请新的底层数组C,容量为4,复制数组A的[1,2]到C的前两位,第三位赋值为0,此时函数内的s结构体更新为ptr=数组C地址, len=3, cap=4 - 循环给
s的所有元素自增,数组C变为[2,3,1] - 函数执行结束,副本
s被销毁,外部的s1没有任何变化,仍然指向数组A[1,2]。
步骤4:调用incr(s2)
将s2的副本传入incr函数,函数内的参数s初始结构体为ptr=数组B地址, len=3, cap=4:
- 执行
s = append(s, 0):当前容量4足够容纳长度4的切片,不需要扩容,直接将数组B的第四位赋值为0,函数内的s结构体更新为ptr=数组B地址, len=4, cap=4 - 循环给
s的所有元素自增,数组B从原来的[1,2,3,0]变为[2,3,4,1] - 函数执行结束,副本
s被销毁,外部的s2结构体仍然是ptr=数组B地址, len=3, cap=4,只能看到数组B的前3位[2,3,4]。
步骤5:打印结果
最终s1的值为[1,2],s2的值为[2,3,4],和代码注释的输出一致。
补充说明
只有满足以下两个条件时,函数内对切片元素的修改才会对外部可见:
- 函数内没有触发切片扩容,仍然和外部切片共享同一个底层数组
- 修改的元素在外部切片的长度范围内
内容的提问来源于stack exchange,提问作者unifreak
相关产品推荐
相关产品推荐

