会,但只限于通过指针写内存的操作;append、s = s[1:]、重新赋值等修改切片头的行为,不会影响原变量。因为切片是值传递,拷贝的是包含指针、len、cap的头部结构,s[i] = v 直接通过指针修改底层数组,未扩容时所有共享该数组的切片均可见变更,而append返回新切片头,不赋值则原变量不变。

会,但只限于通过指针写内存的操作;append、s = s[1:]、重新赋值等修改切片头的行为,不会影响原变量。
为什么 s[i] = v 会改到原数据
因为切片头里存着一个指向底层数组的 unsafe.Pointer,s[i] 实际是用这个指针算出地址后直接写内存。只要没扩容,所有共享该底层数组的切片(包括原变量、函数参数副本、s[1:] 等)看到的是同一块内存。
- 常见错误现象:
modify(s); fmt.Println(s)打印出被改过的值,误以为“切片是引用传递” - 本质不是引用传递,而是多个切片头共用一个数组指针
- 这种修改安全的前提是:不触发扩容、不改变
len/cap字段
append 为什么经常失效
append 返回新切片,但函数内不赋值给原变量,调用方就收不到变更。更关键的是:是否扩容决定了底层数组是否被替换。
- 常见错误现象:
func f(s []int) { append(s, 1) }→ 原s完全不变 - 即使没扩容,
append也只更新副本的len,原变量的len仍为旧值 - 扩容时
append分配新数组,副本指向新地址,原变量和新切片彻底断开 - 验证方式:
fmt.Printf("%p", &s[0]),append前后地址变了就说明扩容了
想让函数内修改生效,必须显式返回或传指针
Go 没有隐式引用语义,所有“让外部变量变”的操作都得靠程序员主动控制。
立即学习“go语言免费学习笔记(深入)”;
- 推荐做法:函数返回新切片,调用方自己赋值,如
s = add(s, x) - 需要原地修改结构体字段时,用
*[]T:函数内写*s = append(*s, x) - 别用
slices.Clone(s)(Go 1.21+)来“预防性拷贝”,除非你明确要隔离底层数组 - 加密、序列化等敏感场景,入参前先做深拷贝:
srcCopy := append([]byte(nil), src...)
结构体字段含切片时最容易翻车
比如 type Config { Data []byte },多个 Config 实例若共用同一底层数组,一个实例调用 append(c.Data, ...) 可能意外改到另一个实例的 Data。
- 并发下更危险:多个 goroutine 同时写共享底层数组 → 数据竞争
- 修复原则:凡是要修改结构体里的切片字段,先深拷贝再操作
- 避免
c.Data = c.Data[:0]这类截取操作——它只改len,底层数组还是共享的 - 最常被忽略的一点:切片的“值类型”标签只管传递方式,真正决定能否改原数据的,是你动的是哪块内存


















