Go默认按值传递,函数内修改结构体字段只作用于副本,原对象不变;必须传指针(&myStruct)并声明参数为*MyStruct,才能通过ptr.Field修改原始内存。

为什么修改结构体字段时值没变
传值调用导致结构体副本被修改,原对象不受影响。Go 默认按值传递,哪怕传的是结构体变量本身,函数内对字段的赋值只作用于副本。
解决方式是传指针:&myStruct,函数参数声明为 *MyStruct。这时函数内通过 ptr.Field = newValue 修改的才是原始内存地址上的数据。
- 不改指针本身(如
ptr = &another)不会影响调用方;但解引用后改字段(ptr.Field)会生效 - 如果结构体很大,传指针还能避免拷贝开销,这是附带收益,不是主要动机
- 空指针传入后直接解引用会 panic:
panic: runtime error: invalid memory address or nil pointer dereference,必须先判空
方法接收者该用值还是指针
只要方法需要修改接收者状态,就必须用指针接收者。值接收者的方法永远无法改变原始实例。
例如定义 func (u User) SetName(n string),调用 u.SetName("Alice") 后 u.Name 仍是旧值;而 func (u *User) SetName(n string) 才能真正更新。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 即使方法不修改字段,若结构体较大,也建议用指针接收者减少拷贝
- 同一类型混用值和指针接收者会导致方法集不一致:值类型变量无法调用指针接收者方法(除非取地址),反之亦然
- 接口实现要求严格匹配:某接口方法是
*T接收者,则只有*T类型能实现它,T不行
nil 指针调用方法时为什么有时不 panic
只要方法内部不访问 nil 指针所指向的字段或方法,就不会崩溃。Go 允许对 nil 指针调用方法——这本质是语法糖,等价于显式传参 nil 给第一个隐式参数。
常见安全模式是把指针判空逻辑放在方法开头:
func (u *User) GetName() string {
if u == nil {
return ""
}
return u.Name
}
-
u.GetName()在u为nil时不会 panic,前提是方法体没写u.Name这类解引用操作 - 数据库查询返回
*User时可能为nil,直接链式调用user.GetName()是安全的,但user.Name就会崩 - 这种设计让“零值友好”的 API 成为可能,比如
fmt.Printf("%v", (*User)(nil))能正常输出<nil></nil>
切片、map、channel 本身已是引用类型,还用指针吗
不用。它们底层是包含指针的结构体(如 slice 是 {*array, len, cap}),传值时复制的是这个结构体,但其中的 *array 仍指向原底层数组。因此修改元素(s[0] = x)、追加(append 未扩容时)、删除 map 键等操作,原变量可见变化。
- 只有想替换整个底层数组(如扩容后需让调用方拿到新 slice 头)或重置 map/channel 变量本身(
m = make(map[string]int))时,才需要传*[]T或*map[K]V - 误传
*[]int并在函数内做*s = append(*s, 1)是常见错误:append 可能分配新底层数组,但调用方看到的仍是旧 slice 头——除非你明确想覆盖原变量 - 简单原则:改内容 → 传值;改变量绑定的目标(数组头、map header、chan header)→ 传指针
最易忽略的点是:指针语义是否真正必要。很多场景下用返回值替代状态修改更清晰(如 func WithName(u User, n string) User),尤其在并发或测试中。指针不是银弹,它把副作用从函数签名里藏起来了。

















