要修改原始结构体字段必须传指针,因Go默认值传递;切片可改元素但扩容不影响原变量;结构体超16–32字节考虑指针以避免拷贝开销;接口实现需匹配接收者类型;并发共享状态必须用指针。

函数内要修改原始结构体字段,必须传指针
Go 默认按值传递,传进去的是副本,改了也白改。比如 User 结构体里想改 Name,用 func updateUser(u User) 再赋值,外面完全没反应;只有写成 func updateUser(u *User),再解引用 u.Name = "new",才能真正生效。
- 基础类型(
int、string)同理:要改调用方的变量,必须传*int或*string - 切片是“引用类型”,但底层数组指针+长度+容量是按值复制的——能改元素,但扩容后重新赋值(如
s = append(s, x))不会影响原变量,这时要么返回新切片,要么传*[]T - 常见错误现象:
IncrementByValue(c Counter)调用后c.Value不变,而你以为它该变
结构体太大时,传指针是为了避免拷贝开销
每次传参都复制整个值。一个含 [1024]byte 和 map[string]string 的结构体,传一次就拷贝 1KB+,高频调用下 CPU 和内存压力明显。而 Point{X, Y int} 这种 16 字节以内的,传值反而更快更安全。
- 经验阈值:结构体超过 16–32 字节,就该考虑指针;但别机械套用,得看实际字段组成和调用频次
- 注意:指针本身也有成本(8 字节地址 + 间接寻址),小结构体传指针可能反而慢
- 性能陷阱:过度使用指针会增加 GC 压力,尤其当大量短期指针指向堆上小对象时
实现接口时,接收者类型决定能否赋值给接口变量
接口要求类型“实现全部方法”,而 Go 的方法集规则很严格:T 类型的方法集只含 func (T) M();*T 的方法集则包含 func (T) M() 和 func (*T) M()。所以如果你只写了 func (w *Writer) Write(),那 Writer 接口只能由 *Writer 满足,Writer{} 字面量直接传给 fmt.Fprint 就会报错。
- 典型报错:
cannot use w (type Writer) as type io.Writer in argument to fmt.Fprint: Writer does not implement io.Writer (Write method has pointer receiver) - 解决办法不是全改成指针接收者,而是统一设计:若结构体需被接口变量持有,优先用指针接收者定义方法
- 常见误操作:定义了
func (s Stringer) String()却想用fmt.Printf("%v", s)触发,结果不调用——因为fmt.Stringer接口要求的是String() string方法,而你只给了值接收者版本
并发读写共享状态时,指针是唯一可行路径
多个 goroutine 要操作同一份数据,必须共享内存地址。传值等于每人一份副本,sync.Mutex 锁的只是副本,完全无效;atomic.StoreInt64 也必须传 *int64 地址。
立即学习“go语言免费学习笔记(深入)”;
- 典型错误:
func f(m sync.Mutex) { m.Lock() }——锁的是参数副本,对原mu零影响 - 正确写法:
var mu sync.Mutex; mu.Lock(),或传*sync.Mutex(但通常直接用变量名调用更自然) - 热切换场景(如日志轮转):需用
*chan string动态替换通道,否则发送端无法感知新通道
指针不是银弹,也不是越早用越好。最容易被忽略的一点是:是否需要修改原值,永远排在大小判断之前。先问“我改它干嘛”,再看“它有多大”,最后才查“接口接不接得住”和“有没有并发”。否则很容易为一个小 Config 结构体加一堆 *,既无必要,又掩盖语义。










