Go切片是值类型,其变量包含指向底层数组的指针、len和cap三个字段;赋值时复制这24字节,故s1和s2共享底层数组,修改内容会相互影响,但扩容后指针分离,互不影响。

Go 切片变量本身是值类型
声明一个 []int 变量时,它在栈上占据固定大小(通常 24 字节:指向底层数组的指针 + 长度 + 容量),赋值或传参时复制的是这 24 字节,不是整个底层数组。所以 s1 := []int{1,2,3}; s2 := s1 后修改 s2[0] = 99,s1[0] 也会变——这不是因为 s1 和 s2 是“同一个引用”,而是因为它们的指针字段指向同一块内存。
常见误解:看到“修改切片内容影响原切片”就认为它是引用类型。其实这是值类型携带指针导致的副作用。
切片底层结构包含三个字段
Go 运行时中,切片头(slice header)定义为:
type slice struct {
array unsafe.Pointer
len int
cap int
}
关键点:
立即学习“go语言免费学习笔记(深入)”;
-
array是指向底层数组首地址的指针,不持有数组所有权 -
len是当前可读写长度,cap是从array起始处开始的最大可用长度 - 对切片做
s = s[1:]或s = append(s, x),可能改变len/cap,也可能触发扩容(分配新数组并拷贝) - 一旦扩容发生,新切片的
array指针就和旧切片不同了,后续修改互不影响
为什么 append 后原切片可能不受影响
当 append 不触发扩容时(即 len ),新切片仍共享底层数组;一旦扩容(len == cap 且追加元素),运行时分配新数组,拷贝原数据,此时新切片与原切片完全独立。
示例:
orig := []int{1, 2}
a := orig
b := append(orig, 3) // 触发扩容 → b.array ≠ orig.array
orig[0] = 99
fmt.Println(orig) // [99 2]
fmt.Println(a) // [99 2]
fmt.Println(b) // [1 2 3] —— 不受影响
容易踩的坑:
- 把切片作为函数参数传入,函数内
append后没返回,调用方看不到新增元素(因为扩容后指针已变) - 误以为
copy(dst, src)会自动扩容dst,实际只拷贝 min(len(dst), len(src)) 个元素,超出部分被丢弃
判断是否共享底层数组的可靠方式
不能靠打印切片值(fmt.Printf("%v", s))来判断,要对比底层指针:
func sameArray(a, b []int) bool {
return len(a) > 0 && len(b) > 0 && &a[0] == &b[0]
}
注意:&a[0] 在 len(a)==0 时 panic,需先判空;另外,即使 &a[0] == &b[0],也不代表整个数组都重叠——比如 a := s[0:2]; b := s[1:3],它们起始地址不同,但仍有重叠。
真正复杂的地方在于:切片行为既不像纯值类型(如 int)那样完全隔离,也不像 Java 的 List 那样有明确的引用标识。它的“半共享”特性必须结合 len/cap 和底层数组生命周期一起理解,稍不留神就会在并发或长期持有切片时引发意外修改。










