必须传入结构体指针给 reflect.ValueOf() 才能修改字段,否则因不可寻址导致 Set 操作 panic;读取需类型转换,修改前须检查 CanAddr() 和 CanSet(),且类型严格匹配。

用 reflect.ValueOf() 获取结构体反射值,但必须传入指针
直接对结构体变量调用 reflect.ValueOf() 得到的是不可寻址的副本,后续无法修改字段。必须传入指针才能读写字段:
type User struct {Name string Age int} u := User{Name: "Alice", Age: 30} v := reflect.ValueOf(&u) // ✅ 传指针 // v := reflect.ValueOf(u) // ❌ 不可寻址,v.CanAddr() == false
常见错误是忘记取地址,导致 v.FieldByName("Name").SetString("Bob") panic 报错:reflect: reflect.Value.SetString using unaddressable value。
通过 FieldByName() 读取字段值,注意类型转换
FieldByName() 返回 reflect.Value,需显式转为目标类型才能使用:
- 读字符串:
v.FieldByName("Name").String() - 读整数:
int(v.FieldByName("Age").Int())(Int()返回int64) - 读布尔:
v.FieldByName("Active").Bool()
若字段是导出的(首字母大写),可直接访问;非导出字段会返回零值且 IsValid() 为 false。不建议强行绕过导出限制——Go 的反射机制尊重可见性规则。
修改字段值前必须检查 CanSet(),且字段类型要严格匹配
即使传了指针,也不是所有字段都能改。必须同时满足:
-
v.CanAddr() == true(值可寻址) -
v.FieldByName("X").CanSet() == true(字段可设置) - 赋值类型完全一致,比如不能用
int64给int字段赋值
v := reflect.ValueOf(&u).Elem() // 先取指针指向的值 nameField := v.FieldByName("Name") if nameField.CanSet() { nameField.SetString("Bob") // ✅ } ageField := v.FieldByName("Age") if ageField.CanSet() { ageField.SetInt(31) // ✅ 注意:SetInt 接受 int64 }
如果字段是未导出的(如 password string),CanSet() 恒为 false,反射无法修改 —— 这不是 bug,是语言设计约束。
嵌套结构体和 slice/map 字段需要递归处理
对嵌套结构体字段(如 User.Profile *Profile)或容器字段(如 User.Hobbies []string),不能直接用 SetString():
- 修改嵌套结构体字段:先
FieldByName("Profile").Elem(),再操作其子字段 - 修改 slice 元素:用
Index(i)取元素,再Set();追加要用reflect.Append() - map 字段:需先
MapIndex(key)查找,或SetMapIndex(key, value)写入
这类操作容易 panic,务必在每步后检查 IsValid() 和 CanSet()。实际项目中,建议只对已知结构、明确需要动态操作的场景用反射,避免把简单逻辑复杂化。