Go 中值类型与引用类型有什么区别?
结论先行
Go 没有官方的“引用类型”分类,一切赋值与传参在语义上都是值拷贝;只是 slice、map、channel、interface、func 这类类型拷贝的是“句柄”(指向底层数据的描述符或指针),底层数据仍只有一份,修改会对原变量可见,因此大家习惯称它们为引用类型。
要点
- 值类型:int、float、bool、string、array、struct,拷贝即复制整块数据,副本与原件互不影响。
- 引用类型:slice、map、channel、interface、func、各类指针,拷贝的是句柄,改的是同一份底层数据。
- slice 的句柄是 3 个字段(data 指针、len、cap);map 与 chan 的句柄是指向 hmap/hchan 的指针。
- nil 句柄并非完全不可用:nil slice 可以 append,nil map 只能读(返回零值)不能写,nil chan 收发都会永久阻塞。
- struct 内含 slice/map 时,结构体的拷贝仍是浅拷贝,不会深拷贝底层数组。
- 是否“共享”,取决于操作改的是句柄本身(append 触发扩容、整体重新赋值),还是句柄指向的底层元素。
- slice/map 之间不能用 == 直接比较(只能与 nil 比),要比较内容得自己写循环或反射。
- 想验证共享:打印两个变量的 &s[0] 或观察 cap 变化,能直观看到底层数组是否更换。
示例
func main() {
m := map[string]int{"a": 1}
f(m) // 拷贝的是指向 hmap 的指针
fmt.Println(m["a"]) // 2:底层数据被 f 改掉
arr := [2]int{1, 2} // 数组是值类型
g(arr)
fmt.Println(arr) // 仍是 [1 2]
}
func f(x map[string]int) { x["a"] = 2 }
func g(x [2]int) { x[0] = 99 }
常见追问/记忆点
- 追问:函数里 append 后外层的 len 为什么不变?答:len/cap 是拷贝的,要生效必须返回新 slice 并重新赋值。
- 追问:什么操作会让两个 slice 从共享变为独立?答:触发扩容换新数组,或对变量重新赋值指向另一块数组。
- 追问:何时必须用指针传参?答:需要修改调用方数据,或想避免复制很大的 struct。
- 记忆:传参永远按值,区别只在“值”是完整数据还是一个句柄。