版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q1007729991/article/details/82713256
如何知道一个未知结构体包含哪些字段呢?利用反射,可以很容易做到。
1. 遍历结构体的 field 和 method
还记得 reflect.Type 接口吧,这个接口还包含这 4 个方法:
type interface Type {
...
NumField() int
Field(i int) StructField
NumMethod() int
Method(int) Method
...
}
也就是说,只要你能拿到 Type 类型的接口值,就可以知道结构体包含了几个字段,几个方法。通过 NumField 和 Method 方法,你就可以获取关于第 i 个 field 和 method 的具体信息。
!!!注意:只有 Kind 为 Struct 的 Type 才可以调用上面这 4 个方法,否则程序会 panic.
field 和 method 的信息是通过 StructField 和 Method 类型进行描述的:
type StructField struct {
// Name is the field name.
Name string
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
type Method struct {
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
2. 示例
package main
import (
"fmt"
"reflect"
)
type Data struct {
weight uint32
height uint32
}
// 定义一个结构体
type Person struct {
Name string `tips:this is name`
age int32 `how old are you?`
Data
}
// 为 *Person 定义方法
func (p *Person) GetName() string {
return p.Name
}
func (p *Person) GetAge() int32 {
return p.age
}
func main() {
p := Person{
"allen",
19,
Data{
50,
180,
},
}
// 1. 取到 type 接口值
t := reflect.TypeOf(p)
fmt.Println("字段枚举:")
fmt.Println("------------------------------")
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("index:%d\nName:%s\nPkgPath:%s\nType:%v\nTag:%s\nOffset:%v\nIndex:%v\nAnonymous:%v\n",
i, f.Name, f.PkgPath, f.Type, f.Tag, f.Offset, f.Index, f.Anonymous)
fmt.Println("------------------------------")
}
fmt.Println("方法枚举:")
fmt.Println("------------------------------")
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("index:%d\nName:%s\nPkgPath:%s\nType:%v\nFunc:%v\nIndex:%v\n",
i, m.Name, m.PkgPath, m.Type, m.Func, m.Index)
fmt.Println("------------------------------")
}
}
输出结果:
字段枚举:
------------------------------
index:0
Name:Name
PkgPath:
Type:string
Tag:tips:this is name
Offset:0
Index:[0]
Anonymous:false
------------------------------
index:1
Name:age
PkgPath:main
Type:int32
Tag:how old are you?
Offset:16
Index:[1]
Anonymous:false
------------------------------
index:2
Name:Data
PkgPath:
Type:main.Data
Tag:
Offset:20
Index:[2]
Anonymous:true
------------------------------
方法枚举:
------------------------------
有同学会很好奇,为什么方法一个都没有遍历出来?不知道你是否还记得“方法的接收器”相关的知识。在上面的例子里,方法的接收器是指针类型,这意味着那两个方法并没有定义在 Person 类型上,而是定义在 *Person 上。
你使用 TypeOf(p) 拿到的类型是关于 Person 的类型信息,而不是 *Person 类型信息。因此,如果你想枚举方法,就只能使用 TypeOf(&p) 拿到关于 *Person 的类型信息,才可以正确的枚举出方法。
3. 如何打印字段对应的值
字段名也有了,那么字段的值怎么获取呢?
要知道,Type 接口只能拿到关于类型的信息,无法拿到 Value 的信息。如果想获取字段值,就只能从 Value 这个结构体入手了。同样的,Value 也提供了 4 个方法:
type struct Value {
...
}
func (v Value) NumField() int
func (v Value) Field(i int) Value
func (v Value) NumMethod() int
func (v Value) Method(i int) Value
不过,这 4 个方法和 Type 接口那 4 个方法返回值不一样,它返回的又是 Value 类型。好了,剩下的就是码代码了,这个实在是太简单,同学们自己完全可以写出来。
4. 总结
- 知道如何遍历结构体的字段和方法
- 知道 Type 接口关于遍历字段和方法的方法与 Value 的区别