Go第 17 章 :反射

news/2025/2/22 5:30:21/

Go第 17 章 :反射

17.1 先看一个问题,反射的使用场景

请添加图片描述

17.2 使用反射机制,编写函数的适配器, 桥连接

请添加图片描述

17.3 反射的基本介绍

17.3.1 基本介绍

  1. 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  4. 使用反射,需要 import (“reflect”)
  5. 示意图
    请添加图片描述

17.3.2 反射的应用场景

请添加图片描述

17.3.3 反射重要的函数和概念

请添加图片描述
3) 变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。画 出示意图
请添加图片描述
请添加图片描述

17.4 反射的快速入门

17.4.1 快速入门说明

 请编写一个案例,演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作
代码演示,见下面的表格:
 请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作
代码演示:

package mainimport ("fmt""reflect"
)//专门演示反
func reflectTest(b interface{}) {//通过反射获取的传入的变量的type,kind值//1、先获取到reflect.TyperType := reflect.TypeOf(b)fmt.Println("rType=", rType)//2、获取到reflect.ValuerVal := reflect.ValueOf(b)n2 := 2 + rVal.Int()fmt.Println("n2=", n2)fmt.Printf("rVal=%v rVal type=%T\n", rVal, rVal)//3、下面将rVal转程interface{}iV := rVal.Interface()//将interface{}通过类型断言转成需要的类型num2 := iV.(int)fmt.Printf("num2=%d", num2)return
}//专门演示反射【对结构体的反射】
func reflectTest02(b interface{}) {//通过反射获取的传入的变量type,kind值//1、先获取到reflect.TyperType := reflect.TypeOf(b)fmt.Println("rType=", rType)//2、获取到reflect.ValuerVal := reflect.ValueOf(b)fmt.Println("rValue=", rVal)//下面我们将 rVal 转成 interface{}iV := rVal.Interface()fmt.Printf("iv=%v iv type=%T \n", iV, iV)//将 interface{} 通过断言转成需要的类型//这里,我们就简单使用了一带检测的类型断言.//这里,我们就简单使用了一带检测的类型断言.//同学们可以使用 swtich 的断言形式来做的更加的灵活stu, ok := iV.(Student)if ok{fmt.Printf("stu.Name=%v\n", stu.Name)}}type Student struct {Name stringAge  int
}
type Monster struct {Name stringAge  int
}func main() {//请编写一个案例,//演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作//1. 先定义一个 intvar num int = 100reflectTest(num)//2. 定义一个 Student 的实例//stu := Student{//	Name: "tom",//	Age:  20,//}//reflectTest02(stu)
}

17.5 反射的注意事项和细节

1) reflect.Value.Kind,获取变量的类别,返回的是一个常量

请添加图片描述

2) Type 和 Kind 的区别

Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的.
比如: var num int = 10 num 的 Type 是 int ,
Kind 也是 int 比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct

3)4)

请添加图片描述

对结构体需要进行一下类型断言才能使用reflect.Value(x).Int()

5) 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法

请添加图片描述

6) reflect.Value.Elem() 应该如何理解?

请添加图片描述

17.6 反射课堂练习

  1. 给你一个变量 var v float64 = 1.2 , 请使用反射来得到它的 reflect.Value, 然后获取对应的 Type, Kind 和值,并将 reflect.Value 转换成 interface{} , 再将 interface{} 转换成 float64. [不说:]
package mainimport ("fmt""reflect"
)func main(){var v float64 = 1.2rVal:=reflect.ValueOf(v)fmt.Println("rVal=",rVal)rType:=reflect.TypeOf(v)fmt.Println("rType=",rType)rKind:=reflect.Kind(v)fmt.Println("rKind=",rKind)rinterface:=rVal.Interface()fmt.Printf("rinterface=%v  rinterface Type=%T",rinterface,rinterface)
}
  1. 看段代码,判断是否正确,为什么
    package main
    import (
    “fmt”
    “reflect”
    )
    func main() {
    var str string = “tom” //ok
    fs := reflect.ValueOf(str)
    //ok
    fs -> string
    fs.SetString(“jack”)
    //error
    fmt.Printf(“%v\n”, str)
    }
![请添加图片描述](https://img-blog.csdnimg.cn/544a34747b7f4c16989a3c1c01e00b76.png)
## 17.7 反射最佳实践
![请添加图片描述](https://img-blog.csdnimg.cn/2b3af851a20f4ce1a70fb653a515e899.png)#### 1) 使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
###### 反射调用方法的排名是通过对象绑定的方法名的首字母的ASCLL码来排列的 PSG
````go
package mainimport ("fmt""reflect"
)type Monster struct {Name  string  `json:"name"`Age   int     `json:monster_age`Score float32 `json:"成绩"`Sex   string
}//方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {return n1 + n2
}//方法 接收四个值,给s赋值
func (s Monster) Set(name string, age int, score float32, sex string) {s.Name = names.Age = ages.Score = scores.Sex = sex
}//方法显示s的值
func (s Monster) Print() {fmt.Println("---start---")fmt.Println(s)fmt.Println("---end---")
}func TestStruct(a interface{}) {//获取reflcet.Type类型typ := reflect.TypeOf(a)fmt.Println("typ=", typ)//获取reflcet.Vale类型val := reflect.ValueOf(a)fmt.Println("val=", val)//找到a对应的类别kd := val.Kind()fmt.Println("kind=", kd)//如果传入的不是struct,就退出if kd != reflect.Struct {fmt.Println("expect struct")return}//获取到该结构体有几个字段num := val.NumField()fmt.Println("num=", num)//变量结构体的所有字段for i := 0; i < num; i++ {fmt.Printf("Filed %d: 值为:%v\n", i, val.Field(i))//获取到struct标签,注意需要通过reflect.Type来获取tag标签的值tagVal := typ.Field(i).Tag.Get("json")//如果该字段于 tag 标签就显示,否则就不显示if tagVal != "" {fmt.Printf("Field %d: tag 为=%v\n", i, tagVal)}}//获取到该结构体有多少个方法numOfMethod := val.NumMethod()fmt.Printf("struct has %d methods\n", numOfMethod)//var params []reflect.Value//方法的排序默认是按照 函数名的排序(ASCII 码)val.Method(1).Call(nil) //获取到第二个方法。调用它//调用结构体的第 1 个方法 Method(0)var params []reflect.Value //声明了[]reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(40))res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Valuefmt.Println("res=", res[0].Int())
}func main() {//创建了一个 Monster 实例var a Monster = Monster{Name:  "黄鼠狼精",Age:   400,Score: 30.8,}//将 Monster 实例传递给 TestStruct 函数TestStruct(a)
}
  1. 使用反射的方式来获取结构体的 tag 标签, 遍历字段的值,修改字段值,调用结构体方法(要求: 通过传递地址的方式完成, 在前面案例上修改即可)
package mainimport ("fmt""reflect"
)type Monster struct {Name  string  `json:"name"`Age   int     `json:"monster_age"`Score float32 `json:"成绩"`Sex   string
}//方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {return n1 + n2
}//方法 接收四个值,给s赋值
func (s Monster) Set(name string, age int, score float32, sex string) {s.Name = names.Age = ages.Score = scores.Sex = sex
}//方法显示s的值
func (s Monster) Print() {fmt.Println("---start---")fmt.Println(s)fmt.Println("---end---")
}func TestStruct(a interface{}) {//获取reflcet.Type类型typ := reflect.TypeOf(a)fmt.Println("typ=", typ)//获取reflcet.Vale类型val := reflect.ValueOf(a)fmt.Println("val=", val)//找到a对应的类别kd := val.Kind()fmt.Println("kind=", kd)//如果传入的不是struct,就退出//要多一层判断,判断是否是指针,并且还要用elem去获取指针的信息(*ptr)//根据获取的数据再去通过kind去获取传入的类型是什么,进而去对比if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {fmt.Println("expect struct")return}//获取到该结构体有几个字段num := val.Elem().NumField()//因为是指针,我们这里获取字段的长度前面也要加个elem表示这个值是一个指针fmt.Println("num=", num)val.Elem().Field(0).SetString("tianyi")//我们通过setStringi去修改索引位0的值,同样要加Elem//变量结构体的所有字段for i := 0; i < num; i++ {fmt.Printf("Filed %d: 值为:%v\n", i, val.Elem().Field(i))//获取到struct标签,注意需要通过reflect.Type来获取tag标签的值tagVal := typ.Elem().Field(i).Tag.Get("json")//只要是获取这个指针的值信息的,都要带elem//如果该字段于 tag 标签就显示,否则就不显示if tagVal != "" {fmt.Printf("Field %d: tag 为=%v\n", i, tagVal)}}tag :=typ.Elem().Field(0).Tag.Get("json")fmt.Printf("tag=%s\n",tag)//获取到该结构体有多少个方法numOfMethod := val.Elem().NumMethod()fmt.Printf("struct has %d methods\n", numOfMethod)//var params []reflect.Value//方法的排序默认是按照 函数名的排序(ASCII 码)val.Elem().Method(1).Call(nil) //获取到第二个方法。调用它//调用结构体的第 1 个方法 Method(0)var params []reflect.Value //声明了[]reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(40))res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Valuefmt.Println("res=", res[0].Int())
}func main() {//创建了一个 Monster 实例var a Monster = Monster{Name:  "黄鼠狼精",Age:   400,Score: 30.8,}//将 Monster 实例传递给 TestStruct 函数TestStruct(&a)fmt.Println(a)
}
typ= *main.Monster
val= &{黄鼠狼精 400 30.8 }
kind= ptr
num= 4
Filed 0: 值为:tianyi
Field 0: tag 为=name
Filed 1: 值为:400
Field 1: tag 为=monster_age
Filed 2: 值为:30.8
Field 2: tag 为=成绩
Filed 3: 值为:
tag=name
struct has 3 methods
---start---
{tianyi 400 30.8 }
---end---
res= 50
{tianyi 400 30.8 }
  1. 定义了两个函数 test1 和 test2,定义一个适配器函数用作统一处理接口【了解】
package mainimport ("fmt""reflect"
)func Call1(v1 ,v2 int){fmt.Println(v1/v2)
}
func Call2(v1,v2 int,str string){fmt.Println(v1+v2,str)
}//定义一个通用的函数适配器
//通过args...interface{}用于接收多个参数
func bridge(call interface{},args ...interface{}){n:=len(args)//先统计传入的参数长度inValue:=make([]reflect.Value,n)//根据传入的参数数量 创建一个typ Value的切片for i:=0;i<n;i++{//因为传入的是多个参数,接收到的是切片inValue[i]=reflect.ValueOf(args[i])//循环将接收到的参数 以常量的形式加入到[]reflect.Value切片中}function:=reflect.ValueOf(call)//将传入的函数转换为Value结构体类型function.Call(inValue)//使用Value Call方法直接执行传入的函数,并将[]Value的inValue作为参数交给函数使用}
func main(){bridge(Call1,1,2)bridge(Call2,1,2,"test2")
}
  1. 使用反射操作任意结构体类型:【了解】
package mainimport ("fmt""reflect"
)type user struct{UserId stringName string
}func main(){model := &user{}sv := reflect.ValueOf(model)fmt.Println("reflect.ValueOf",sv.Kind().String())sv =sv.Elem()//可以再这里做一下 指针指定,下面就不用指定了fmt.Println("reflect.ValueOf.Elem",sv.Kind().String())//FiledByName指定要修改的字段//SetString修改字段信息sv.FieldByName("UserId").SetString("12345678")sv.FieldByName("Name").SetString("nickname")fmt.Println("model",model)
}
  1. 使用反射创建并操作结构体
package mainimport ("fmt""reflect"
)type user1 struct{UserId stringName string
}func main(){var(model *user1//st reflect.Type//elem reflect.Value)st := reflect.TypeOf(model)fmt.Println("reflect.Typeof",st.Kind().String())//ptrst = st.Elem()fmt.Println("reflect.Typeof.Elem",st.Kind().String())//struct//定义一个结构体elem := reflect.New(st)//这里的New返回一个Value类型值,该值持有一个指向类型为st的新申请的零值的指针//返回值的Type为PtrTo(st)fmt.Println("reflect.New",elem.Kind().String())//ptrfmt.Println("reflect.New.Elem",elem.Kind().String())//Struct//可以看到我们New出来的Value类型和上面提前定义的结构体类似model = elem.Interface().(*user1)//这里通过转换位空接口后类型推导为*uSer的结构体类型// 并将我们声明出来的这个结构体又赋予给了开头的model结构体变量//但是我们现在这个elem是一个内存地址啊,赋予到了model,那么elem就和model数据同步了elem = elem.Elem() //这里通过Elem这里将内存地址进行调用,类似*ptr//modeL现在拿的是elem给的值,我们还可以通过elem去动态修改其他值//当modeL或elem被修改时,对方也会同步数据elem.FieldByName("UserId").SetString("123456")elem.FieldByName("Name").SetString("tianyi")fmt.Println("model model.name",model,model.Name)
}

17.8 课后作业

请添加图片描述

package mainimport ("fmt""reflect"
)type Cal struct {Num1 intNum2 int
}func (c Cal)GetSub(name string){fmt.Println("tom完成了减法预算,",c.Num2-c.Num1)
}func reflectCal (cal interface{}){rValue := reflect.ValueOf(cal)fmt.Println("reflect.ValueOf",rValue)filedNum := rValue.NumField()for i:=0;i<filedNum;i++{fmt.Printf("filed%d value = %v\n",i,rValue.Field(i))}MethodNum :=rValue.NumMethod()fmt.Printf("a has %d Methods \n",MethodNum)var params []reflect.Valueparams = append(params,reflect.ValueOf("tom"))rValue.Method(0).Call(params)
}func main(){var cal =Cal{Num1:3,Num2:8,}reflectCal(cal)}

http://www.ppmy.cn/news/154193.html

相关文章

Linux学习-磁盘的分区、格式化、检验与挂载

如果想要在系统新增一颗磁盘时&#xff0c;哪些动作需要做的&#xff1a; 对磁盘进行分区&#xff0c;以创建可用的partition&#xff1b;对该partition进行格式化(format)&#xff0c;以创建系统可用的filesystem&#xff1b;若想要仔细一点&#xff0c;则可对刚刚创建好的fil…

OVN Southbound DB简介及其相关命令示例

Southbound DB 里面有如下几张表&#xff1a; Chassis&#xff1a;chassis这个概念, Chassis 是 OVN 新增的概念&#xff0c;OVS 里面没有这个概念。 chassis表的每一行表示一个 HV 或者 VTEP 网关&#xff0c;由 ovn-controller/ovn-controller-vtep 填写&#xff0c;包含 ch…

nginx.conf配置拆解/include功能;

(一) &#xff08;1&#xff09; https://www.cnblogs.com/d0usr/p/12488117.html &#xff08;二&#xff09;实操 &#xff08;1&#xff09; secneodev-PowerEdge-R440:~/quanqing/server/service/flowprobe-agent-service$ docker inspect nginx_monitor [{"Id&quo…

Linux学习笔记16——磁盘的分区、格式化、检验与挂载

目录 一、磁盘的分区、格式化、检验与挂载 1&#xff0c;观察磁盘分区状态 lsblk 列出系统上的所有磁盘列表 blkid 列出设备的 UUID 等参数 parted 列出磁盘的分区表类型与分区信息 2&#xff0c;磁盘分区&#xff1a; gdisk/fdisk gdisk 用 gdisk 新增分区 partprobe …

啃Docker之必备基础管理操作

啃Docker之必备基础管理操作 前言一&#xff1a;环境准备二&#xff1a;镜像的常规操作三&#xff1a;容器的常规操作 前言 对于理论可以看我之前的博客 链接: https://blog.csdn.net/m0_47219942/article/details/108684100. 一&#xff1a;环境准备 关闭防火墙和核心防护 …

SSM 博客系统开发实战

课程简介 SSM 框架即 SpringMVC+Spring+Mybatis,相比 SSH(Struts2+Spring+Hibernate)来说较新,SpringMVC 可与 Spring 更好的整合,Mybatis 相比 Hibernate 使用更简单、轻便,大部分公司都在使用 SSM 这套框架,主要是轻量级、易使用,故本达人课特选择 SSM 框架作为课程…

Redis【有与无】【AC2】Redis 安装、配置、集群搭建

本文章基于Redis 6.0.9版本&#xff0c;对Redis配置进行说明 目录 序&#xff1a; 1.准备阶段 2.安装阶段 3.配置 4.创建集群 5.测试 6.防火墙开放端口&#xff08;选&#xff09; 7.关闭redis 附录 T.术语 T.1.TLS Q.问题 Q.1.致命错误&#xff1a;openssl/ssl.…

分享一些ABP..ABS的广告过滤规则

点开ABS或ABP的设置中心&#xff0c;点以文本编辑&#xff0c;把下面的复制进去&#xff0c;保存 163.com##[id"dlFrame_l"] 里面放ID 规则开始------------------------------------------------------------------------------------ #*(src*http://bes.baidu.…