Go中struct字段首字母大小写决定导出性、JSON序列化、ORM映射等行为;必须大写才导出,小写仅限本包;初始化须用键值对;方法接收者选*T才能修改原值;嵌入结构体需导出类型才支持字段提升。

Go 里 struct 不是“类”,也不是语法糖,它直接决定字段能否跨包访问、JSON 能否序列化、ORM 能否映射、方法能否修改原值——定义错一个字母大小写,后面全链路都会静默失效。
struct 字段首字母大小写决定导出性与序列化行为
Go 没有 public/private 关键字,字段是否能被其他包访问、是否参与 json.Marshal、是否被 GORM 映射,只看首字母:大写(如 Name、ID)才导出;小写(如 name、id)仅限本包内使用。
- 跨包调用时出现
cannot refer to unexported field,八成是字段名小写了 -
json.Marshal输出为空字段或整个字段消失?检查字段名首字母 - GORM 插入数据库时某字段始终为 NULL 或零值?大概率是
createdAt写成了小写,而CreatedAt才会被自动识别 - 嵌入结构体时,如果嵌入的是小写类型(如
person Person),它的字段不会被提升;必须是导出类型(如Person)才能支持e.Name这种写法
初始化必须用字段名赋值,别依赖顺序
用 Student{101, "lisi", 20, 97} 这种位置式初始化,看似简洁,但一旦结构体增删字段或调整顺序,就会 silently 错位——编译不报错,运行时却把 Age 写进 Score 位置,极难排查。
- 始终优先用键值对:
Student{ID: 101, Name: "lisi", Age: 20, Score: 97} - 字段可乱序、可省略(省略字段取零值:
""、0、nil) - 加新字段后,旧初始化代码仍能编译通过,不会意外覆盖其他字段
- 嵌套结构体也得完整初始化:
Address: Address{City: "Beijing"},不能简写成Address: {"Beijing"}
方法接收者选 *T 还是 T 不是风格问题
接收者类型直接决定语义和行为:值接收者(func (s Student) Modify())操作的是副本,改了也不影响原结构体;指针接收者(func (s *Student) Modify())才能真正修改原值——而且前提是该字段本身是导出的(首字母大写),否则连编译都过不去。
立即学习“go语言免费学习笔记(深入)”;
- 只要方法里要修改字段(哪怕只是
s.Age++),就必须用*Student - 结构体含
[]byte、map、chan等引用类型时,即使只读,也建议用指针接收者,避免误以为值拷贝是深拷贝 - 结构体较大(字段多或含大 slice)时,值接收者会引发明显性能开销
-
&s.Method()和s.Method()行为可能不同,尤其当方法接收者混用时,容易混乱
嵌入结构体不是继承,是字段提升 + 方法组合
写 type Employee struct { Person; Title string },不是“Employee 继承 Person”,而是把 Person 的导出字段和方法“提上来”一层,让 e.Name 和 e.SayHello() 可以直接调用。
- 匿名嵌入(如
Person)才支持字段提升;命名嵌入(如person Person)必须用e.person.Name - 两个嵌入结构体有同名导出字段(比如都含
ID),e.ID会编译报错,必须显式写成e.Person.ID或e.Company.ID - 若两个嵌入类型都有
Save()方法,e.Save()会编译失败,必须限定调用路径(e.Person.Save()) - 嵌入的结构体本身也必须是导出类型(
Person),否则字段根本不会被提升
最常被忽略的其实是字段可见性与序列化的强绑定:一个字段在代码里能读能写,但 json.Marshal 输出为空,几乎可以断定是首字母小写了;嵌入结构体时忘了它本身也要导出,导致字段提升失效,也是高频盲区。


















