该用 *T 而不是 T 的核心依据是「语义是否需要共享可变状态」:需修改入参、字段含切片/map/大数组、或类型不可拷贝(如 sync.Mutex)时才用指针,否则默认传值更安全易推理。

什么时候该用 *T 而不是 T?
核心判断依据不是“性能”,而是「语义是否需要共享可变状态」。如果函数要修改入参、或结构体字段较大(比如含切片、map、大数组)、或类型本身不允许拷贝(如 sync.Mutex),才考虑指针。否则默认传值更安全、更易推理。
-
sync.Mutex必须取地址:直接传值会复制锁,失去互斥意义 - 结构体字段含
[]byte或map[string]int时,传指针避免底层数据重复分配 - 函数签名暴露
*T意味着调用方需承担「该值可能被修改」的责任,这是契约的一部分
哪些常见类型传指针反而有害?
小而固定大小的类型(如 int、bool、string、time.Time)传指针通常没收益,还增加 nil 风险和 GC 压力。尤其是 string —— 它本身是只读 header(16 字节),传值开销极小,但传 *string 后必须检查是否为 nil,且无法保证底层字节数组不被意外修改(虽然 string 不可变,但指针解引用后仍可能引发误用)。
-
string、int64、error(接口,但多数实现很小)优先传值 - 返回
*T时,若T是小类型,不如直接返回T+error - 方法接收者用
*T仅当方法需修改字段;否则T更清晰
如何快速识别指针滥用?
看代码里是否频繁出现 nil 检查、if p == nil、panic("nil pointer dereference") 这类防御逻辑。这往往说明本该用值语义的地方强行用了指针,把控制流复杂度推给了调用方。
- 构造函数返回
*T却没做非空校验 → 调用方极易 panic - 参数是
*[]int或**string→ 几乎总是设计错误,应重构为返回新值或使用切片/接口 - HTTP handler 中把
*http.Request当普通参数传(实际框架已保证非 nil)→ 多余且误导
func process(data *string) { // ❌ 无必要
if data == nil {
return
}
// ...
}
func process(data string) { // ✅ 更直接,调用方无需担心 nil
// ...
}
真正难的是权衡「语义清晰性」和「运行时成本」——多数 Go 项目卡点不在指针与否,而在是否让调用方一眼看出谁拥有数据、谁负责生命周期、哪些操作是副作用。把这点想清楚了,指针只是自然结果,不是优化手段。
立即学习“go语言免费学习笔记(深入)”;










