Go的append不修改原切片,因其值传递三字段结构体并总返回新切片;是否共享底层数组严格取决于len+新增数≤cap,扩容与否由当前容量唯一决定,必须赋值s=append(s,x)才生效。
Go 的
不会修改原切片变量,它返回一个新切片值;是否共享底层数组,只取决于当前
是否足够容纳新增元素——不是“有时共享、有时不”,而是“每次调用都严格按容量判断”。
为什么
后
没变?
因为
返回的是一个新的切片结构体(含新的
,可能指向新或旧数组),而 Go 是值传递:传入的是
这个三字段结构体的副本。函数内部或表达式中不赋值,原变量就永远停在旧状态。
是必须写的,不是可选习惯
写成
单独一行,等同于什么都没做
用
打印的是底层数组地址,不是切片变量地址;别靠这个判断“有没有扩容”
什么时候会意外覆盖数据?
典型场景是多个
共享同一个低容量切片,且都没更新变量本身。例如:
结果
和
都看到 [2],因为
覆盖了
底层数组第一个位置。
只要
,
就原地写入,不管之前有没有 append 过
多个子切片(如
,
)共用底层数组时,改一个会影响另一个
HTTP body 解析后取
存进 context,会“钉住”整个几 MB 的原始
如何安全提取或复制一段 slice?
目标是切断与原底层数组的指针关联,避免 GC 拖累或意外修改。
通用方案:
—— 利用
切片强制分配新数组
Go 1.21+ 推荐:
(
在
,1.21 后进标准库)
仅限
:
(Go 1.20+)
性能敏感路径:
—— 避免
内部的额外判断开销
扩容不是安全开关,cap 才是唯一依据
有人误以为“append 过一次就断连了”,其实完全错误。关键永远是当前切片的
和你要追加的元素总数。
→
仍复用原数组,
会立刻反映在新切片上
(即 cap=3)→
必扩容,新切片独立
不要依赖“看起来扩容过”,每次调用
前都要检查
最常被忽略的一点:
和
是两个独立控制的字段。你看到
,不代表这 10 个位置都“归你管”——它只说明底层数组还有多长,不提供任何安全边界或写保护。
appendcapappend(s, x)sappendlenss = append(s, x)append(s, x)fmt.Printf("%p", s)appendcommon := make([]int, 0, 4)
a := append(common, 1)
b := append(common, 2) // 这里直接复用 common 的底层数组,从索引 0 开始写!abbcommonlen + 新元素数 ≤ capappends[2:4]s[3:5]body[8:16][]bytenewS := append([]T(nil), oldS...)nilnewS := slices.Clone(oldS)slicesgolang.org/x/exp/slices[]bytenewB := bytes.Clone(oldB)dst := make([]T, len(src)); copy(dst, src)appendcapmake([]int, 3, 8)append(s, 1)s[0] = 99make([]int, 3)append(s, 1)appendcap(s) - len(s) >= nlencapcap=10