Go支持任意层级指针,**T用于修改指针变量本身(如函数内改变调用方的*T指向)或Cgo交互等不可替代场景;但需警惕nil解引用panic、初始化陷阱及可读性下降,多数情况应优先选用结构体、切片或接口。

Go 语言支持多级指针,*T 可以再取地址变成 **T
可以,而且语法完全合法。Go 不限制指针层级,*int 的变量本身有内存地址,自然能被另一个 **int 指向。这不是“模拟”或“语法糖”,而是真实存在的地址嵌套。
但要注意:每多一层,就要多一次解引用(*),也多一分空指针风险和可读性损耗。
-
var x int = 42→p := &x(p类型是*int) -
pp := &p(pp类型是**int),此时**pp == 42 - 若
p是nil,对pp解引用一次得到nil,再解引用就会 panic:panic: runtime error: invalid memory address or nil pointer dereference
什么时候真需要 **T?不是为了炫技
多层指针在 Go 中不常见,因为多数场景用结构体字段、切片或接口更清晰。但以下情况它不可替代:
- 需要修改一个指针变量本身的值(比如让函数内改变调用方的
*T指向新地址)——必须传**T - Cgo 交互中对接 C 函数要求二级指针(如
char **argv或某些输出参数) - 某些底层数据结构实现(如跳表节点的多级指针数组,但 Go 标准库一般用切片代替)
- 避免拷贝大结构体时,又需动态切换指向目标(不过这时常该考虑
sync.Pool或重设计)
典型反例:用 **string 去“间接修改字符串内容”是错的——string 本身不可变,且 *string 已足够改其指向;多加一层纯属增加复杂度。
立即学习“go语言免费学习笔记(深入)”;
**T 和 []*T 容易混淆,但语义完全不同
新手常把“多个指针”和“指针的指针”搞混。它们解决的问题不同:
-
arr := []*int{&a, &b, &c}:一个切片,存了三个独立的*int,各自指向不同地址 -
pp := &p(其中p *int):只有一个**int,它只指向p这一个指针变量的地址 - 修改
arr[0]只影响切片第一个元素;修改**pp会真正改变p所指向的int值;而修改*pp(即p)才能让p指向别处
性能上,**T 解引用两次,[]*T 是一次索引加一次解引用,访问成本不同;内存布局也完全不同——前者是链式地址,后者是连续指针数组。
实际写 **T 时最常踩的坑
不是语法不会写,而是逻辑绕晕后触发 panic 或行为不符合预期:
- 忘记检查中间层是否为
nil:if pp != nil && *pp != nil { ... }缺一不可 - 误以为
new(**T)初始化了两级 —— 实际只分配**T空间,其值是nil,*pp仍是nil,不能直接**pp = 123 - 在方法接收者中用
**T:容易让调用方困惑谁负责管理内存,也违背 Go “接收者尽量扁平”的惯用法 - 与 JSON 或 Gob 编码配合时,
**T不会被自动解包;json.Unmarshal对**int默认不做深层赋值,常需自定义UnmarshalJSON
多一层指针,就多一层间接性和责任边界。Go 的简洁性恰恰来自克制使用这种表达——能用 *T 解决的,就别升到 **T;能用结构体字段的,就别靠指针链穿来穿去。










