必须传入结构体指针并调用.Elem()获取可寻址的reflect.Value,遍历前检查Kind为Struct且IsValid,字段名取自Type,值取自Value,未导出字段需用CanInterface()判断是否可访问。

用 reflect.ValueOf 获取结构体值并遍历字段
直接对结构体变量调用 reflect.ValueOf 得到的是一个 reflect.Value,但必须确保它可寻址(addressable)才能获取字段值。传入指针是最稳妥的做法,否则 v.Field(i) 可能 panic。
常见错误:传入非指针结构体变量,导致 v.Kind() == reflect.Struct 但 v.CanAddr() == false,后续调用 v.Field(i) 会报 panic: reflect: call of reflect.Value.Field on struct Value。
- 始终传入结构体指针:
reflect.ValueOf(&myStruct) - 再用
.Elem()获取实际结构体值:v := reflect.ValueOf(&s).Elem() - 遍历前检查
v.Kind() == reflect.Struct和v.IsValid()
用 reflect.TypeOf 和 reflect.ValueOf 配合获取字段名与值
字段名来自类型信息(reflect.Type),字段值来自值信息(reflect.Value),二者需同步索引。注意:匿名字段(嵌入结构体)的字段也会被列出,且导出性不影响遍历——但不可导出字段的 Value 无法读取(v.Field(i).CanInterface() == false)。
示例中若字段未导出(如 age int),v.Field(i).Interface() 会 panic;应先判断 v.Field(i).CanInterface() 或用 v.Field(i).CanSet() 辅助判断可访问性。
立即学习“go语言免费学习笔记(深入)”;
type User struct {
Name string
age int // 小写,不可导出
}
u := User{Name: "Alice", age: 30}
v := reflect.ValueOf(&u).Elem()
t := reflect.TypeOf(u)
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
if value.CanInterface() {
println(field.Name, ":", value.Interface())
} else {
println(field.Name, ": (unexported, cannot read)")
}
}
处理嵌套结构体和指针字段时的注意事项
反射遍历时,v.Field(i) 返回的 reflect.Value 可能是结构体、指针、切片等任意类型。若字段是指针(如 *Time),需先判断 value.Kind() == reflect.Ptr 并调用 value.Elem() 才能继续展开;若为 nil 指针,value.Elem() 会 panic。
- 安全展开指针字段:先检查
!value.IsNil()再调用value.Elem() - 嵌套结构体字段可递归处理,但需限制深度防止无限循环(如循环引用)
-
reflect.Value的Interface()方法在字段不可导出时失效,此时只能用String()或跳过
性能与替代方案:什么情况下不该用反射遍历
反射在 Go 中开销显著:每次 reflect.ValueOf、Field()、Interface() 都涉及运行时类型检查和内存分配。简单结构体字段枚举(如 JSON 序列化、日志打印)建议手写方法或用代码生成工具(如 stringer、easyjson)。
只有当字段数量动态变化、结构体类型未知(如 ORM 映射、通用校验器)时,才值得引入反射。另外,go:generate + structtag 等静态分析方式比运行时反射更安全、更快。
真正难处理的不是遍历本身,而是统一处理导出/非导出、nil 指针、接口值、递归嵌套这四类边界情况——漏掉任一,线上就可能 panic。










