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。
  • 记忆:传参永远按值,区别只在“值”是完整数据还是一个句柄。
笔记加载中…