go语言 | 图解反射(二)

news/2024/7/8 3:43:21 标签: golang, 开发语言, 后端

在这里插入图片描述

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)。
  • 其余的位用于存储方法值的方法号。

在这里插入图片描述

可以看到,这个结构体中的字段都是私有的,我们只能使用 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
  • 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 语言的所有底层类型:

在这里插入图片描述
一共 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


http://www.niftyadmin.cn/n/5183882.html

相关文章

Java聚合对外网关,使用国密SM4采用CBC分组填充模式实现数据加密工具类,Jmeter压测

添加依赖配置 <!-- 仓库地址: https://mvnrepository.com/artifact/commons-codec/commons-codec --><!-- org.apache.commons.codec.binary.Base64 --><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artif…

Python Web APP在宝塔发布

本地测试运行&#xff1a;uvicorn main:app --host 127.0.0.1 --port 8082 --reload 宝塔发布&#xff1a; 运行配置——>启动模式&#xff1a;worker_class uvicorn.workers.UvicornWorker

论软件开发模型及应用

2022年1月&#xff0c;我所在的公司&#xff0c;某能源集团下属的信息公司&#xff0c;承担该能源集团下属的化工部投资建设的化工生产综合运营管控系统&#xff0c;简称运营管控系统。作为公司技术骨干&#xff0c;我有幸担任了该系统架构师&#xff0c;主要负责该系统的架构设…

图像的希尔波特变换(Hilbert Transform)

引言: 希尔伯特变换在信号处理中很重要,能够导出信号u(t)的解析表示。这就意味着将实信号u(t) 拓展到复平面,使其满足柯西-黎曼方程。 图像的希尔波特变换 1. 希尔波特(Hilbert)变换简介1.1 Hilbert变换的作用1.2 什么是实值信号的解析信号(Analytic signal)表示?2. Mon…

医院安全(不良)事件管理系统源码 不良事件报告全套源码

不良事件管理系统是一种专为企业或组织设计的软件工具&#xff0c;用于跟踪、记录和管理不良事件。该系统可以有效地整合不良事件的收集、分类、分析和报告&#xff0c;帮助企业及时识别和处理不良事件&#xff0c;从而降低风险和损失。通过实时监控和自动化报告&#xff0c;该…

电池故障估计:Realistic fault detection of li-ion battery via dynamical deep learning

昇科能源、清华大学欧阳明高院士团队等的最新研究成果《动态深度学习实现锂离子电池异常检测》&#xff0c;用已经处理的整车充电段数据&#xff0c;分析车辆当前或近期是否存在故障。 思想步骤&#xff1a; 用正常电池的充电片段数据构造训练集&#xff0c;用如下的方式构造…

AC修炼计划(AtCoder Regular Contest 162)

传送门&#xff1a;AtCoder Regular Contest 162 - AtCoder A题签到 B - Insertion Sort 2 我们可以从头开始一个一个排序&#xff0c;把1通过操作放到第一个&#xff0c;把2通过操作放到第二个。。。以此类推。但会出现一种情况&#xff0c;如果我们所要排的数字在最后一位&…

cobol-简介

cobol学习笔记 cobol概述 COBOL是一门高级语言。我们必须了解COBOL的工作方式。计算机只能理解机器代码&#xff0c;0和1的二进制流。 COBOL代码必须使用编译器转换成机器代码。通过编译器运行程序源码。编译器首先检查是否有任何语法错误&#xff0c;然后将其转换为机器语言。…