传指针更省内存,因只拷贝8字节指针而非整个结构体;可修改原数据、明确函数副作用,但需防nil解引用和逃逸导致堆分配。

为什么传指针比传值更省内存
Go 中函数参数是值传递,哪怕传的是结构体,也会完整拷贝一份。如果结构体很大(比如含切片、map 或大量字段),拷贝开销明显。传 *T 只拷贝一个指针(通常 8 字节),不管 T 多大,成本恒定。
常见错误现象:func processUser(u User) { u.Name = "modified" } 修改无效——因为改的是副本;而 func processUser(u *User) { u.Name = "modified" } 能生效,且无额外内存分配。
- 结构体超过 4–8 个字段时,建议优先考虑指针传参
- 含
[]byte、map[string]int、sync.Mutex等不可复制字段的类型,必须用指针(否则编译报错cannot use ... as type ... in argument to ... (invalid memory address or nil pointer dereference)) - 小结构体(如两个
int)传值反而更快——避免解引用和缓存未命中,别盲目全用指针
指针参数如何影响函数的可修改性语义
函数签名中出现 *T 是一种明确的契约:该函数可能修改调用方的数据。这比靠文档或命名(如 UpdateUser)更可靠,也方便静态分析工具识别副作用。
使用场景:
立即学习“go语言免费学习笔记(深入)”;
- 需要就地更新状态(如
json.Unmarshal必须传*T) - 实现接口方法时,接收者为指针才能修改字段(如
func (u *User) SetID(id int) { u.ID = id }) - 避免意外修改:若函数只读,应传
T或interface{},而非*T——否则调用方无法从签名判断是否安全
nil 指针风险与防御式写法
传 *T 带来便利,但也引入 nil 风险。一旦函数内直接解引用未判空的指针,运行时报错 panic: runtime error: invalid memory address or nil pointer dereference。
实操建议:
- 函数开头加
if u == nil { return errors.New("user cannot be nil") }或直接panic(仅限内部工具函数) - 对可选参数,用指针 + 明确默认逻辑,例如:
func DoThing(opt *Options) { if opt == nil { opt = &Options{Timeout: 30} } } - 不要依赖调用方保证非 nil——尤其跨包调用时,文档和类型系统都不够,必须代码校验
逃逸分析与堆分配的实际影响
传指针不等于变量一定分配在堆上,但会显著提高逃逸概率。Go 编译器通过逃逸分析决定变量位置:如果函数返回了指向参数的指针,或将其存入全局变量、channel、map,该变量就会逃逸到堆。
性能提示:
- 用
go build -gcflags="-m" main.go查看逃逸情况,关注类似... escapes to heap的输出 - 频繁创建小对象并传指针,可能加剧 GC 压力;此时权衡:传值 + 栈分配,可能比传指针 + 堆分配更优
- 切片本身是 header(含指针、长度、容量),传
[]int已经是“轻量级指针语义”,通常无需再传*[]int
真正要小心的不是“用不用指针”,而是“这个指针背后的数据生命周期是否可控”。很多性能问题源于没看清数据到底落在哪,而不是指针本身。











