go语言基础 -- 反射

embedded/2025/1/31 16:15:23/

反射的基本介绍

  1. 反射可以在运行时动态获取变量的信息,如变量的类型(type),类别(kind)。
  2. 如果是结构体变量,还可以获取到变量的字段、方法等结构体本身信息;
  3. 通过反射,可以修改变量的值或调用关联的方法;
  4. 使用反射需要import(“reflect”)
    我们前面的文章空接口接收任意类型的变量,通过typeof来判断变量类型

反射的基础应用场景

  1. 不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这时需要对函数或方法进行反射,如下例传入函数指针,及回调函数的参数
    func bradge(funcPtr interface{}, args …interface{})
    在bradge中通过反射来执行传入的函数
  2. 结构体序列化时,指定了字段tag,通过反射生成对应的字符串

反射的重要函数

  1. reflect.TypeOf(变量名)–获取变量的类型,返回值是reflect.Type类型(是一个接口类型)
  2. reflect.ValueOf(变量名)–获取变量的值,返回reflect.Value类型(是一个struct),具体可查go的反射包的介绍
变量、interface{}、reflect.Value是可以相互转换的

我们来看一个相互转换的例子,这在反射中是很常用的

var student Student
var num ntfunc trans(b interface{}) {// 将b这个interface{}类型转为reflect.Value类型rVal := reflect.ValueOf(b)// 将reflect.Value类型转为interface{}类型iVal := rVal.Interface()// 将interface类型转为原来的变量类型--类型断言val := iVal.(Student)// 若b是int类型,还可以直接用reflect.Value类型里面的Int方法取变量值,之后才能和其他int类型进行操作n := rVal.Int()}
  1. reflect.Value.kind和reflect.Value.type的区别
    kind是变量的分类,是大的范畴
    在这里插入图片描述
    kind的取值是枚举里面的值,是一个常量,type是变量的具体类型,type的取值可以是自定义类型,比如一个自定义结构体类型Student,它的kind是struct,但type是Student
  2. 在使用反射的方式来获取变量的值并获取对应的数据类型,必须类型一致,比如rVal := reflect.ValueOf(b),假设这里的rVal的值是一个int类型,但我们用rVal.Float()来获取值,那么就会panic。
  3. 通过反射来修改变量时,使用SetXxx的方法来修改变量值,这时需要传递变量的指针,同时需要用到reflect.Elem().SetXxx()而不是reflect.SetXxx()
func main() {var num int = 10// 这里需要传地址rVal = reflect.ValueOf(&num)// 这里不能直接set,需要用指针指向的elem来修改rVal.Elem().SetInt(20)
}

反射的几种最佳实践

1. 使用反射遍历结构体字段,调用结构体方法,并获取结构体标签

package main
import("fmt""reflect"
)
type Monster struct {Name string `json:name`Age int `json:monster_age`Score float32Sex string
}func (s Monster)Print() {fmt.Println("print monster", s)
}func (s Monster)GetSum(n1, n2 int) int {return n1 + n2
}func (s Monster) Set(Name string, Age int, Score float32, Sex string) {s.Name = Names.Age = Ages.Score = Scores.Sex = Sex
}func TestStruct(b interface{}) {rtype := reflect.TypeOf(b)rVal := reflect.ValueOf(b)kd := rVal.Kind()if kd != reflect.Struct {fmt.Println("expect struct")return}// 获取该结构体有几个字段num := rVal.NumField()fmt.Printf("struct has %d filed", num)for i :=0; i < num; i++ {// 通过Field方法遍历字段,注意这里Field(i)是reflect.Value类型,不能直接用于运算,也需要进行类型断言才能运算fmt.Printf("field %d value is %v", i, rVal.Field(i))// 这里不能用value获取tag,只能用reflect.Type来获取标签,如果结构体不止一个标签,那么这里只能获取json的标签数据tagval := rtype.Field(i).Tag.Get("json")if tagval != "" {fmt.Printf("field %d tag is:%v", i, tagval)}}//获取到该结构体有多少个方法,方法序号从0开始,方法的排序默认按照函数名的ascii码大小比较排序numOfMethod := val.NumMethod()fmt.Printf("struct has %d methods\n", numOfMethod)// var params []reflect.Value// 调用第二个方法:Print方法rval.Method(1).Call(nil)// 调用结构体的第1个方法Method(0)var params []reflect.Valueparams append(params, reflect.Valueof(10))params append(params, reflect.Valueof(40))// Call方法参数类型reflect.Value类型的切片类型,因此上面先构造切片,返回值也是一个切片res := rval.Method(0).Call(params)//传入的参数是[]reflect.Valuefmt.Println("res=",res[0].Int())//返回结果,返回的结果是[]reflect.Value*/}func main() {var m1 Monster := Monster{Name : "huangshulang",Age : 400,Score : 30.9,}// 将Monster实例传给TestStruct函数即可调用结构体相关的方法,这在一些固定框架的代码中很有用,我们通常只需要定义好结构体及对应的方法,框架里面通过反射即可相关功能自动处理,无需显式通过结构体实例调用TestStruct(m1)
}

补充上面Call方法的官方说明:
在这里插入图片描述

2、修改结构体、标签

这个基本上与上面一致,需要注意的市,我们在传参的时候,TestStruct(&m1)要传地址,才能修改成功,其余的就是用类似
rval.Elem().Field(0).SetString(“白象精”)修改方法来操作修改即可

3、适配器实现

定义了两个函数test1和test2,定义一个适配器函数用作统一处理接口

(1)定义了两个函数
test1 := func(v1 int, v2 int){
t.Log(v1,v2)
}
test2 := func(v1 int, v2 int, s string){
t.Log(v1,v2,s)
}
现在要定义一个适配器函数用作统一处理接口,其大致结构如下
bridge:=func(call interface{}, args.….interface{}){
// 内容
}
实现调用test1对应的函数
bridge(test1, 1, 2)
实现调用test2对应的函数
bridge(test2, 1, 2, “test2”)

func TestReflectFunc(t *testing.T){call1 := func(v1 int,v2 int){t.Log(v1,v2)}call2 := func(v1 int,v2 int,s string){t.Log(v1,v2,s)}var(function reflect.ValueinValue []reflect.Valuen int)bridge := func(call interface{}, args...interface{}){n = len(args)inValue=make(reflect.Value, n)for i := 0; i<n; i++ {inValue[i]=reflect.ValueOf(args[i])}function reflect.ValueOf(call)function.Call(inValue)
}
bridge(call1,1,2)
bridge(call2,1,2,"test2")

4、使用反射操作任意结构体

type user struct {Userld stringName string
}
func TestReflectStruct(t *testing.T) {var ( model *usersv reflect.Value)// model指向一个user对象model = &user{}// 将model转成一个reflect.Value类型sv = reflect.ValueOf(model)t.Log("reflect.ValueOf",sv.Kind().String())// 这里获取Elem之后,sv实际就指向user对象了,后续就能直接用sv来操作结构体了sv = sv.Elem()t.Log("reflect.ValueOf.Elem",sv.Kind().String())sv.FieldByName("Userld").SetString("12345678")sv.FieldByName("Name").SetString("nickname")t.Log("model",model)
}

5、使用反射创建并操作结构体

package test
import ("testing""reflect"
)
type user struct {Userld stringName string
}
func TestReflectStructPtr(t *testing.T) {var(model *userst reflect.Typeelem reflect.Value)st = reflect.Typeof(model)/获取类型*usert.Log("reflect.Typeof",st.Kind().String())//ptrst = st.Elem()/st指向的类型t.Log("reflect.TypeOf.Elem",st.Kind).String())//structelem = reflect.New(st)/New返回一个Value类型值,该值持有一个指向类型为type的新申请的零值的指针t.Log("reflect.New",elem.Kind().String())//ptrt.Log("reflect.New.Elem",elem.Elem().Kind().StringO)//struct/model就是创建的user结构体变量(实例)model = elem.Interface().(*user)/model:是user它的指向和elem是一样的elem = elem.Elem()/取得elem指向的值elem.FieldByName("Userld").SetString("12345678")/赋值.elem.FieldByName("Name").SetString("nickname")t.Log("modelmodel.Name", model ,model.Name)

http://www.ppmy.cn/embedded/7489.html

相关文章

高职人工智能综合实训案例之Al分类模型实战

一、案例背景 随着信息技术的迅猛发展&#xff0c;人工智能已经成为引领新一轮科技革命和产业变革的重要驱动力。特别是在大数据和云计算技术的支持下&#xff0c;AI分类模型在各个领域展现出广阔的应用前景。无论是图像识别、语音识别&#xff0c;还是自然语言处理&#xff0…

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程 &#x1f4cd;相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f388;《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》&#x1f4cc;MT6701当前最新文档资料&#xff1a;https://www.magntek.com.cn/u…

什么是渐进式框架

渐进式框架是一种设计理念&#xff0c;它允许开发者根据项目需求逐步采用框架的功能。这意味着你可以从一个非常轻量、简洁的开始&#xff0c;根据实际需要不断引入更多的组件和功能。这种方式的好处在于它使得项目的起点低、上手快&#xff0c;同时随着项目的增长&#xff0c;…

Java面试八股之Iterator接口和Iterable接口

1. Java为什么不直接实现Iterator接口&#xff0c;而是实现Iterable? 这道题算是一道比较基础的题&#xff0c;面试官肯定也不是想让回答得多深入&#xff0c;只是考查对迭代器的了解程度&#xff0c;最好是看过源码&#xff0c;实际上迭代器的源码并不难。我们把注释折叠起来…

C语言趣味代码(二)

1.珠玑妙算 1.1 介绍 《珠玑妙算》(Mastermind)是英国Invicta公司于1973年开始销售的一款益智游戏&#xff0c;据说迄今为止已经在全世界销售了5000万套。《珠玑妙算》于1974年获奖后&#xff0c;在1975年传入美国&#xff0c;1976年leslieH.Autl博士甚至还出版了一本名为The…

力扣:104. 二叉树的最大深度(Java,DFS,BFS)

目录 题目描述&#xff1a;输入&#xff1a;输出&#xff1a;代码实现&#xff1a;1.深度优先搜索&#xff08;递归&#xff09;2.广度优先搜索&#xff08;队列&#xff09; 题目描述&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从…

基于LSTM的新闻中文文本分类——基于textCNN与textRNN

构建词语字典 def build_vocab(file_path, tokenizer, max_size, min_freq):# 定义词汇表字典&#xff1a;使用 vocab_dic {} 初始化一个空字典&#xff0c;用于存储每个词及其出现频率vocab_dic {}with open(file_path, r, encodingUTF-8) as f:for line in tqdm(f):lin l…

「 网络安全常用术语解读 」软件成分分析SCA详解:从发展背景到技术原理再到业界常用检测工具推荐

软件成分分析&#xff08;Software Composition Analysis&#xff0c;SCA&#xff09;是一种用于识别和分析软件内部组件及其关系的技术&#xff0c;旨在帮助开发人员更好地了解和管理其软件的构建过程&#xff0c;同时可帮助安全人员揭秘软件内部结构的神秘面纱。SCA技术的发展…