本节摘要:Go 保留指针但砍掉指针运算,用取地址符与解引用符完成"间接指向"。本节讲清值传递的本质、指针与函数参数/数组/结构体/切片/map 的组合行为、new 与取地址的等价性、二级指针的使用边界,以及 unsafe 包打开的底层通道与代价——为第 6 章的 unsafe 深潜做铺垫。
阅读完本节,你应当能够:
函数调用永远拷贝实参。指针的价值在于:拷贝一份地址,比拷贝整个结构体便宜得多,而且顺着地址能改到原件。
func swap(a, b *int) { *a, *b = *b, *a // 解引用,改的是调用方的变量 } x, y := 1, 2 swap(&x, &y) // 取地址传入
三个符号各司其职:& 取地址,* 在类型位置表"指向某类型的指针",在表达式位置表解引用。没有 C 的算术(不能 p+1)、没有 ->(自动透过指针访问字段,p.Name 直接可用),安全性大幅提升。
type User struct{ Name string; Age int } u := User{Name: "Ada", Age: 36} p := &u p.Age = 37 // 等效 (*p).Age,语法糖
| 类型 | 传值给函数后 | 想改原件要用 |
|---|---|---|
| 基本类型 int 等 | 拷贝,改不到原件 | *int 参数 |
| 数组 | 整块拷贝 | *数组 或改用切片 |
| 结构体 | 字段全拷贝 | *结构体(最常用) |
| 切片 | 拷贝三元组头,底层数据共享 | 通常不需要;要改 len 才传 *切片 |
| map | 拷贝头指针,桶共享 | 通常不需要 |
| 指针本身 | 拷贝地址值 | **T(二级指针,罕见) |
切片与 map 因为头里带指针,天然"改内容生效、改头不生效"。由此有个经典坑:函数内向 nil 切片 append 后返回,调用方却没接返回值——头是拷贝的,新头到不了调用方。
二级指针几乎只出现在"让函数初始化调用方的指针变量"这类罕见需求,或某些 C 互操作场景,业务代码见到它多半是设计问题。
new(T) 分配零值并返回指针,var p *T 加赋值、或直接 &T{} 结构体字面量取地址,效果相近。现代 Go 代码里结构体一律 &User{...},new 主要服务非结构体类型与泛型分配。指针零值是 nil,解引用 nil 会 panic——Go 没有"野指针悬垂"的常规来源(GC 保证对象活着就有指针有效性),内存安全的最大敌人只剩 nil 本身。
func newUser() *User { u := User{Name: "局部变量"} return &u // 合法!GC接手,不会悬垂 }
从 C 来的人会本能觉得"返回局部变量地址"危险。Go 里编译器的逃逸分析发现 u 被带出函数,就把他分配到堆上。代价是堆分配加 GC 压力——高频小对象场景,值语义反而是性能优化。第 6 章工具链会展示如何用编译器选项观察逃逸决策。
正常指针不能转换类型(*int 不能转 *string),但经 unsafe.Pointer 这座桥可以。它是与任何指针双向转换的特殊类型,再配合 uintptr 做数值地址。三步链条:
var i int = 10 p := &i up := unsafe.Pointer(p) // 任意指针 → unsafe.Pointer num := uintptr(up) // → 数值地址(仅瞬时使用) _ = num
unsafe 包同时提供四个"只读探针":Sizeof 量类型字节数(如 64 位平台上 int 是 8、string 头是 16,因为含指针加长度两个字)、Alignof 量对齐、Offsetof 量字段偏移;新版本还加了 Add 与 Slice 等受限工具。这些是第 6 章 unsafe 专节的主角,这里只立一个观念:
⚠️ 常见坑:uintptr 只是整数,GC 不认它。把地址长期存在 uintptr 里、期间对象可能被搬走或回收,再转回指针访问就是未定义行为。规则是"指针到数值、用完立刻回来",中间不许藏变量。
反过来,并发共享的可变指针是数据竞争的温床——指针给了你"改原件"的能力,也就给了你"同时改原件"的能力。第 5 章整章都在讲怎么管住这件事。
💡 关键直觉:把指针当成"遥控器"而不是"地址"——遥控器可以复制给很多人,但被遥控的电视只有一台;谁都能按,同时按就打架。
补几个判断问答。方法接收者与函数参数的指针选择要一致吗? 尽量一致(第 3.3 节的方法集规则),混用会在装接口时咬人。指针池化优化值得吗? 先用第 6 章的剖析证明分配真是热点再谈,否则是负优化。& 取的地址什么时候失效? 永不"悬垂"——GC 保证可达对象不动声色地续命;你能犯的错只有 nil 解引用。两个指针能比较吗? 能,== 判"是否指向同一对象";结构体指针比内容要解引用逐字段比(或用反射的深度相等,见第 6 章)。
下一节把行为挂到类型上:方法与接口。