go语言 | 图解反射(二)

go语言 | 图解反射(二)_第1张图片

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
}

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

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

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

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

go语言 | 图解反射(二)_第2张图片

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

go语言 | 图解反射(二)_第3张图片
方法分为三类:

  • 获取和修改对应的值
  • 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
  • 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
  • 示例中我们通过反射修改了一个变量。
  • 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}

步骤总结:

  • 传递一个 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

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

go语言 | 图解反射(二)_第4张图片
一共 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

你可能感兴趣的:(go语言,golang,开发语言,后端)