
在 go 中,对指针方法接收器使用 s = &t{...} 仅修改局部指针变量,不影响原值;而 *s = t{...} 才真正修改调用方所指向的内存内容。理解二者差异是避免“看似赋值却无效”问题的关键。
在 go 中,对指针方法接收器使用 s = &t{...} 仅修改局部指针变量,不影响原值;而 *s = t{...} 才真正修改调用方所指向的内存内容。理解二者差异是避免“看似赋值却无效”问题的关键。
在 Go 的方法定义中,func (s *student) update() 表示该方法接收一个指向 student 的指针——这等价于传入一个 *student 类型的参数。关键在于:指针本身是按值传递的,即方法内部的 s 是原始指针的一个副本,它和调用方持有的指针指向同一地址,但变量本身独立存在。
因此,两种赋值操作语义截然不同:
✅ *s = student{"unknown", 0}
对指针解引用后赋值,即把新结构体值写入 s 所指向的内存地址。由于该地址正是 main 中 s 变量的底层数值存储位置,因此外部结构体内容被真实修改。❌ s = &student{"unknown", 0}
这是将一个新的地址(指向堆上新分配的 student)赋给局部变量 s。原调用方的指针变量未被触碰,其仍指向原来的栈上结构体({"hongseok", 13}),所以打印结果不变。
下面通过完整示例对比验证:
type student struct {
name string
age int
}
func (s *student) updateByReassign() {
s = &student{"unknown", 0} // ❌ 仅改变局部指针
}
func (s *student) updateByDeref() {
*s = student{"unknown", 0} // ✅ 修改指针所指内存
}
func main() {
s := student{"hongseok", 13}
fmt.Printf("before reassign: %+v\n", s) // {name:"hongseok" age:13}
s.updateByReassign()
fmt.Printf("after reassign: %+v\n", s) // {name:"hongseok" age:13} —— 无变化
s.updateByDeref()
fmt.Printf("after deref: %+v\n", s) // {name:"unknown" age:0} —— 成功更新
}
⚠️ 注意事项:
- 若需完全替换整个结构体(尤其含大量字段或不可变字段时),*s = T{...} 是最直接、高效且符合 Go 惯用法的方式;
- s = &T{...} 仅在需要重新绑定指针目标(如切换到另一对象)且该变更需被调用方感知时才有意义——但这要求显式返回新指针并由调用方重新赋值,例如 s = s.updateWithNewPtr();
- 不要混淆“修改指针值”与“修改指针指向的值”,这是 Go 值语义的核心体现,也是初学者常见误区。
总结:指针方法接收器赋予你修改原始数据的能力,但前提是*操作解引用后的值(`s)**,而非覆盖指针本身(s`)。掌握这一区别,才能写出可预测、可维护的 Go 代码。











