[go学习笔记.第十五章.反射] 1.反射的基本介绍以及实践

news2025/7/12 7:36:54

一.反射的引入以及基本介绍

1.看两个问题

(1).对于结构体的序列化和反序列化,看一段代码

package main

import(
    "fmt"    
    "encoding/josn"
)

type Monster struct {
    Name string `json:"monsterName"`
    Age int `json:"monsterAge"`
    Sal flotat64 `json:"monsterSal"`
    Sex string `json:"monsterSex"`
}

func main() {
    m := Monster {
        Name : "狗子",
        Age : 12,
        Sal : 22.33,
        Sex : "公",
    }
    data, _ := json.Marsha1(m)
    fmt.Println("json results: ", string(data))
}

输出结果:

        json results: {"monsterName": "狗子", Age: 12, Sal : 22.33, Sex : "公"}

思考:

        为什么序列化后,key-val的key值是结构体Tag的值,而不是字段的名称,比如:不是Name而是"monsterName ": "狗子"

引出反射

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

要求如下:

(1).定义了两个匿名函数

test1 = func(v1 int, v2 int) {
    t.Log(v1, v2)
}

test2 = func(v1 int, v2 int, s string) {
    t.Log(v1, v2, s)
}

(2).定义一个适配器函数用作统一处理接口,其大致结构如下:

bridge := func(call interface{}, args ...interface{}){
    //内容
}

//实现调研test1对应的函数
bridge(test1, 1, 2)
//实现调研test2对应的函数
bridge(test2, 1, 2, "test2")

3.反射的基本介绍 

(1).反射可以在运行时动态获取变量的各种信息,比如变量的类型(type ) ,类别(kind)

(2).如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法

(3).通过反射,可以修改变量的值,可以调用关联的方法

(4).使用反射,需要加import ("refect")

(5).示意图,如下:

 

4.反射的应用场景 

(1).不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射:例如以下这种桥接模式:

bridge(funcPtr interface{}, args ...interface{})

第一个参数 funcPtr 以接口的形式传入函数指针,函数参数 args 以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr

(2).对结构体序列化,如果结构体有指定的Tag,也会使用到反射生成对应的字符串

package main

import(
    "fmt"    
    "encoding/josn"
)

type Monster struct {
    Name string `json:"monsterName"`
    Age int `json:"monsterAge"`
    Sal flotat64 `json:"monsterSal"`
    Sex string `json:"monsterSex"`
}

func main() {
    m := Monster {
        Name : "狗子",
        Age : 12,
        Sal : 22.33,
        Sex : "公",
    }
    data, _ := json.Marsha1(m)
    fmt.Println("json results: ", string(data))
}

5.反射重要的函数和概念 

(1).reflect.TypeOf(变量名),获取变量的类型,返回 reflect.Type类型 )

(2).reflect.ValueOf(变最名),获取变量的值,返回 reflectValue 类型,reflectValue 是一个结构体类型。 【看文档】 ,通过reflect.Value,可以获取到关于该变量的很多信息

(3). 变量,interface{},reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到,示意图如下:

 

二.反射的快速入门

1.案例

(1).演示对(基本数据类型、 interface{}、 reflect.Value)进行反射的基本操作代码演示

(2).演示对(结构体类型、 interface{}、 reflect.Value )进行反射的基本操作

代码如下:

package main

import (
    "fmt"
    "reflect"
)

//演示反射
func reflectTest(b interface{})  {
    //通过反射获取传入值的type,kind,值
    //1.先获取到reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rTyp=", rTyp) //rTyp= int

    //2.获取reflect.ValueOf()
    rVal := reflect.ValueOf(b)

    //运算
    n := 12 + rVal.Int()
    n2 := rVal.Float() // panic
    fmt.Println("n=", n) // n=112
    fmt.Println("n2=", n2) // panic: reflect: call of reflect.Value.Float on int Value

    fmt.Printf("rVal=%v, rVal type=%T\n", rVal, rVal)//rVal=100, rVal type=reflect.Value

    //下面将rVal转成interface{}
    iV := rVal.Interface()
    //将interface{}通过类型断言转成需要的类型
    num := iV.(int) //使用类型断言
    fmt.Println("num=", num)

    //3.获取变量对应的Kind(Kind代表Type类型值表示的具体分类。零值表示非法分类。)
    //rVal.Kind()
    kind := rVal.Kind()
    //rTyp.Kind()
    kind2 := rTyp.Kind()
    fmt.Printf("kind = %v, kind2 = %v\n", kind, kind2)
}
//演示结构体的反射
func reflectTest2(b interface{})  {
    //通过反射获取传入值的type,kind,值
    //1.先获取到reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rTyp=", rTyp)

    //2.获取reflect.ValueOf()
    rVal := reflect.ValueOf(b)

    //下面将rVal转成interface{}
    iV := rVal.Interface()
    fmt.Printf("iV=%v, iV type=%T\n", iV, iV)
    //将interface{}通过类型断言转成需要的类型
    //使用一个简单的带检测的类型断言, 还可以使用switch封装成一个方法来判断
    stu, ok := iV.(Student)
    if ok {
        fmt.Printf("stu.Name=%v, stu.Age=%v\n", stu.Name, stu.Age)
    }

    //3.获取变量对应的Kind(Kind代表Type类型值表示的具体分类。零值表示非法分类。)
    //rVal.Kind()
    kind := rVal.Kind()
    //rTyp.Kind()
    kind2 := rTyp.Kind()
    fmt.Printf("kind = %v, kind2 = %v\n", kind, kind2)
}

type Student struct {
    Name string
    Age int 
}

func main() {
    //编写一个案例
    //演示对(基本数据类型,interface{},reflect.Value)进行反射的基本操作
    //1.先定义一个int
    var num int = 100
    reflectTest(num)

    //2.定义一个Student实例
    stu := Student{
        Name : "tom",
        Age : 12,
    }
    reflectTest2(stu)
}

2.注意事项和细节

(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).通过反射可以在让变量在 interface{}和 Reflect.value 之间相互转换,看下是如何在代码中体现

变量<===> interface{} <===> reflect.Value

    //通过反射获取传入值的type,kind,值
    //1.先获取到reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rTyp=", rTyp)

    //2.获取reflect.ValueOf()
    rVal := reflect.ValueOf(b)

    //下面将rVal转成interface{}
    iV := rVal.Interface()
    fmt.Printf("iV=%v, iV type=%T\n", iV, iV)
    //将interface{}通过类型断言转成需要的类型
    //使用一个简单的带检测的类型断言, 还可以使用switch封装成一个方法来判断
    stu, ok := iV.(Student)
    if ok {
        fmt.Printf("stu.Name=%v, stu.Age=%v\n", stu.Name, stu.Age)
    }

(4).使用反射的方式来获取变量的值 (并返回对应的类型),要求数据类型匹配,比如 x 是 int , 那么就应该使用reflect.VaIue (x).Int(),而不能使用其它的,否则报 panic

    //运算
    n := 12 + rVal.Int()
    n2 := rVal.Float() // panic
    fmt.Println("n=", n) // n=112
    fmt.Println("n2=", n2) // panic: reflect: call of reflect.Value.Float on int Value

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

package main

import (
    "fmt"
    "reflect"
)

func reflectTest(b interface{})  {
    //通过反射获取传入值的type,kind,值
    //1.先获取到reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rTyp=", rTyp) //rTyp= *int

    //2.获取reflect.ValueOf()
    rVal := reflect.ValueOf(b)
    //rVal当前的Kind值(是一个指针)
    fmt.Printf("rVal kind = %v\n", rVal)    //rVal kind = 0xc0000aa058
    //3.rVal: rval.Elem() 返回rVal具体指针指向的那个值
    //Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装
    rVal.Elem().SetInt(20)
}
func main() {
    var num int = 100
    reflectTest(&num)
    fmt.Printf("num = %v\n", num)//num = 20
}
package main

import(
    "fmt"
    "reflect"
)

func main() {
    //看看下面是否正确
    var str string = "tom" //ok
    rf := reflect.ValueOf(str) //ok :rf => string
    rf.SetString("mary") //error
    fmt.Println("%v", str)
}
package main

import(
    "fmt"
    "reflect"
)

func main() {
    //正确写法
    var str string = "tom" //ok
    rf := reflect.ValueOf(&str) //ok :rf => string
    rf.Elem().SetString("mary") //ok: str => mary
    fmt.Println("%v", str)//mary
}

三.反射的实践

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

  

package main

import(
    "fmt"
    "reflect"
)

//定义一个Monster的结构体方法
type Monster struct {
    Name string `json:"name"`
    Age int `json:"monster_age"`
    Score float32
    Sex string 
}
//方法:显示s的值
func (s Monster) Print()  {
    fmt.Println("---start---")
    fmt.Println(s)
    fmt.Println("---end---")
}
//方法:返回两个数的和
func (s Monster) GetSum(n1 int, n2 int) int  {
    return n1 + n2
}
//方法:接收4个值,给Monster赋值
func (s Monster) Set(name string, Age int, score float32, sex string)  {
    s.Name = name
    s.Age = Age
    s.Score = score
    s.Sex = sex
}

func TestStruct(a interface{})  {
    //获取reflect.Type 类型
    typ := reflect.TypeOf(a)
    //获取reflect.Value 类型
    val := reflect.ValueOf(a)
    //获取a对应的类别
    kind := val.Kind()
    //如果传入的a不是struct类别,就退出
    if kind != reflect.Struct {
        fmt.Println("except struct")
        return
    }
    //获取该结构体有几个字段
    num := val.NumField()
    fmt.Printf("struct has %d filed\n", num) // 4
    //遍历结构体所有字段
    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)
        }
    }

    //获取该结构体有多少个方法
    numMethod := val.NumMethod()
    fmt.Printf("struct has %d method\n", numMethod) 
    //var parmas [] reflect.Value
    //方法的默认排序是按照函数名来排序的(ASCII码)
    val.Method(1).Call(nil) //获取到第二个方法,并调用它

    //调用结构体的第一个方法Method(0)
    var params []reflect.Value  //声明了 []reflect.Value
    params = append(params, reflect.ValueOf(10))
    params = append(params, reflect.ValueOf(20))
    res := val.Method(0).Call(params)   //传入的参数是 []reflect.Value,返回的是[]reflect.Value
    fmt.Printf("res = %v\n", res[0].Int())//返回结果,返回的是[]reflect.Value
}
func main()  {
    //定义一个Monster实例
    var s Monster = Monster{
        Name: "猫",
        Age : 12,
        Score : 10.11,
        Sex : "公的",
    }
    //将monster实例传递给TestStruct函数
    TestStruct(s)
}

(2).使用反射的方式来获取结构体的 tag 标签,遍历字段的值,修改字段值,调用结构体方法(要求:通过传递地址的方式完成,在前面案例上修改即可)

反射的struct tag核心代码:

        tag := typ.Elem().Field(0).TagGet("json")

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

//定义两个函数
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")

(4).使用反射操作任意结构体类型

type User struct {
    UserId string
    Name string
}

func TestRelectStruct (t *testing.T) {
    var (
        model *User
        sv reflect.Value
    )
    model := &User{}
    sv = reflect.ValueOf(model)
    t.Log("reflect.ValueOf", sv.Kind().String())
    sv = sv.Elem()
    t.Log("reflect.ValueOf.Elem", sv.Kind().String())
    sv.FieldByName("UserId").SetString("123456")
    sv.FieldByName("Name").SetString("nickname")
    t.Log("model", model)
}

(5).使用反射创建并操作结构体

package test

import (
    "testing"
    "reflect"
)

type User struct {
    UserId string
    Name string
}

func TestRelectStruct (t *testing.T) {
    var (
        model *User
        st reflect.Type
        elem reflect.Value
    )

    st = reflect.TypeOf(model) //获取类型 *User
    t.Log("reflect.TypeOf", st.Kind().String()) // ptr
    st = st.Elem() //st指向的类型
    t.Log("reflect.TypeOf.Elem", st.Kind().String()) //struct
    elem = reflect.New(st) // New返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针
    t.Log("reflect.New", elem.Kind().String()) //ptr
    t.Log("reflect.New.Elem", elem.Elem().Kind().String()) //struct
    //model就是创建的User结构体变量(实例)
    model = elem.interface{}.("User") // model就是*User,它的指向和elem是一样的
    elem = elem.Elem() // 取得elem指向的值
    sv.FieldByName("UserId").SetString("123456")
    sv.FieldByName("Name").SetString("nickname")
    t.Log("model model.Name", model, model.Name)
}

实践

要求:

        (1).编写一个Cal结构体,有两个字段 Num ,和 Num2

        (2).方法 GetSub ( name string )

        (3).使用反射遍历Cal结构体所有的字段信息

        (4).使用反射机制完成对 GetSub 的调用,输出形式为: "mary 完成了减法运行, 9-5 = 4"

[上一节][go学习笔记.第十四章.协程和管道] 3.协程配合管道案例以及管道的注意事项和使用细节

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

deque容器(20221115)

1、基本概念 功能&#xff1a;双端数组&#xff0c;可以对头端进行插入删除元素 deque与vector的区别&#xff1a; vector对应头部的插入删除效率低&#xff0c;数据量越大&#xff0c;效率越低 deque对头部的插入删除速度比vector快 vector访问元素速度比deque快 deque内…

sanic 教程

sanic 教程 在Sanic的生命周期流程大致如下&#xff1a; http请求——Sanic解析request——匹配路由——请求中间件——视图函数——响应中间件——http响应 依赖库 sanic21.3.4 sanic-jwt1.7.0 sanic-openapi21.12.0 gunicorn20.1.0 PyMySQL1.0.2 aiomysql0.1.1 DBUtils1.3…

WPS(WSC)中M1 到M8 图解

背景 之前实习的时候就学了Wifi p2p相关的东西&#xff0c;当时找M1到M8的功能把我累惨了&#xff0c;找到的还全是千篇一律的东西&#xff0c;讲的不是很清楚&#xff08;当然原版出书的那个前辈肯定是懂的&#xff09;&#xff0c;但是对我等小白不友好&#xff0c;就萌生了这…

3.线性代数-矩阵

矩阵和Tensor1. Tensor2.矩阵3.线性代数正确打开方式3.1 行视图3.2 列视图4.线性相关和线性无关5. Span、基和子空间(Subspace)6.四个基本的子空间6.1 列空间6.2 零空间6.3 行空间6.4 左零空间6.5 四个基本子空间的关系7.可逆矩阵8.方阵的特征值与特征向量9.特征分解9.1一般矩阵…

【Pytorch with fastai】第 7 章 :训练SOTA的模型

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

Nginx优化方案

目录 一、Nginx返回错误页面 1、HTTP常见状态代码列表 二、Nginx状态页面 1、安装status模块 2、激活status 三、优化并发连接数 1、压力测试软件ab&#xff08;http-tools&#xff09; 2、优化并发连接数 2.1、修改nginx并发数 2.2、修改内核最大文件数量 四、Nginx…

吉莱斯皮随机模拟算法(SSA)(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

【K8S系列】第十讲:Knative 简介

目录 一、 Serverless介绍 二、Knative 介绍 2.1 Knative 的定位 2.2 Knative的组成 2.2.1 Build 构建系统 2.2.2 Serving&#xff1a;服务系统 2.2.3 Eventing&#xff1a;事件系统 补充&#xff1a; 三、总结&#xff1a; 一、 Serverless介绍 在讲Knative之前&a…

【毕业设计】机器视觉手势检测和识别系统 - python 深度学习

文章目录0 前言1 实现效果2 技术原理2.1 手部检测2.1.1 基于肤色空间的手势检测方法2.1.2 基于运动的手势检测方法2.1.3 基于边缘的手势检测方法2.1.4 基于模板的手势检测方法2.1.5 基于机器学习的手势检测方法3 手部识别3.1 SSD网络3.2 数据集3.3 最终改进的网络结构4 最后0 前…

线程池源码解析 2.工作原理与内部结构

线程池源码解析—工作原理与内部结构 工作原理 概述 线程池是线程的池子&#xff0c;本质上是通过单个线程执行多个并发任务&#xff0c;使得尽量少的创建线程&#xff0c;减少开销。在线程池内部&#xff0c;是没有区分核心线程和非核心线程的&#xff0c;是通过 Set 集合的…

拒绝内卷,阿里架构师整理的这份Java核心手册,堪称最强

2022年注定是不寻常的一年&#xff0c;在今年因为疫情以及各大大厂纷纷传来裁员的消息&#xff0c;引得整个互联网圈动荡不堪。腾讯裁员30%、京东、百度、字节等大厂都在纷纷裁员&#xff0c;引的这些中厂和小厂也跟风裁员。 这个时候外部的各种变化愈发证明一个重要的一点&am…

2022.11.7-11.13 AI行业周刊(第123期):技术人员的职业发展在哪里?

篇章一&#xff1a;技术人员的职业发展 上周和大学时的舍友聊天&#xff0c;交流当前大家的生活状态。 我们已经本科毕业将近10年了&#xff0c;他目前也有两个孩子&#xff0c;在湖北的一个地级市中&#xff0c;从事的是通信行业。 不过随着工作的时间越久&#xff0c;他发…

软件测试面试真题 | 黑盒测试和白盒测试的基本概念是什么?

在软件测试的面试中&#xff0c;什么是黑盒测试&#xff0c;什么是白盒测试是特别容易被问到的一个问题。 面试官问出这个问题&#xff0c;其实考察的是大家对于软件测试基础理论的掌握程度。下面来梳理一下这个问题的回答思路。 黑盒测试 黑盒测试会把被测的软件看作是一个…

只会加班的项目经理,迟早被淘汰

早上好&#xff0c;我是老原。 最近看到一个文章的标题「废掉一个人最好的方式&#xff0c;就是让他忙到没时间学习」&#xff0c;具体内容是什么我还没有细读&#xff0c;只看完标题&#xff0c;有一丝心酸和自豪&#xff1a; 有那么一群人&#xff0c;在玻璃渣里找糖吃&…

皮带跑偏检测系统

皮带跑偏检测系统对皮带运行状态进行全天候实时监测&#xff0c;一旦皮带跑偏检测系统监测到现场皮带跑偏、撕裂、堆煤、异物等异常情况时&#xff0c;系统马上开展警报&#xff0c;通知后台&#xff0c;并提醒相关人员及时处置。皮带跑偏检测系统并把警报截屏和视频储存到数据…

附参考文献丨艾美捷Cholesterol胆固醇说明书

Cholesterol胆固醇以固体形式提供。可以通过将胆固醇溶解在所选择的溶剂中来制备储备溶液&#xff0c;该溶剂应使用惰性气体吹扫。胆固醇以约30mg/ml的浓度溶于有机溶剂氯-仿中。 艾美捷Cholesterol胆固醇参数&#xff1a; CAS号&#xff1a;57-88-5 正式名称&#xff1a;&am…

自动驾驶入门:预测

目录 概念 预测方式 障碍物预测 递归神经网络在预测中的应用 轨迹生成 概念 无人车是在许多物体间穿梭行驶&#xff0c;其中许多物体本身就是一直在移动的&#xff0c;比如像其他汽车、自行车、行人。无人车需要预测这些物体的行为&#xff0c;这样才能确保做出最佳决策。…

工作中对InheritableThreadLocal使用的思考

最近在工作中结合线程池使用 InheritableThreadLocal 出现了获取线程变量“错误”的问题&#xff0c;看了相关的文档和源码后在此记录。 1. 先说结论 InheritableThreadLocal 只有在父线程创建子线程时&#xff0c;在子线程中才能获取到父线程中的线程变量&#xff1b;当配合…

coding持续集成

先看看官网的一些操作提示 1、创建SSH密钥对 2、创建制品仓库 看完官网的介绍&#xff0c;持续集成需要提前准备好SSH凭证和制品仓库&#xff0c;下面将让我们动手开始吧 一、创建SSH密钥对 登录服务器控制台&#xff0c;创建 SSH 密钥对。获取私钥对后将其录入至 CODING 中…

Netty源码阅读(2)之——服务端源码梗概

上文我们把客户端源码梗概大致了解了一下&#xff0c;这样再了解服务端源码就轻松一点&#xff0c;我们将从服务端和客户端的区别着手去解析。 目录 区别 ④ ③ ① ⑤ 区别 ④ 客户端&#xff1a;.option(ChannelOption.TCP_NODELAY, true) 在TCP/IP协议中&#xff0c;无论…