• go语言 | 图解反射(二)


    在这里插入图片描述

    reflect.Value 和 reflect.Type

    反射有两种类型 reflect.Value 和 reflect.Type ,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOf 和 reflect.TypeOf 分别获取任意对象的 reflect.Value 和 reflect.Type。


    reflect.Value

    reflect.Value 可以通过函数 reflect.ValueOf 获得。reflect.Value 被定义为一个 struct 结构体:

    type Value struct {
       typ *rtype
       ptr unsafe.Pointer
       flag
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    typ: 用于保存值的类型信息。在 Go 的 reflect 包中,rtype 结构表示类型信息。

    ptr: 用于保存数据的指针或指向数据的指针。如果 flagIndir 标志被设置,ptr 就是指向数据的指针;否则,ptr 就是数据的指针。

    flag: 用于保存与值相关的元信息和标志位。flag 的最低的几位用于表示标志位,其余的位用于存储类型的信息和其他元数据。flag 的位域包括:

    • flagStickyRO: 表示该值是通过不导出的非嵌入字段获得的,因此是只读的。
    • flagEmbedRO: 表示该值是通过导出的嵌入字段获得的,因此是只读的。
    • flagIndir: 标志 val 持有指向数据的指针。
    • flagAddr: v.CanAddr 为 true,表示该值的地址可以被取得(implies flagIndir)。
    • flagMethod: 表示该值是一个方法值。
    • 低几位表示值的种类(Kind)。
    • 其余的位用于存储方法值的方法号。

    在这里插入图片描述

    可以看到,这个结构体中的字段都是私有的,我们只能使用 reflect.Value 的方法,部分方法比如有:

    在这里插入图片描述
    方法分为三类:

    • 获取和修改对应的值
    • struct 类型的字段有关,用于获取对应的字段
    • 类型上的方法集有关,用于获取对应的方法

    任意类型的对象 与 reflect.Value 互转

    func main() {
       i := 5
       //int to reflect.Value
       iv:=reflect.ValueOf(i)
       //reflect.Value to int
       i1 := iv.Interface().(int)
       fmt.Println(i1)
    }
    //运行结果:
    5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • i := 5:创建一个整数变量 i 并初始化为 5。
    • iv := reflect.ValueOf(i):使用 reflect.ValueOf 函数将整数 i 转换为 reflect.Value 类型。iv 现在是一个包含整数值的 reflect.Value 对象。
    • i1 := iv.Interface().(int):使用 Interface() 方法将 reflect.Value 转换为接口类型,并通过断言将其还原为原始的整数类型。这里假设 iv 确实包含一个整数值,因此使用 (int) 进行断言。如果类型不匹配,这会导致运行时恐慌。
    • fmt.Println(i1):打印还原后的整数值。

    修改对应的值

    func main() {
       i := 5
       ipv := reflect.ValueOf(&i)
       ipv.Elem().SetInt(6)
       fmt.Println(i)
    }
    
    //运行结果:
    6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 示例中我们通过反射修改了一个变量。
    • reflect.ValueOf 函数返回的是一份值的拷贝,所以我们要传入变量的指针才可以。
    • 因为传递的是一个指针,所以需要调用 Elem 方法找到这个指针指向的值,这样才能修改。
    • 要修改一个变量的值,关键点:传递指针(可寻址),通过 Elem 方法获取指向的值,才可以保证值可以被修改,reflect.Value 为我们提供了 CanSet 方法判断是否可以修改该变量。

    修改 struct 结构体字段的值

    func main() {
       p := person{Name: "微客鸟窝",Age: 18}
       pv:=reflect.ValueOf(&p)
       pv.Elem().Field(0).SetString("无尘")
       fmt.Println(p)
    }
    type person struct {
       Name string
       Age int
    }
    
    //运行结果:
    {无尘 18}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    步骤总结:

    • 传递一个 struct 结构体的指针,获取对应的 reflect.Value;
    • 通过 Elem 方法获取指针指向的值;Elem 方法是在 Go 中 reflect.Value 类型上的一个方法,它用于获取接口或指针类型 reflect.Value 中所包含的值。如果 v 是一个指向某个值的指针,Elem 方法会返回该指针指向的值;如果 v 是一个接口类型,Elem 方法会返回接口中包含的具体值。
    • 通过 Field 方法获取要修改的字段;

    通过反射修改一个值的规则:
    可被寻址,通俗地讲就是要向 reflect.ValueOf 函数传递一个指针作为参数。
    如果要修改 struct 结构体字段值的话,该字段需要是可导出的,而不是私有的,也就是该字段的首字母为大写。
    记得使用 Elem 方法获得指针指向的值,这样才能调用 Set 系列方法进行修改。

    获取对应的底层类型

    因为我们可以通过 type 关键字来自定义一些新的类型,而底层类型就是一些基础类型。比如上面示例中的 p 变量类型为 person,底层类型为 struct 结构体类型。

    type person struct {
       Name string
       Age int
    }
    
    func main() {
    	p := person{Name: "微客鸟窝", Age: 18}
    	p1 := reflect.ValueOf(p)
    	p2 := reflect.ValueOf(&p)
    	fmt.Println(p1.Kind())
    	fmt.Println(p2.Kind())
    }
    
    //运行结果:
    struct
    ptr
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Kind 方法返回一个 Kind 类型的值,它是一个常量,从源码看下定义的 Kind 常量列表,含了 Go 语言的所有底层类型:

    在这里插入图片描述
    一共 26 种,我们可以分类如下:

    • 基础类型Bool、String以及各种数值类型(有符号整数Int/Int8/Int16/Int32/Int64,无符号整数Uint/Uint8/Uint16/Uint32/Uint64/Uintptr,浮点数Float32/Float64,复数Complex64/Complex128)
    • 复合(聚合)类型Array和Struct
    • 引用类型Chan、Func、Ptr、Slice和Map(值类型和引用类型区分不明显,这里不引战,大家理解意思就行)
    • 接口类型Interface
    • 非法类型Invalid,表示它还没有任何值(reflect.Value的零值就是Invalid类型)

    Field和NumField

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person struct {
    	Name    string
    	Age     int
    	Address string
    }
    
    func main() {
    	p := Person{"Alice", 30, "123 Main St"}
    
    	// 使用 reflect.ValueOf 获取结构体实例的反射值
    	pValue := reflect.ValueOf(p)
    
    	// 获取结构体字段的个数
    	numFields := pValue.NumField()
    	fmt.Println("Number of fields:", numFields)
    
    	// 遍历结构体的字段
    	for i := 0; i < numFields; i++ {
    		// 获取第 i 个字段的反射值
    		fieldValue := pValue.Field(i)
    
    		// 输出字段名和值
    		fieldName := pValue.Type().Field(i).Name
    		fmt.Printf("Field %s: %v\n", fieldName, fieldValue.Interface())
    	}
    }
    
    //输出结果
    Number of fields: 3
    Field Name: Alice
    Field Age: 30
    Field Address: 123 Main St
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
  • 相关阅读:
    Redis:新的3种数据类型Bitmaps、HyperLoglog、Geographic
    【50关于Visual Studio 2019控制台输出中文出现乱码问题及解决办法】
    java毕业设计选题基于SSM项目源代码实现的家政服务系统成品
    Python中setdefault()通过键查找字典中对应的值
    Google Earth Engine APP——在线计算23类植被指数app代码
    Kafka怎样完成建立和Broker之间的连接?
    【Hadoop---14】MapReduce:OutputFormat『 TextOutputFormat | 自定义OutputFormat』
    理解Go中的数据类型
    【解救ROS】ros小车机器人摄像头寻线的实现(基于opencv)
    【华为校招】【校招】【Java】考古问题
  • 原文地址:https://blog.csdn.net/qq_40318498/article/details/134342668