append不会修改原切片变量,因为Go中切片是值传递,传入的是包含Data、Len、Cap的结构体副本;即使共享底层数组,扩容后新数组和更新的字段仅作用于副本。

因为 append 返回的是新切片,而函数参数是值传递的切片头副本 —— 原变量没被重新赋值,自然不变。
为什么 append 不会修改原切片变量
Go 中所有函数参数都是值传递,[]T 也不例外:传入的是包含 Data(指针)、Len、Cap 的结构体副本。即使副本和原切片共享同一底层数组,append 内部一旦扩容,就会分配新数组、更新副本的 Data 和 Len,但这些变更只存在于副本里。
常见错误现象:
- 调用
append(s, x)后不赋值,如append(s, x)单独一行 → 原s完全没变 - 在函数内
s = append(s, x),但调用方没接收返回值 → 外部变量仍指向旧头 - 误以为“切片是引用类型所以自动更新”,忽略了 header 是被复制的
什么操作会影响原切片,什么不会
影响原切片的操作,本质是通过共享的 Data 指针写内存,且不改变 Len/Cap:
立即学习“go语言免费学习笔记(深入)”;
-
s[i] = v(索引赋值)→ 改的是底层数组,生效 -
copy(dst, src)→ 若dst和原切片共享底层数组,生效
不影响原切片的操作,本质是修改了副本的 header 字段:
-
s = append(s, x)→ 可能改Data和Len,仅作用于副本 -
s = s[1:]或s = s[:n]→ 只改副本的Len/Cap,原变量无感知 -
s = make([]int, 0, 10)→ 完全新建 header,彻底断开
如何让函数内的 append 生效到外部
必须显式把返回值赋给原变量,或用指针传参:
- 最常用:调用方自己赋值,如
s = append(s, x) - 函数内需修改外部变量时,接收
*[]T,并在函数内解引用赋值:*s = append(*s, x) - 若只需隔离数据不共享底层数组,用
slices.Clone(s)(Go 1.21+)或copy手动拷贝
示例:
func addOne(s []int) []int {
return append(s, 1)
}
// 调用方必须:
s = addOne(s) // ✅
// 而不是:
addOne(s) // ❌ s 不变
容易忽略的扩容临界点
是否触发扩容,直接决定底层数组是否被替换 —— 这是“有时变、有时不变”的关键:
- 初始
s := make([]int, 2, 2),再append(s, 3)→ 必扩容,新数组,原变量无感 - 初始
s := make([]int, 2, 4),再append(s, 3)→ 不扩容,共享数组,但原s的Len仍为 2,除非你把它赋回来
别依赖“刚好不扩容”来绕过赋值;append 的语义就是返回新切片,是否复用数组只是实现细节,不是契约。


















