go指针是显式契约,需严格遵循&和的作用边界:&只能用于已有变量左值,解引用前必须判nil,传指针核心为修改原值而非省拷贝,结构体方法用指针接收者因需改字段,接口实现时指针接收者要求*t满足接口。

Go 指针不是“绕不开的坎”,而是你写错一次就立刻暴露问题的显式契约——它不隐藏拷贝,不自动解引用,也不允许算术偏移。只要盯住 & 和 * 的作用边界,绝大多数指针困惑当场消失。
什么时候必须用 &?不是“想传地址”就加,而是看变量是否已存在
& 只能作用于**已有变量的左值**(即有确定内存地址的变量),不能对字面量、函数调用结果或表达式直接取地址。
- ✅ 正确:
x := 42; p := &x——x是变量,有地址 - ❌ 错误:
p := &42或p := &(len(s))—— 字面量和表达式无固定地址,编译报错cannot take the address of ... - ⚠️ 特殊情况:
slice元素可取地址(&s[0]合法),但map值不行(&m["k"]编译失败)——因为 map 值可能被重新分配,地址不保证稳定
*p 解引用前必须确认 p != nil
Go 不做空指针防护,nil 指针解引用会直接 panic:panic: runtime error: invalid memory address or nil pointer dereference。
- 函数入参是
*T类型时,调用方可能传nil,必须显式检查:if p == nil { return } - 结构体字段为指针类型(如
type User struct { Name *string }),JSON 反序列化后该字段可能为nil,访问前要判空:if u.Name != nil { fmt.Println(*u.Name) } -
new(T)返回非nil指针,但只初始化为零值;var p *T声明后默认是nil,二者语义不同,别混用
函数参数传 *T 而不是 T:关键在“改原值”而非“省拷贝”
很多人以为传指针只为避免大对象拷贝,其实更核心的动机是:**需要函数内部修改调用方变量的原始值**。
- 如果只是读取大结构体(如
func printUser(u User)),传值没问题——Go 对小结构体有优化,且不可变语义更清晰 - 真正需要指针的场景:
func updateUser(u *User)中执行u.Name = "Alice",此时外部User实例被修改 - 切片虽是引用类型,但底层数组指针+长度+容量三者打包传递;若需扩容并让调用方看到新底层数组,则必须传
*[]T(指针的指针),否则扩容操作仅影响副本
结构体方法用指针接收者,不是因为“大”,而是因为“要改字段”
值接收者(func (u User) SetName(n string))操作的是副本,改了也白改;指针接收者(func (u *User) SetName(n string))才能改原结构体字段。
- 即使结构体很小(比如只有两个
int),只要方法要修改字段,就必须用指针接收者 - 一致性原则:如果结构体任意一个方法用了指针接收者,其他方法也建议统一用指针接收者,避免调用方混淆(值实例无法调用指针接收者方法)
- 接口实现时,若类型用指针接收者实现接口,则只有
*T能满足该接口,T不能——这点极易被忽略,导致接口断言失败
最常被跳过的细节是:指针的生命周期完全由 Go 的逃逸分析决定,你写的 &x 可能被分配到堆上,也可能留在栈里——但这不影响语义,只影响 GC 压力。别试图“手动控制”,盯住 & 和 * 的合法性,比猜内存布局重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











