必须用指针传参,因Go所有参数均为值传递,传结构体仅拷贝副本,修改无效;正确写法是func update(u *User) { u.Name = "Bob" }并调用&user。

函数内修改外部变量必须用指针传参
Go 所有参数都是值传递,想让函数修改调用方的变量,唯一办法是传入该变量的地址。常见错误是以为传结构体就能改字段,结果只是拷贝了一份——原变量完全不受影响。
- 正确写法:
func updateName(u *User) { u.Name = "Bob" },调用时传&user - 错误写法:
func updateName(u User) { u.Name = "Bob" },u是副本,改了也没用 - 注意:
*int、*string这类基础类型指针也适用,但别为了改一个int就传指针——小值拷贝开销远小于解引用成本
大结构体传指针能省多少内存拷贝
结构体字段超过 4–5 个,或含 []byte、map、chan 等 header 类型时,值传递会复制整个结构体头(比如 []byte 的 24 字节 header),而指针只传 8 字节地址。实测 1KB 结构体在循环中值传参比指针慢约 1.8×。
- 典型高开销结构体:
type User struct { ID int; Name string; Email string; Avatar []byte },其中Avatar占几 MB,但 header 仍要复制 - 切片、map、chan 本身已是轻量 header,
func process(data []int)就够了,func process(data *[]int)反而容易触发逃逸 - 用
go build -gcflags="-m -l"检查:若输出moves to heap,说明值传递已导致堆分配,指针未必能救——得先看逃逸根源
方法接收者该用值还是指针
不是“所有方法都要用指针”,而是看是否满足以下任一条件:修改字段、结构体过大、含不可拷贝字段(如 sync.Mutex)、或实现高频接口(如 io.Reader)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须用指针:
func (u *User) Save() { u.LastSaved = time.Now() }(改字段);func (m *sync.Mutex) Lock()(sync.Mutex不可拷贝) - 建议用指针:
func (u *User) GetName() string(User大于 32 字节);否则每次接口赋值都拷贝整块内存 - 该用值:
func (p Point) Distance(q Point) float64(struct{ x, y int }仅 16 字节,值传参更快)
channel 传指针要注意生命周期和并发安全
channel 可以传 *User,但接收方拿到的指针若指向栈变量(如函数局部变量取地址后发出去),goroutine 还没执行完,栈帧已销毁,就成了悬空指针。
立即学习“go语言免费学习笔记(深入)”;
- 安全做法:确保指针指向堆分配对象(如
u := &User{...}或new(User)),或用sync.Pool复用 - 并发读写同一指针指向的数据,必须加锁(
sync.Mutex)或用atomic.Value,不能依赖 channel 自带同步 - 避免裸指针暴露:更推荐封装命令结构体 + channel,例如
type UpdateCmd struct { User *User; Op string },由单一 goroutine 负责执行
go build -gcflags="-m -l" 看逃逸,再决定动不动指针。

















