必须用 & 时:需函数修改原变量或避免大对象拷贝;不能用 & 时:对字面量、map值、未寻址返回值取地址会编译错误。核心是理解 & 取地址、 解引用,指针类型声明(t)与操作符分离。

什么时候必须用 &,什么时候用了就错
核心就一条:你想让函数改你原来的变量,就得传 &x;否则传 x 就够了。Go 默认所有参数都是拷贝——哪怕是个 1MB 的 struct,不加 & 进去,函数里改破天也影响不到外面。
常见错误现象:
-
cannot take the address of 42—— 对字面量、常量、map value(如&m["k"])、函数返回值(除非它本身可寻址)取地址,编译直接报错 -
invalid indirect of x (type int)—— 把普通变量当指针用,比如写了*x,但x根本不是*T类型 -
panic: runtime error: invalid memory address or nil pointer dereference—— 声明了var p *int没初始化,就直接写*p = 5
正确做法:
- 要修改原结构体字段?
updateScore(&student, 95) - 想避免大对象拷贝?传
*BigStruct,别传BigStruct - 初始化指针变量?用
p = new(int)或p = &x,别只声明不赋值
*T 是类型声明,* 是解引用操作——别混成一回事
很多人卡在语法上,以为 *int 里的 * 是“指针运算符”,其实它只是类型的一部分,就像 []string 里的 [] 不是“切片操作”一样。真正干活的是运行时的两个操作:& 取地址、* 解引用。
所以:
-
var p *int这行没任何内存访问行为,只是告诉编译器:“我这个变量将来存一个 int 的地址” -
p = &a才真去查a在哪,把那个地址塞进p -
*p = 100才真正跳到那个地址,把 100 写进去
典型坑:
- 写
var p *int; *p = 42——p是 nil,一解引用就 panic - 误以为
*p是“定义指针”,结果在函数签名里写错:func f(*int)是对的,但func f(* p int)语法错误
数组指针 *[N]T 和切片 []T 完全不是一回事
看到 *[3]int 别条件反射当成“C 风格数组指针”。它是 Go 里一种独立类型:指向**长度固定为 3 的 int 数组**的指针,长度 N 是类型的一部分,不能省,也不能和 *[5]int 互转。
使用场景很窄,基本只在以下情况值得考虑:
- 你要传一个很大的定长数组(比如
[1024]byte),又不想拷贝整块内存 - 你明确需要保留数组长度信息,且后续逻辑严格依赖
N
但要注意:
-
&arr中的arr必须是具名变量,&[3]int{1,2,3}在旧版本会报错,1.21+ 虽允许,但可能触发逃逸 - 解引用后是
[N]T,不是[]T,不能直接传给期望切片的函数(得手动转:myFunc(arr[:])) - 别和
*[]T搞混:*[]T是“指向切片头的指针”,底层是三级结构;*[N]T是二级结构,指向一块连续内存
原子指针 sync/atomic.Pointer 不是“更安全的指针”,而是“更危险的裸指针”
它本质是对 unsafe.Pointer 的原子封装,不提供类型安全、不参与 GC、不做生命周期检查。你用它,等于主动放弃 Go 的一部分内存安全保障。
必须做的几件事:
- 存之前必须显式转换:
p.Store(unsafe.Pointer(&x)),不能直接p.Store(&x) - 取出来必须先判空:
if ptr := p.Load(); ptr != nil { v := (*MyType)(ptr) },否则强转 nil 会 panic - 别缓存转换后的指针:下次读必须重新
Load()+ 转换,因为别的 goroutine 可能已经替换了它 - 如果指针关联状态(比如版本号、校验和),单靠
Pointer不够,还得加锁,否则有 ABA 问题
真正适合它的场景只有:读多写少、无锁优化关键路径、你清楚自己在干啥——比如配置热更新、状态机切换。日常业务代码里,99% 的情况用 sync.RWMutex 更稳妥。
最易被忽略的一点:Go 的 GC 不认 unsafe.Pointer,你存了个局部变量地址进去,函数返回后那块栈内存可能被回收,后面再 Load 出来就是悬垂指针——崩溃或静默读错数据,都不奇怪。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











