Go语言指针的“高级”实为对基础机制的组合运用,关键风险点在于结构体字段访问、方法接收器自动取址规则、new与&差异、nil检查覆盖不全。

Go 语言里没有“高级指针”这个独立概念——所有指针操作都基于 & 和 *,所谓“高级”其实是对基础机制的组合运用和边界理解。真正容易出错、被忽略、影响稳定性的点,集中在指针与结构体、方法接收器、内存逃逸、nil 检查这四个层面。
结构体指针可以直接访问字段,但不是“指针解引用”
你写 testP.Num = 4 不需要写成 (*testP).Num = 4,这不是语法糖,而是 Go 的显式设计:结构体指针类型支持字段直接访问。编译器会自动插入解引用,等价于 (*testP).Num,但你不能对非结构体指针这么做(比如 *int 就不能写 ptr.Value)。
常见错误现象:
- 误以为
ptr.Field是通用语法,对*int或*string也这么写 → 编译报错cannot select from *int - 在嵌套结构体中混用值和指针,比如
s.Inner.Field和s.InnerPtr.Field行为不一致,但表面看不出来
关键判断:只有结构体(或接口)类型的指针才支持点号访问字段;其他类型指针必须显式 * 解引用后才能参与运算。
立即学习“go语言免费学习笔记(深入)”;
方法接收器是值还是指针,直接影响调用是否自动取地址
定义 func (v *MyStruct) Do() 后,你既能用 var s MyStruct; s.Do(),也能用 var sp = &s; sp.Do()。Go 编译器会在值类型调用时自动插入 &,前提是该值是可寻址的(比如变量、切片元素、结构体字段)。但以下情况不会自动转换:
-
MyStruct{}.Do()→ 编译失败:cannot call pointer method on MyStruct literal -
slice[0].Do(),而slice是不可寻址的(如函数返回的临时切片)→ 同样报错 - 接收器是值类型,但你想在方法内修改原结构体 → 修改无效,因为传的是副本
性能影响:值接收器会复制整个结构体;指针接收器只传 8 字节地址。结构体超过 32 字节,建议统一用指针接收器。
new() 和 & 变量的区别在于零值初始化与可寻址性
new(T) 返回一个指向新分配的、零值填充的 T 类型内存的 *T;&x 返回一个已有变量 x 的地址。二者看似都能得到指针,但行为差异明显:
-
p := new(int)→p指向堆上新分配的内存,初始值为0,且该内存可安全读写 -
x := 0; p := &x→p指向栈上变量x,生命周期由作用域决定 -
p := &0→ 编译错误:cannot take the address of 0,字面量不可寻址
容易踩的坑:用 new() 创建结构体指针后,字段仍为零值,需手动赋值;而 &MyStruct{Field: 1} 直接初始化并取地址,更常用也更清晰。
nil 指针 panic 不总发生在解引用那一刻
最典型的 panic 是 panic: runtime error: invalid memory address or nil pointer dereference,但它可能出现在看似无害的位置:
-
if p != nil && *p > 0→ 安全,短路求值保证不会解引用 nil -
if *p > 0 && p != nil→ 危险,先解引用就 panic,后面条件根本不会执行 -
fmt.Printf("%d", *p)→ panic,即使只是格式化输出 -
map[string]*MyStruct{"k": nil}; v := m["k"]; v.Method()→ 如果Method是指针接收器,调用时才会 panic
真正复杂的地方在于:nil 检查必须覆盖所有可能触发解引用的路径,包括方法调用、接口断言、channel 操作(如 close(*ch)),而不仅仅是 *p 这个表达式本身。


















