go语言基础 -- 反射

news2024/11/23 12:44:55

反射的基本介绍

  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 nt

func 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 float32
    Sex 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 = Name
    s.Age = Age
    s.Score = Score
    s.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.Value
    params append(params, reflect.Valueof(10))
    params append(params, reflect.Valueof(40))
    // Call方法参数类型reflect.Value类型的切片类型,因此上面先构造切片,返回值也是一个切片
    res := rval.Method(0).Call(params)//传入的参数是[]reflect.Value
    fmt.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.Value
        inValue []reflect.Value
        n 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 string
    Name string
}
func TestReflectStruct(t *testing.T) {
    var ( 
        model *user
        sv 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 string
    Name string
}
func TestReflectStructPtr(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类型值,该值持有一个指向类型为type的新申请的零值的指针
    t.Log("reflect.New",elem.Kind().String())//ptr
    t.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.coloradmin.cn/o/1591973.html

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

相关文章

【蓝桥杯】第十五届填空题a.握手问题

题解&#xff1a; 根据问题描述&#xff0c;总共有 50 人参加会议&#xff0c;每个人除了与自己以外的其他所有人握手一次。但有 7 个人彼此之间没有进行握手&#xff0c;而与其他所有人都进行了握手。 首先&#xff0c;计算所有人进行握手的总次数&#xff1a; 总人数为 50 …

LabVIEW电信号傅里叶分解合成实验

LabVIEW电信号傅里叶分解合成实验 电信号的分析与处理在科研和工业领域中起着越来越重要的作用。系统以LabVIEW软件为基础&#xff0c;开发了一个集电信号的傅里叶分解、合成、频率响应及频谱分析功能于一体的虚拟仿真实验系统。系统不仅能够模拟实际电路实验箱的全部功能&…

对给定向量旋转

对给定向量旋转 顺时针&#xff1a; 逆时针&#xff1a; 源码&#xff1a; QPointF rotateVector(const QPointF& dir, double angle, bool flag){double rad (angle * M_PI) / 180;QPointF res;if (flag){float x static_cast<float>(dir.x() * std::cos(rad) …

YOLOv8使用设备摄像头实时监测

代码如下&#xff1a; from ultralytics import YOLO import cv2 from cv2 import getTickCount, getTickFrequency yoloYOLO(./yolov8n.pt)#摄像头实时检测cap cv2.VideoCapture(0) while cap.isOpened():loop_start getTickCount() #记录循环开始的时间&#xff0c;用于计…

Rust腐蚀服务器常用参数设定详解

Rust腐蚀服务器常用参数设定详解 大家好我是艾西&#xff0c;一个做服务器租用的网络架构师上期我们分享了rust腐蚀服务器的windows系统搭建方式&#xff0c;其中启动服务器bat参数因为涉及的东西比较多所以想通过这篇文章给大家做一下详细的分享。 &#xff08;注本文中xxxx…

叉车载货出入库AI检测算法介绍及应用

随着物流行业的快速发展&#xff0c;叉车作为物流运输的重要设备&#xff0c;其安全性和效率性越来越受到人们的关注。然而&#xff0c;在实际操作中&#xff0c;由于人为因素和操作环境的复杂性&#xff0c;叉车事故时有发生&#xff0c;给企业和个人带来了巨大的损失。为了提…

openjudge_2.5基本算法之搜索_1700:八皇后问题

题目 1700:八皇后问题 总时间限制: 10000ms 内存限制: 65536kB 描述 在国际象棋棋盘上放置八个皇后&#xff0c;要求每两个皇后之间不能直接吃掉对方。 输入 无输入。 输出 按给定顺序和格式输出所有八皇后问题的解&#xff08;见Sample Output&#xff09;。 样例输入 样例输…

【python】python饮料销售数据分析可视化(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

【C语言__编译和链接__复习篇2】

目录 前言 一、翻译环境和运行环境 二、翻译环境 2.1 预处理 2.1 编译 2.1.1 词法分析 2.1.2 语法分析 2.1.3 语义分析 2.2 汇编 2.3 链接 三、运行环境 四、简答主线问题 前言 本篇主要讨论以下问题&#xff1a; 主线问题&#xff1a; 1. 源文件(.c)如何转换成(.exe)文件…

Java 基于微信小程序的智能停车场管理小程序

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

二维数组---刷题

一维数组不想更了&#xff0c;弄点二维数组&#xff01; 1.新矩阵 思路 题目简单&#xff0c;6*636&#xff0c;可以得知有36个元素。数组就定义成a[7][7]&#xff0c;难点在与如何找出对角线上的元素。可以画图分析&#xff1a; 通过观察不难发现&#xff0c;元素1&#xff…

Django开发一个学生选课系统

在这个选课系统中&#xff0c;分为管理员和学生两种角色。 学生登录系统以后&#xff0c;只能看到选课信息。管理员登录以后&#xff0c;可以看到选课信息和其他的管理系统。 选课界面如下&#xff1a; 学生管理界面如下&#xff1a; 数据分析界面如下&#xff1a; 数据…

记一次centos合并excel,word,png,pdf为一个整体pdf的入坑爬坑过程(一直显示宋体问题)。

一、背景 原先已经简单实现了excel,word,png,pdf合成一个整体pdf的过程。并将它弄到docker容器中。 1、原先入坑的技术栈 php:7.4 (业务有涉及)php第三方包 setasign\Fpdi\Fpdi : 2.3.6 &#xff08;pdf合并&#xff09;libreoffice : 5.3.6.1ImageMagick: 6.9.10-68 2、…

计算机组成原理【CO】Ch2 数据的表示和应用

文章目录 大纲2.1 数制与编码2.2 运算方法和运算电路2.3 浮点数的表示和运算 【※】带标志加法器OFSFZFCF计算机怎么区分有符号数无符号数? 【※】存储排列和数据类型转换数据类型大小数据类型转换 进位计数制进制转换2的次幂 各种码的基本特性无符号整数的表示和运算带符号整…

@AutoConfigurationPackage 和 @ComponentScan 有何区别?

首先&#xff0c;从名字上看&#xff0c;这两个注解意义特别接近&#xff0c;AutoConfigurationPackage 就是自动配置包&#xff0c;自动配置包的目的是能让系统扫描到包内的 Bean&#xff1b;ComponentScan 则是组件扫描&#xff0c;这个松哥在之前的教程中也多次提到过了&…

MYSQL原理学习篇简记(二)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小周同志&#xff0c;25届双非校招生Java选手&#xff0c;很高兴认识大家 &#x1f4d5;学习出处&#xff1a;本文是学自小林coding (xiaolincoding.com) 网站的MYSQL图解篇 &#x1f525;如果感觉博主的文章还不错的…

初识SpringMVC(SpringMVC学习笔记一)

1 、还是熟悉的配方&#xff0c;先创建一个父Maven项目&#xff08;忘记怎么创建项目了就去前面翻笔记&#xff09;&#xff0c;导入通用的配置依赖 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instan…

MyBatis核心配置文件介绍使用

文章目录 一、environments二、properties三、typeAliases四、mappers五、创建核心配置文件模板&映射文件模板核心配置文件模板映射文件模板 六、总结 一、environments 核心配置文件中的标签必须按照固定的顺序&#xff1a; properties?,settings?,typeAliases?,typeH…

前端学习<四>JavaScript基础——20-函数简介

函数的介绍 函数&#xff1a;就是一些功能或语句的封装。在需要的时候&#xff0c;通过调用的形式&#xff0c;执行这些语句。 补充&#xff1a; 函数也是一个对象 使用typeof检查一个函数对象时&#xff0c;会返回 function 函数的作用&#xff1a; 一次定义&#xff0c;…

物流自动分拣系统激光雷达漫反射板

早在二十世纪六十年代&#xff0c;激光器的诞生为激光雷达技术的发展奠定了基础。随后&#xff0c;激光雷达技术开始应用于各种领域&#xff0c;包括军事、航空、地理勘测等。然而&#xff0c;在物流自动分拣领域&#xff0c;激光雷达的应用相对较晚。 随着物流行业的快速发展和…