
go 中 slice 虽含指针(指向底层数组),但本身是值类型:函数内对 slice 头部(len/cap/ptr)的修改(如 append)不会影响调用方,除非显式返回并重新赋值。
go 中 slice 虽含指针(指向底层数组),但本身是值类型:函数内对 slice 头部(len/cap/ptr)的修改(如 append)不会影响调用方,除非显式返回并重新赋值。
在 Go 中,slice 并非传统意义上的“引用类型”,而是一个包含三个字段的结构体:指向底层数组的指针(*array)、当前长度(len)和容量(cap)。当我们将 slice 作为参数传递给函数时,实际上传递的是该结构体的一个副本——即值传递(pass-by-value)。这意味着函数内部对 slice 本身的重新赋值(例如 people = append(...))只会修改这个副本,而不会影响调用方持有的原始 slice 变量。
以下代码直观体现了这一行为:
func main() {
people := make([]Person, 0)
fmt.Println(len(people)) // 输出: 0
getList(people) // 传入 slice 值副本
fmt.Println(len(people)) // 仍输出: 0 —— 主函数中的 people 未被改变
}
func getList(people []Person) {
people = append(people, Person{"Foo", "1"})
people = append(people, Person{"Bar", "2"})
// 此处的 people 是副本,其 len 和 ptr 可能已更新,
// 但这些变更不会回传到 main 中的变量
}
⚠️ 注意:虽然 slice 头部是值传递,但其指向的底层数组是共享的。因此,若函数内通过索引修改元素(如 people[0].name = "Updated"),调用方将看到该修改——因为操作的是同一块内存。但 append 不同:它可能触发底层数组扩容(分配新数组),并返回一个全新的 slice 结构体(新指针 + 新长度),原 slice 变量不受影响。
✅ 正确做法是让函数返回更新后的 slice,并由调用方显式接收:
func main() {
people := make([]Person, 0)
fmt.Println(len(people)) // 0
people = getList(people) // 接收返回值,覆盖原变量
fmt.Println(len(people)) // 2 —— 正确更新
}
func getList(people []Person) []Person {
people = append(people, Person{"Foo", "1"})
people = append(people, Person{"Bar", "2"})
return people // 必须返回,调用方需重新赋值
}
? 总结:
- Slice 是值类型,传递的是结构体副本;
- append 返回新 slice,不修改原变量;
- 若需扩展 slice 并反馈给调用方,必须采用「返回 + 重新赋值」模式;
- 真正的“引用语义”仅体现在对底层数组元素的直接读写上,而非 slice 头部元数据的变更。











