go语言中的反射

为什么会引入反射

有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射。

  1. 空接口可以存储任意类型的变量,那我们如何知道这个空接口保存数据的类型是什么?值是什么呢?
  • 可以使用类型断言
  • 可以使用反射实现,也就是在程序运行时动态的获取一个变量的类型信息和值信息
  1. 把结构体序列化成 json 字符串,自定义结构体 Tag 标签的时候就用到了反射
package mainimport ("encoding/json""fmt"
)type Student struct {ID     int    `json:"id"`Gender string `json:"gender"`Name   string `json:"name"`Sno    string `json:"sno"`
}func main() {var s1 = Student{ID: 1, Gender: "男", Name: "李四", Sno: "s0001"}var s, _ = json.Marshal(s1)jsonStr := string(s)fmt.Println(jsonStr)
}
  1. ORM 框架就用到了反射技术

ORM:对象关系映射(Object Relational Mapping,简称 ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。

反射简介

反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

  1. Golang 中反射可以实现以下功能:
  • 反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
  • 如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法、结构体的 tag。
  • 通过反射,可以修改变量的值,可以调用关联的方法
  1. Go 语言中的变量是分为两部分的:
  • 类型信息:预先定义好的元信息。
  • 值信息:程序运行过程中可动态变化的。

在 GoLang 的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。

在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两 部 分 组 成 , 并 且 reflect 包 提 供 了 reflect.TypeOf 和reflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type。

reflect.TypeOf()获取任意值的类型对象

在 Go 语言中,使用 reflect.TypeOf()函数可以接受任意 interface{}参数,可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。

package mainimport ("fmt""reflect"
)func reflectType(x interface{}) {v := reflect.TypeOf(x)fmt.Printf("type:%v\n", v)
}
func main() {var a float32 = 12.5reflectType(a) // type:float32var b int64 = 100reflectType(b) // type:int64
}
  1. type Name 和 type Kind

在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

Go 语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。

package mainimport ("fmt""reflect"
)func reflectType(x interface{}) {t := reflect.TypeOf(x)fmt.Printf("TypeOf:%v Name:%v Kind:%v\n", t, t.Name(), t.Kind())
}type myInt int64
type Person struct {Name stringAge  int
}
type Animal struct {Name string
}func main() {var a *float32 // 指针var b myInt    // 自定义类型var c rune     // 类型别名reflectType(a) // TypeOf:*float32 Name: Kind:ptrreflectType(b) // TypeOf:main.myInt Name:myInt Kind:int64reflectType(c) // TypeOf:int32 Name:int32 Kind:int32var d = Person{Name: "itying", Age: 18}var e = Animal{Name: "小花"}reflectType(d) // TypeOf:main.Person Name:Person Kind:structreflectType(e) // TypeOf:main.Animal Name:Animal Kind:structvar f = []int{1, 2, 3, 4, 5}reflectType(f) // TypeOf:[]int Name: Kind:slice
}

在 reflect 包中定义的 Kind 类型如下:

type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号 8 位整型
Int16 // 有符号 16 位整型
Int32 // 有符号 32 位整型
Int64 // 有符号 64 位整型
Uint // 无符号整型
Uint8 // 无符号 8 位整型
Uint16 // 无符号 16 位整型
Uint32 // 无符号 32 位整型
Uint64 // 无符号 64 位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64 位复数类型
Complex128 // 128 位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)

reflect.ValueOf()

reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相转换。

reflect.Value 类型提供的获取原始值的方法如下:
在这里插入图片描述

  • 通过反射获取原始值案例:
package mainimport ("fmt""reflect"
)func reflectValue(x interface{}) {v := reflect.ValueOf(x)var c = v.Int() + 6 //获取反射的原始值fmt.Println(c)      // 106
}
func main() {var a int64 = 100reflectValue(a)
}
  • 通过反射设置变量的值
    在这里插入图片描述
    想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的 Elem()方法来获取指针对应的值。
package mainimport ("fmt""reflect"
)func reflectSetValue1(x interface{}) {v := reflect.ValueOf(x)if v.Kind() == reflect.Int64 {v.SetInt(200) //修改的是副本,reflect 包会引发 panic}
}
func reflectSetValue2(x interface{}) {v := reflect.ValueOf(x)// 反射中使用 Elem()方法获取指针对应的值if v.Elem().Kind() == reflect.Int64 {v.Elem().SetInt(200)}
}
func main() {var a int64 = 100// reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable valuereflectSetValue2(&a)fmt.Println(a) // 200
}

结构体反射

  1. 与结构体相关的方法

任意值通过 reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField()和 Field()方法获得结构体成员的详细信息。reflect.Type 中与获取结构体成员相关的的方法如下表所示:

在这里插入图片描述
在这里插入图片描述

  1. StructField 类型

StructField 类型用来描述结构体中的一个字段的信息。StructField 的定义如下:

type StructField struct {
// 参见 http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string // Name 是字段的名字
PkgPath string //PkgPath 是非导出字段的包路径,对导出字段该字段为"" Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于 Type.FieldByIndex 时的索引切片
Anonymous bool // 是否匿名字段
}
  1. 结构体反射示例

当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。

  • 获取结构体属性,获取执行结构体方法
package mainimport ("fmt""reflect"
)// student 结构体
type Student struct {Name  string `json:"name"`Age   int    `json:"age"`Score int    `json:"score"`
}func (s Student) GetInfo() string {var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)fmt.Println(str)return str
}
func (s *Student) SetInfo(name string, age int, score int) {s.Name = names.Age = ages.Score = score
}
func (s *Student) Print() {fmt.Println("打印方法...")
}func PrintStructFn(s interface{}) {t := reflect.TypeOf(s)v := reflect.ValueOf(s)if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {fmt.Println("传入的不是结构体")return}//1、通过类型变量里面的 Method 可以获取结构体的方法var tMethod = t.Method(0) //注意fmt.Println(tMethod.Name)fmt.Println(tMethod.Type)//2、通过类型变量获取这个结构体有多少个方法fmt.Println(t.NumMethod())//3、执行方法 (注意需要使用值变量,并且要注意参数)// v.Method(0).Call(nil)v.MethodByName("Print").Call(nil)//4、执行方法传入参数 (注意需要使用值变量,并且要注意参数)var params []reflect.Value //声明了 []reflect.Valueparams = append(params, reflect.ValueOf("张三"))params = append(params, reflect.ValueOf(22))params = append(params, reflect.ValueOf(100))v.MethodByName("SetInfo").Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Value// 5、执行方法获取方法的值info := v.MethodByName("GetInfo").Call(nil)fmt.Println(info)
}
func main() {stu1 := Student{Name: "小明", Age: 15, Score: 98}PrintStructFn(&stu1)
}

运行结果:

GetInfo
func(*main.Student) string
3
打印方法...
姓名:张三 年龄:22 成绩:100
[姓名:张三 年龄:22 成绩:100]
  • 修改结构体方法
package mainimport ("fmt""reflect"
)// student 结构体
type Student struct {Name  string `json:"name"`Age   int    `json:"age"`Score int    `json:"score"`
}func (s Student) GetInfo() string {var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)return str
}// 反射修改结构体属性
func reflectChangeStruct(s interface{}) {t := reflect.TypeOf(s)v := reflect.ValueOf(s)if t.Elem().Kind() != reflect.Struct {fmt.Println("传入的不是结构体指针类型")return}name := v.Elem().FieldByName("Name")name.SetString("李四") // 设置值age := v.Elem().FieldByName("Age")age.SetInt(20) // 设置值
}
func main() {stu1 := Student{Name: "小明", Age: 15, Score: 98}// PrintStructField(stu1)reflectChangeStruct(&stu1)fmt.Println(stu1.GetInfo())
}

运行结果:

姓名:李四 年龄:20 成绩:98

不要乱用反射

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,
原因有以下两个。

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发 panic,
    那很可能是在代码写完的很长时间之后。
  2. 大量使用反射的代码通常难以理解。

参考文献

https://gobyexample.com/

https://www.w3schools.com/go/

https://go.dev/doc/tutorial/

https://www.geeksforgeeks.org/golang-tutorial-learn-go-programming-language/

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac电脑上好用的压缩软件

在Mac电脑上,有许多优秀的压缩软件可供选择,这些软件不仅支持多种压缩格式,还提供了便捷的操作体验和强大的功能。以下是几款被广泛推荐的压缩软件: BetterZip 功能特点:BetterZip 是一款功能强大的压缩和解压缩工具&a…

大学资产管理系统中的下载功能设计与实现

大学资产管理系统是高校信息化建设的重要组成部分,它负责记录和管理学校内所有固定资产的信息。随着信息技术的发展,下载功能成为提高资产管理效率的关键环节之一。 系统架构的设计是实现下载功能的基础。一个良好的系统架构能够确保数据的高效传输和存储…

UnityShader学习笔记——动态效果

——内容源自唐老狮的shader课程 目录 1.原理 2.Shader中内置的时间变量 3.Shader中经常会改变的数据 4.纹理动画 4.1.背景滚动 4.1.1.补充知识 4.1.2.基本原理 4.2.帧动画 4.2.1.基本原理 5.流动的2D河流 5.1.基本原理 5.2.关键步骤 5.3.补充知识 6.广告牌效果 …

Node.js 实现简单爬虫

介绍 爬虫是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。 本文将使用 Nodejs 编写一个简单的爬虫脚本,爬取一个美食网站,获取菜品的标题和图片链接,并以表格的形式输出。 准备工作 1、初始化项目 首先&#xff0…

JVM执行流程与架构(对应不同版本JDK)

直接上图(对应JDK8以及以后的HotSpot) 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭: 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间,堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…

2025蓝桥杯JAVA编程题练习Day3

1.黛玉泡茶【算法赛】 问题描述 话说林黛玉闲来无事,打算在潇湘馆摆个茶局,邀上宝钗、探春她们一起品茗赏花。黛玉素来讲究,用的茶杯也各有不同,大的小的,高的矮的,煞是好看。这不,她从柜子里…

p5r预告信生成器API

p5r预告信生成器API 本人将js生成的p5r预告信使用go语言进行了重写和部署,并开放了其api,可以直接通过get方法获取预告信的png。 快速开始 http://api.viogami.tech/p5cc/:text eg: http://api.viogami.tech/p5cc/persona5 感谢p5r风格字体的制作者和…

VsCode创建VUE项目

1. 首先安装Node.js和npm 通过网盘分享的文件:vsCode和Node(本人电脑Win11安装) 链接: https://pan.baidu.com/s/151gBWTFZh9qIDS9XWMJVUA 提取码: 1234 它们是运行和构建Vue.js应用程序所必需的。 1.1 Node安装,点击下一步即可 …

软件设计模式

目录 一.创建型模式 抽象工厂 Abstract Factory 构建器 Builder 工厂方法 Factory Method 原型 Prototype 单例模式 Singleton 二.结构型模式 适配器模式 Adapter 桥接模式 Bridge 组合模式 Composite 装饰者模式 Decorator 外观模式 Facade 享元模式 Flyw…

Maven架构项目管理工具

1.1什么是Maven 翻译为“专家”,“内行”Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建,依赖管理和项目信息管理。什么是理想的项目构建? 高度自动化,跨平台,可重用的组件,标准化的 什么…

【Linux】25.进程信号(1)

文章目录 1. 信号入门1.1 进程与信号的相关知识1.2 技术应用角度的信号1.3 注意1.4 信号概念1.5 信号处理常见方式概览 2. 产生信号2.1 通过终端按键产生信号2.2 调用系统函数向进程发信号2.3 由软件条件产生信号2.4 硬件异常产生信号2.5 信号保存 3. 阻塞信号3.1 信号其他相关…

第二个Qt开发实例:在Qt中利用GPIO子系统和sysfs伪文件系统实现按钮(Push Button)点击控制GPIO口(效果为LED2灯的灭和亮)

引言 本文承接博文 https://blog.csdn.net/wenhao_ir/article/details/145420998 里的代码,在那里面代码的基础上添加上利用sysfs伪文件系统实现按钮(Push Button)点击控制GPIO口的代码,进而实现LED2灯的灭和亮。 最终的效果是点击下面的LED按钮实现LED…

Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分

上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…

2025.2.5——五、[网鼎杯 2020 青龙组]AreUSerialz 代码审计|反序列化

题目来源:BUUCTF [网鼎杯 2020 青龙组]AreUSerialz 目录 一、打开靶机,整理信息 二、解题思路 step 1:代码审计 step 2:开始解题 突破protected访问修饰符限制 三、小结 一、打开靶机,整理信息 直接得到一串ph…

芯科科技的BG22L和BG24L带来应用优化的超低功耗蓝牙®连接

全新的BG22L为常见蓝牙设备提供强大的安全性和处理能力,而BG24L支持先进的AI/ML加速和信道探测功能 2025年2月6日 – 致力于以安全、智能无线连接技术,建立更互联世界的全球领导厂商Silicon Labs(亦称“芯科科技”,NASDAQ&#x…

iOS 音频录制、播放与格式转换

iOS 音频录制、播放与格式转换:基于 AVFoundation 和 FFmpegKit 的实现 在 iOS 开发中,音频处理是一个非常常见的需求,比如录音、播放音频、音频格式转换等。本文将详细解读一段基于 AVFoundation 和 FFmpegKit 的代码,展示如何实现音频录制、播放以及 PCM 和 AAC 格式之间…

区块链技术:Facebook 重塑社交媒体信任的新篇章

在这个信息爆炸的时代,社交媒体已经成为我们生活中不可或缺的一部分。然而,随着社交平台的快速发展,隐私泄露、数据滥用和虚假信息等问题也日益凸显。这些问题的核心在于传统社交媒体依赖于中心化服务器存储和管理用户数据,这种模…

STM32的HAL库开发---高级定时器---输出比较模式实验

一、高级定时器输出比较模式实验原理 定时器的输出比较模式总共有8种,本文使用其中的翻转模式,当TIMXCCR1TIMXCNT时,翻转OC1REF的电平,OC1REF为输出参考信号,高电平有效,OC1REF信号连接到0C1上面&#xff…

Games104——游戏引擎Gameplay玩法系统:基础AI

这里写目录标题 寻路/导航系统NavigationWalkable AreaWaypoint NetworkGridNavigation Mesh(寻路网格)Sparse Voxel Octree Path FindingDijkstra Algorithm迪杰斯特拉算法A Star(A*算法) Path Smoothing Steering系统Crowd Simu…

Nginx进阶篇 - nginx多进程架构详解

文章目录 1. nginx的应用特点2. nginx多进程架构2.1 nginx多进程模型2.2 master进程的作用2.3 进程控制2.4 worker进程的作用2.5 worker进程处理请求的过程2.6 nginx处理网络事件 1. nginx的应用特点 Nginx是互联网企业使用最为广泛的轻量级高性能Web服务器,其特点是…