Go中需用T仅当函数必须修改指针变量自身的地址值(如BST根节点重绑定),而非其指向内容;多数场景传*T足矣,滥用T易致nil panic与理解成本上升。

Go 里用 **T 不是为了“炫技”,而是当你必须让函数修改「指针变量本身」的值(比如把它从指向 A 改成指向 B)时,才真正需要它。绝大多数场景下,传 *T 就够了;硬上 **T 反而容易引入 nil panic 和理解成本。
什么时候非得用 **T?
核心判断标准:函数要改的是「指针变量自己的地址值」,不是它指向的内容。
- 常见错误写法:
func updatePtr(p *int) { p = &newVal }——这只会改p这个形参副本,调用方的原指针没变 - 正确做法:调用方传
&p(即**int),函数内写*p = &newVal - 典型场景:BST 插入时重绑定根节点、模拟 C 的
realloc、动态切换结构体中某个字段的指针目标
**string 解引用前必须检查两层 nil
Go 不会跳过空指针,**pp 在任意一层为 nil 都会直接 panic。
- 错误写法:
**pp = "hello"(如果pp == nil或*pp == nil) - 安全写法:
if pp != nil && *pp != nil { **pp = "hello" } - 初始化别偷懒:
var s string = "a"; ps := &s; pps := &ps,而不是var pps **string后直接解引用
结构体字段指针该不该用 **Field 来改?
表面上“一行代码换掉字段指针”很干净,但代价常被低估。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 性能:多一次内存寻址,逃逸分析更难,易导致堆分配
- 可读性:调用方要理解
SetData(&thing.field)中的&是为了改谁的地址,嵌套深时极易混淆 - 更稳方案:函数返回新指针,由调用方显式赋值(
thing.field = newDataPtr());或用接口抽象行为,避免硬换指针
CGO 场景下传 **C.char 的生死线
C 函数写入的内存,生命周期必须由 C 侧管理;Go 侧只负责传地址、不负责释放。
- 常见崩溃:
C.free(unsafe.Pointer(*cstr))—— 如果*cstr指向的是 Go 分配的内存(比如C.CString返回的),就不能用C.free - 正确流程:用
C.malloc分配二级空间 → 传给 C 函数 → C 写入 → Go 侧用C.free释放(且仅限这次分配) - 关键原则:谁分配,谁释放;跨语言边界时,所有权必须清晰落在某一方
多级指针真正的复杂点不在语法,而在每一层地址背后隐含的所有权和生命周期责任——Go 的简洁性,恰恰藏在它不鼓励你轻易跨过这道线。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










