修改结构体字段必须用指针接收者,因值接收者操作副本;切片的len/cap可被指针方法修改,但底层数组地址不能被“换掉”;map/chan/func值接收者可修改底层数据但不能替换变量本身;nil指针调用方法会panic,接口值nil未必panic。

修改结构体字段必须用指针接收者
Go 里如果想在方法内部修改调用者的字段值,接收者必须是指针类型。值接收者会操作副本,原结构体完全不受影响。
常见错误现象:user.Name 在 SetName() 方法里改了,但调用后发现没变——八成用了值接收者。
- 正确写法:
func (u *User) SetName(name string) { u.Name = name } - 错误写法:
func (u User) SetName(name string) { u.Name = name }(只改了栈上副本) - 调用时
u := User{}; u.SetName("Alice"),值接收者下字段不变;u := &User{}; u.SetName("Alice")或u := User{}; (&u).SetName("Alice")才生效
切片长度和容量能被指针方法修改,但底层数组地址不能靠指针“换掉”
切片本身是三元组(ptr, len, cap),指针接收者能改其中的 len 或 cap,但无法让原变量指向新分配的底层数组——除非返回新切片并由调用方重新赋值。
典型场景:扩容逻辑常被误认为“能就地扩容”,实际 append 可能分配新底层数组,原指针接收者无法把新地址回传给调用方的变量。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 能安全做的:
func (s *[]int) Truncate(n int) { *s = (*s)[:n] }(直接改指针所指的切片头) - 不能靠指针接收者完成的:
func (s *[]int) Grow() { *s = append(*s, 0) }—— 如果触发扩容,*s确实会更新,但这是靠解引用+赋值实现的,不是“方法自动改了调用方变量” - 更稳妥做法仍是返回新切片:
func (s []int) Grow() []int { return append(s, 0) },然后s = s.Grow()
map、chan、func 类型本身是指针语义,传值也等效于传指针
这三类类型在 Go 运行时底层都是指针包装,所以即使方法用值接收者,也能修改其指向的底层数据(如往 map 里插入键值、向 chan 发送数据)。
但注意:你不能通过值接收者“替换整个 map 变量”,比如 m = make(map[string]int) 这种赋值只作用于副本;而 m["k"] = v 是可以的,因为操作的是它指向的哈希表。
- 值接收者可安全做:
func (m map[string]int) Set(k string, v int) { m[k] = v }(改底层哈希表) - 值接收者无法做到:
func (m map[string]int) Reset() { m = make(map[string]int) }(只清空副本) - 真正要重置 map,得用指针接收者:
func (m *map[string]int) Reset() { *m = make(map[string]int) }
nil 指针调用方法会 panic,但接口值 nil 未必
如果方法接收者是指针类型,而你传了个 nil 的指针变量(比如 var u *User),再调用其方法,运行时直接 panic:invalid memory address or nil pointer dereference。
但若该指针实现了某个接口,而接口变量本身是 nil,是否 panic 取决于方法里有没有解引用操作——空接口值调用方法不必然 panic,只有真正访问 u.Name 这类字段时才崩。
- 危险代码:
var u *User; u.SetName("x")→ 立即 panic - 稍隐蔽的坑:
var i fmt.Stringer = (*User)(nil); fmt.Println(i)—— 如果User.String()方法里没判空就访问字段,这里才 panic - 防御写法:指针接收者方法开头加
if u == nil { return }或明确报错
func (s *[]T) Push(x T) 能安全替代 s = append(s, x),但没意识到一旦扩容发生,原变量是否被正确更新,取决于你有没有在方法里做 *s = ... 赋值,以及调用方是否传了取址表达式。

















