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

Posted --Allen--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了082-反射(结构体字段遍历)相关的知识,希望对你有一定的参考价值。

如何知道一个未知结构体包含哪些字段呢?利用反射,可以很容易做到。

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 的区别

以上是关于082-反射(结构体字段遍历)的主要内容,如果未能解决你的问题,请参考以下文章

使用反射和泛型简化Golang查询数据库代码的方案

go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 结构体标签(struct tag)是结构体字段的额外信息标签

go语言 从结构体中获取某个字段的值(反射+泛型)

083-反射(序列化 json)

Golang实践录:利用反射reflect构建通用打印结构体接口

go-反射