go结构体字段必须大写才能被外部设置;小写字段为未导出字段,json、orm、reflect等均不可见或不可设,初始化后需通过导出字段名用点号赋值,且变量须可寻址。

Go 里结构体字段不能直接“设置”——得先有实例,再按规则赋值。字段首字母大小写决定你能不能设、在哪能设、用什么方式设。
结构体字段必须大写才能被外部设置
小写字母开头的字段(如 name、age)是未导出字段,连 json.Unmarshal、reflect、跨包调用都看不到,更别说设置了。常见现象:json.Unmarshal 解析成功但字段全是零值;gorm 插入时字段没进数据库;reflect.ValueOf(u).FieldByName("name").CanSet() 返回 false。
- 对外暴露的字段必须大写:用
Name、ID、Email - 想隐藏实现细节(比如内部缓存、状态标记),就用小写,并配套提供
SetName()这类方法 - 别为了“统一风格”全大写——等于主动放弃封装能力,也容易被误改
初始化后直接用点号赋值最常用
只要结构体变量已存在且字段可导出,直接 u.Name = "Alice" 就行。但注意:这要求变量本身可寻址。
- 局部变量、切片/映射里的元素、指针解引用后都可直接赋值:
u := User{}; u.Name = "Alice" - 字面量临时值不行:
User{}.Name = "Alice"编译报错:cannot assign to User{}.Name - 函数返回的结构体值也不能直接设:
getUser().Name = "Bob"同样失败 - 嵌套字段要逐层访问:
u.Address.City = "Beijing",前提是Address和City都是导出字段
用 reflect 动态设置字段值要过三关
反射不是万能钥匙,它严格遵守 Go 的可见性与可寻址规则。漏掉任一条件都会 panic。
- 必须传指针并调
.Elem():v := reflect.ValueOf(&u).Elem(),否则SetString()报unaddressable value - 字段名必须导出:
v.FieldByName("Name")可用,v.FieldByName("name")返回无效值(!f.IsValid()) - 类型必须严格匹配:
Age int字段不能用SetInt(25)(那是int64),得用SetInt(25)前先转成int64,或用Set(reflect.ValueOf(int64(25))) - 指针/切片/map 字段为 nil 时,不能直接设子字段(如
u.Configs[0].Key = "x"),得先make或初始化
含 map/slice/chan/指针字段必须显式初始化
结构体字面量或 &T{} 只分配内存,不初始化复合字段。这些字段默认是 nil,一用就 panic。
-
map[string]int字段:不make就panic: assignment to entry in nil map -
[]string字段:不make就panic: runtime error: index out of range(即使只len()也不安全) -
*sync.RWMutex字段:不&sync.RWMutex{}就panic: invalid memory address - 推荐在工厂函数里统一处理:
func NewUser() *User { return &User{Cache: make(map[string]int, 16), Items: make([]string, 0, 8)} }
最容易被忽略的是:字段导出性不是“要不要序列化”的选择题,而是“能不能被设置”的开关。哪怕你只在本包里用,只要涉及 json、database/sql、reflect,就必须大写——这不是约定,是编译器强制的硬边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











