**int 是指向指针变量的类型,其值为 *int 变量的地址;仅当需修改指针变量自身(如重置为 nil 或重定向)时才使用,且必须逐层判空以防 panic。

什么是 **int?它不是“多级语法糖”,而是“指向指针的变量”
**int 是一个合法、明确的 Go 类型,但它不表示“更灵活的间接访问”,而是一个实实在在的变量——它的值是某个 *int 变量的内存地址。就像 *int 指向一个 int 变量一样,**int 指向一个 *int 变量。
- 你不能写
&&x(编译报错:cannot take the address of&x),因为&x是表达式,没有可寻址的存储位置 - 但你可以写
p := &x; pp := &p——p是变量,有地址,pp才能合法取它的地址 -
***int同理:它必须指向一个已声明的**int变量,而不是临时计算结果
什么时候真需要 **T?只在要修改“指针变量本身”时
Go 所有参数都是值传递。*T 参数只能让你改它指向的值;若想让函数改变调用方那个指针变量的指向(比如重置为 nil、指向新分配对象),就必须传 **T。
- 常见场景:
resetConfig(&cfg, newCfg)、prepend(&head, val)(链表头更新)、C.get_string(&cPtr)(CGO) - 错误写法:
func set(p *int) { p = &v }→ 外部指针不变,只是改了局部副本 - 正确写法:
func set(pp **int) { *pp = &v }→*pp就是调用方的p变量本身
为什么 **pp 解引用会 panic?空指针检查必须逐层做
Go 不做任何隐式空指针防护。**pp 安全执行的前提是:两层都非 nil。漏判任意一层,运行时就直接 panic: invalid memory address or nil pointer dereference。
- 声明后不能直接用:
var pp **int→*pp必 panic(pp是 nil) - 安全访问模式:
if pp != nil && *pp != nil { use **pp },顺序不能颠倒(短路求值) - 调试技巧:打印中间态,比如
fmt.Printf("pp=%v, *pp=%v\n", pp, *pp),快速定位哪一层断了
三层以上(***T)几乎不该出现,替代方案更清晰
***int 语法合法,但实际项目中极少必要。每多一层,可读性下降、空检查成本翻倍、生命周期更难追踪。
立即学习“go语言免费学习笔记(深入)”;
- 典型陷阱:
if ppp != nil && *ppp != nil && **ppp != nil—— 写错一层就崩溃,且难以维护 - 更 Go 的做法:
– 返回新指针:cfg = newConfig()(显式、无副作用)
– 封装结构体:type ConfigRef struct { ptr **Config }+ 方法封装
– 用sync/atomic.Value替换并发场景下的裸指针 - 如果代码里冒出
***T,先停下来问一句:是不是该用切片[]*T、map 或接口抽象了?
多级指针的核心不是“能写几层星号”,而是“是否真需要间接控制指针变量自身”。一旦开始嵌套两层以上,往往说明那块逻辑的职责边界已经模糊——这时候重构比硬写 ***T 更省时间。










