结构体方法需严格遵循接收者语法(func (p person))、字段首字母大写(导出)以支持跨包访问和json序列化,修改字段必须用指针接收者,嵌入类型名须大写才能提升方法。

func 定义结构体方法不是加个括号就行,关键在接收者写法、大小写规则和指针/值语义——写错一个地方,编译不报错但运行时字段改不了、跨包访问失败、JSON序列化为空,全都是静默故障。
接收者语法必须带括号和变量名
结构体方法的接收者不是可选装饰,而是函数签名第一部分,格式固定为 func (p Person),不能漏括号、不能省变量名、不能写成 func (Person) 或 func Person.。
常见错误现象:
-
func (Person) Say() {}→ 编译报错:missing argument name -
func (p *Person) Name string→ 不是方法,是字段声明,直接语法错误
正确写法示例:
type Person struct {
Name string
Age int
}
func (p Person) Greet() string { return "Hi, " + p.Name }
func (p *Person) SetAge(a int) { p.Age = a }
字段首字母大小写决定方法能否访问字段
小写字段(如 name、age)在方法内部可以读写,但对外不可见;大写字段(如 Name、Age)才能被其他包访问、参与 JSON 序列化、被 ORM 映射。
容易踩的坑:
- 定义了
func (p *Person) GetName() string { return p.name }→ 编译通过,但外部包调用p.GetName()没问题,p.name却永远拿不到值(因为name是私有字段,GetName又没返回它) -
json.Marshal(&Person{name: "x"})输出{},不是{"name":"x"}—— 小写字段被自动忽略
所以:想让字段进 JSON、被其他包用、被数据库存,必须大写;只在本包内逻辑用,才考虑小写。
指针接收者 vs 值接收者:改不改原值是硬分界
这不是“性能优化建议”,而是行为差异。值接收者传的是整个结构体拷贝,指针接收者传的是地址。
使用场景判断:
- 要修改结构体字段(哪怕只是
p.Age++)→ 必须用func (p *Person) - 结构体含
map、slice、chan或sync.Mutex→ 即使只读也建议用指针,避免误以为值拷贝是深拷贝 - 结构体很小(比如只有 2 个
int)且纯读取 → 值接收者可接受,但统一用指针更安全 - 混用值和指针接收者 → 同一类型的方法集分裂,
interface{}实现可能失效
典型错误:
func (p Person) IncAge() { p.Age++ } // 外部调用后 p.Age 没变
func (p *Person) IncAge() { p.Age++ } // 这才真改了原始值
嵌入结构体时方法提升依赖导出类型
嵌入(embedding)不是继承,而是字段和方法的“自动提升”。但只有嵌入的结构体类型名首字母大写,它的字段和方法才会被提升。
例如:
type User struct{ Name string }
type Admin struct{ User; Level int } // OK:User 大写,Name 和方法都可直接用 admin.Name
type admin struct{ User; Level int } // 错误:admin 小写,但更重要的是嵌入的 User 是大写,仍可提升
type Admin2 struct{ user User; Level int } // user 小写 → 不提升,admin2.Name 编译失败
真正卡住人的点:
- 嵌入小写结构体(如
user)→ 提升完全失效,连admin.user.Name都得显式写 - 两个嵌入结构体有同名方法(如
User.Speak()和Robot.Speak())→admin.Speak()编译报错ambiguous selector,必须写成admin.User.Speak()
别指望编译器提醒你嵌入失效——它只在你真正访问时才报错,而且错得莫名其妙。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











