切片值传递包含指向底层数组的指针,故元素修改生效;但append等操作需返回新切片或传*[]T才能更新调用方变量,核心在于区分数据修改与描述符更新。

Go 中切片本身不是指针,但内部**必然携带一个指向底层数组的指针**;你不需要显式用 *[]T 就能修改元素,但想让函数内的 append 影响调用方变量时,才真正需要它。
为什么传 []int 就能改原数组,却常误以为要传 *[]int
因为切片值传递的是“头信息副本”(含指针、len、cap),其中的指针字段仍指向原底层数组。只要不重新赋值切片变量(比如不扩容、不 make 新切片),所有元素修改都直接落在同一块内存上。
-
s[0] = 42✅ 生效:改的是底层数组第 0 个位置 -
s = append(s, 99)❌ 不影响调用方:只改了副本的头信息,原变量仍指向旧地址 - 误判根源:把“能改数据”等同于“能改切片变量本身”,混淆了数据层和描述符层
什么时候必须用 *[]T?只有一种真实场景
仅当函数内部执行 append(或其他操作)导致底层数组重分配,且你**要求调用方的切片变量立刻指向新地址**时,才需传指针并解引用赋值。
- 典型模式:
func push(s *[]int, x int) { *s = append(*s, x) } - 不传指针的替代写法更推荐:
s = appendAndReturn(s, x)—— 显式接收返回值,语义清晰无副作用 - 注意:
*[]T是“指向切片结构体的指针”,不是“指向底层数组的指针”;别写成**int
共享底层数组带来的坑,比指针问题更常见
多个切片(如 s1 := arr[1:3]、s2 := arr[2:4])通过内部指针共享同一段内存,修改 s1[1] 会悄悄改掉 s2[0],还可能让大数组因小切片长期存活而无法被 GC 回收。
立即学习“go语言免费学习笔记(深入)”;
- 判断是否真共享:用
unsafe.Pointer(&s[0])比对起始地址 - 安全切断关联:
copy(dst, src)或append([]T(nil), src...) - 初始化习惯:
var s []int(nil 切片)比s := []int{}(空切片)更明确,且两者都支持len(s)==0判空
真正容易被忽略的,是“切片共享”这个隐式行为——它不报错、不警告,只在某个深夜调试时突然让你发现用户数据被另一个无关模块改掉了。比起纠结要不要加星号,先看一眼 cap 和 unsafe.Pointer(&s[0]) 更实在。










