**T 是指向指针变量的类型,而非语法糖;它表示变量存储的是 *T 类型变量的地址,解引用需两次 * 且必须逐层判空,仅在需修改指针自身(如重定向或置 nil)时使用。

什么是 **T:它不是语法糖,而是“指向指针的变量”
Go 里没有 C 那种 ***int 风格的多级指针语法糖,**int 就是实实在在的一个类型:它表示“一个变量,这个变量存的是某个 *int 类型变量的地址”。换句话说,**int 指向的是指针本身,而不是间接地指向值。
-
*int→ 指向一个int值 -
**int→ 指向一个*int变量(比如你声明的var p *int) - 要拿到原始
int,必须写**pp,少一次*类型不匹配,多一次就 panic - 不能对字面量取地址再取地址:
&(&42)是非法的,因为42不可寻址
什么时候真需要 **T:只在必须修改“指针变量自身”时
Go 所有参数都是值传递,传 *T 进函数,你只能改它指向的值;想让调用方那个指针变量本身被重定向(比如从指向 A 改成指向 B,或置为 nil),就必须传 **T。
- 典型场景:惰性初始化、全局配置热替换、链表头节点更新、CGO 中接收 C 分配的内存(如
C.get_string(&cPtr)) - 错误写法:
func setToFive(p *int) { p = &five }→ 外部指针不变 - 正确写法:
func setToFive(pp **int) { *pp = &five }→ 调用时传&ptr,*pp就是原ptr变量 - 常见误用:用
**T管理多个不同指针,其实用[]*T+ 索引更清晰
安全访问 **T 的硬性步骤:逐层判 nil 是底线
Go 不做空指针防护,**pp 解引用前若任意一层为 nil,直接 panic:runtime error: invalid memory address or nil pointer dereference。这不是可选优化,是必须写的防御逻辑。
- 声明后不能直接用:
var pp **int→*pp必 panic - 初始化必须分步:
val := 42; p := &val; pp := &p - 解引用前必写:
if pp != nil && *pp != nil { use **pp },顺序不能颠倒(&&短路) - 调试技巧:打印每层值:
fmt.Printf("pp=%v, *pp=%v\n", pp, *pp),快速定位哪一层断了
超过两级就该停一停:***T 合法但危险,优先重构
***int 在语法上完全允许,但它意味着你要管理三层间接、三次解引用、三层 nil 判断。实际项目中几乎见不到合理使用它的场景。
立即学习“go语言免费学习笔记(深入)”;
- 三层判空成本陡增:
if ppp != nil && *ppp != nil && **ppp != nil - 一旦出现
***T,先问自己:能不能用结构体封装(如type IntRef struct { Ptr **int })、返回新指针、或用sync/atomic.Value安全替换? - 性能无优势:每次解引用都是一次内存加载,GC 压力略升
- CGO 或系统编程外,绝大多数业务代码里,
***T是设计信号——提示你抽象可能不够干净
多层指针的关键不在“会不会写”,而在于“有没有必要暴露指针变量的地址”。日常开发中,**T 已属边缘操作,***T 几乎就是重构触发器。真正容易被忽略的,不是语法,而是每一层 nil 的生命周期是否可控、谁负责初始化、谁负责释放——这些比星号个数更影响稳定性。










