082-反射(结构体字段遍历)

版权声明:本文为博主原创文章,未经博主允许不得转载。 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 的区别

猜你喜欢

转载自blog.csdn.net/q1007729991/article/details/82713256
今日推荐