编程笔记 Golang基础 033 反射的类型与种类
编程笔记 Golang基础 033 反射的类型与种类
- 一、反射的类型和种类
- 二、切片与反射
- 三、集合与反射
- 四、结构体与反射
- 五、指针与反射
- 六、函数与反射
- 小结
反射机制的作用范围涵盖了几乎所有的类型和值的操作层面,它极大地增强了Go语言在运行时对于自身类型系统的探索和操作能力。然而,这种灵活性也带来了性能开销和安全性问题,因此应当谨慎使用,在保证代码简洁性和高效性的前提下选择性地利用反射特性。
一、反射的类型和种类
在Go语言中,反射主要涉及两种核心类型和一个概念——种类(Kind):
-
reflect.Type:
reflect.Type
表示Go程序中的任何类型的元数据或类型描述符。它提供了类型的各种信息,如名称、包路径、方法集以及其底层的种类(Kind)。通过这个类型,你可以获取到一个类型的所有静态信息,但不能直接操作它的值。
-
reflect.Value:
reflect.Value
是对Go程序运行时某个值的反射对象,包含了该值的类型信息和实际的值内容。通过Value
类型,你可以读取并有时甚至是修改变量的值,这依赖于 Value 的具体种类和可寻址性。
-
种类(Kind):
Kind
是reflect.Type
或reflect.Value
中的一个属性,用于标识该类型或值的具体类别。种类是一个枚举类型,包含了一系列预定义的常量,比如reflect.Int
,reflect.String
,reflect.Slice
,reflect.Struct
,reflect.Ptr
,reflect.Interface
等等。种类不仅涵盖了Go语言的基本类型(如整数、浮点数、字符串等),还包括复合类型如数组、切片、映射、函数、接口、结构体以及指针等。
例如,如果你有一个变量 i int
,你可以使用 reflect.TypeOf(i)
得到 i
的 reflect.Type
对象,表示它是 int
类型;而 reflect.ValueOf(i)
会得到 i
的 reflect.Value
对象,可以用来检查或操作其具体的数值。通过 value.Kind()
方法,你可以进一步得知 i
的种类是 reflect.Int
。
二、切片与反射
在Go语言中,切片(slices)是一种灵活的数据结构,它提供了对数组的动态视图。切片不拥有数据,而是指向底层数组的一个连续片段,并且包含三个信息:指针、长度和容量。
type sliceHeader struct {Data uintptrLen intCap int
}
通过反射,可以对切片进行更深层次的操作:
- 获取切片类型和值:
使用reflect.TypeOf
和reflect.ValueOf
函数可以分别获取切片的类型信息和反射值对象。
s := []int{1, 2, 3}
typ := reflect.TypeOf(s)
val := reflect.ValueOf(s)
- 操作切片元素:
反射允许你访问并修改切片中的元素。不过要注意,只有当反射值是可设置(val.CanSet()
返回true
)的时候才能修改其元素。
if val.CanSet() {index := 0elem := val.Index(index)elem.SetInt(42) // 将第0个元素设为42
}
- 整体修改切片内容:
如果你想替换整个切片的内容,可以通过反射的Value.Set
方法来实现,前提是你有一个同样类型的可设置的切片值。
newSlice := []int{4, 5, 6}
val.Set(reflect.ValueOf(newSlice))
-
切片扩容与append操作:
虽然反射包本身并没有提供直接针对切片扩容的方法,但你可以模拟append
的行为,通过创建新的切片并复制原有元素以及添加新元素。 -
检查切片的长度和容量:
通过反射的Value.Len()
和Value.Cap()
方法可以得到切片的长度和容量。
length := val.Len()
capacity := val.Cap()
总的来说,在Go语言中,反射与切片结合使用时,可以在运行时动态地操作和分析切片的各种属性和内容,为程序带来更高的灵活性,但也需要注意反射操作的性能开销和安全性问题。
三、集合与反射
在Go语言中,集合通常指的是类似键值对的数据结构,最常用的集合实现是map
(映射),它是一个无序的键值对集合,可以通过键快速检索到对应的值。Go语言中的map
使用哈希表来实现,因此提供了高效的查找、更新和删除操作。
反射与集合(如map
)在Go中的结合使用可以实现一些动态的操作,例如:
- 检查类型的集合属性:
通过反射,可以获取到一个类型是否为map
类型,以及其键和值的具体类型。
typ := reflect.TypeOf(someValue)
if typ.Kind() == reflect.Map {keyType := typ.Key()valueType := typ.Elem()// 现在你知道了这个映射的键和值是什么类型
}
- 访问和修改映射内容:
反射允许你通过运行时类型信息动态地访问和修改映射的内容。
val := reflect.ValueOf(someMap)
for _, key := range val.MapKeys() {value := val.MapIndex(key)fmt.Println("Key:", key.Interface(), "Value:", value.Interface())// 修改映射值,前提是可以设置if value.CanSet() {newValue := reflect.ValueOf(newValueObject)val.SetMapIndex(key, newValue)}
}
-
创建新的映射实例:
使用反射还可以根据已知的键值类型动态创建新的映射实例。 -
处理接口类型包含映射的情况:
当遇到接口类型变量实际存储的是映射时,反射尤其有用,因为需要通过反射来“解包”出具体的映射类型和值。
总之,在Go语言中,反射机制使得程序可以在运行时获得类型及其值的详细信息,并进行动态操作,这对于集合类数据结构(比如映射)来说意味着更大的灵活性。然而,反射由于性能开销较大且可能导致不安全的操作,因此在设计代码时应当谨慎使用。
四、结构体与反射
在Go语言中,结构体(struct)是一种复合数据类型,它允许你将多个不同类型的字段封装到一个单一的类型中。反射机制可以与结构体紧密配合,以动态的方式在运行时检查和操作结构体的各种属性。
以下是如何使用Go中的反射来处理结构体:
- 获取结构体类型信息:
使用reflect.TypeOf
函数可以获得结构体类型的反射对象。
type Person struct {Name stringAge int
}p := Person{"Alice", 30}
typ := reflect.TypeOf(p)
- 获取结构体值信息:
使用reflect.ValueOf
函数可以得到结构体实例的反射值对象。
value := reflect.ValueOf(p)
- 遍历结构体字段:
可以通过NumField()
方法获取结构体字段数量,并用Field(i)
方法访问每个字段的信息。
for i := 0; i < typ.NumField(); i++ {field := typ.Field(i)fmt.Printf("Field name: %s, Type: %v\n", field.Name, field.Type)fieldValue := value.Field(i)fmt.Printf("Field value: %v\n", fieldValue.Interface())
}
- 读取和修改结构体字段的值:
如果结构体变量是可设置的(即不是指向结构体的指针的零值或者未导出字段),可以通过反射来读取或修改其字段值。
if fieldValue.CanSet() {// 修改字段值,假设字段类型为intfieldValue.SetInt(35)
}
- 处理结构体标签(Tags):
结构体字段可以包含标签,如JSON、XML等序列化标签,反射能让我们在运行时解析这些标签。
tag := field.Tag.Get("json")
fmt.Println("JSON tag:", tag)
- 调用结构体方法:
若结构体有方法,反射还能用于动态地调用这些方法。
总之,通过反射机制,Go程序可以在编译期未知具体结构体细节的情况下,在运行时探索并操作任何结构体类型的实例,这在实现通用工具函数、动态数据处理、序列化/反序列化以及某些高级设计模式时非常有用。然而,由于反射会增加代码复杂性和可能带来性能损失,因此应当谨慎使用。
五、指针与反射
在Go语言中,指针和反射机制结合使用可以实现更复杂的动态类型操作。指针允许我们间接访问内存中的数据,而反射则提供了在运行时检查和修改任意类型的对象的能力。
- 通过指针获取反射值:
在Go中,如果要对非接口类型的变量进行反射操作,通常需要先获取其指针的反射值,然后通过reflect.Value.Elem()
方法获取指向的元素(即解引用)的反射值。
var i int = 42
ptr := &i
value := reflect.ValueOf(ptr).Elem() // 获取指针所指向的int类型的反射值
fmt.Println(value.Interface()) // 输出: 42
- 修改指针指向的值:
如果反射值是可设置的,可以通过它来改变原始指针指向的数据。
if value.CanSet() {value.SetInt(1337) // 将int类型的值设为1337
}
fmt.Println(i) // 输出:1337
- 处理结构体指针:
对于结构体类型的指针,反射可以帮助我们遍历并修改结构体字段,即使这些字段是不可导出的(私有字段)。
type Person struct {name stringage int
}p := &Person{"Alice", 30}
v := reflect.ValueOf(p).Elem()// 修改字段
nameField := v.FieldByName("name")
if nameField.IsValid() && nameField.CanSet() {nameField.SetString("Bob")
}ageField := v.FieldByName("age")
if ageField.IsValid() && ageField.CanSet() {ageField.SetInt(35)
}
- 创建新的指针值:
虽然反射不直接提供创建新指针的功能,但你可以通过分配一个新的底层类型实例,并获取其地址来间接创建。
newType := reflect.TypeOf(Person{})
newValue := reflect.New(newType).Elem()
总之,在Go语言中,反射与指针一起工作时,能够让我们在运行时更加灵活地操作程序中的数据结构,包括读取、修改甚至创建它们。不过需要注意的是,过度或不恰当使用反射可能导致代码难以理解和维护,同时可能带来性能损失。
六、函数与反射
在Go语言中,反射不仅可以用于处理变量和结构体,还可以与函数进行交互。通过反射机制,可以动态地调用函数、获取函数信息以及实现更高级的动态编程技术。
- 获取函数类型:
使用reflect.TypeOf
函数可以获得一个函数类型的反射对象。
func add(a, b int) int {return a + b
}typ := reflect.TypeOf(add)
fmt.Println(typ.String()) // 输出: func(int, int) int
- 调用函数:
通过反射,可以动态地调用具有已知签名的函数。这通常涉及到将参数转换为reflect.Value
类型,并使用Value.Call()
方法执行调用。
fn := reflect.ValueOf(add)// 创建参数列表
params := []reflect.Value{reflect.ValueOf(3), reflect.ValueOf(5)}result := fn.Call(params)
fmt.Println(result[0].Interface()) // 输出: 8
- 检查函数接收者:
如果函数是方法,可以通过反射来获取其接收者类型:
type MyType struct{}
func (m MyType) MyMethod() {}method := reflect.ValueOf(MyType{}.MyMethod)
receiverType := method.Type().NumIn()
if receiverType > 0 {fmt.Println(method.Type().In(0)) // 输出: main.MyType
}
- 获取函数返回值数量和类型:
可以通过FuncType.NumOut()
获取函数返回值的数量,并通过FuncType.Out(i)
获取第i个返回值的类型。
numReturns := typ.NumOut()
for i := 0; i < numReturns; i++ {returnType := typ.Out(i)fmt.Println("Return type:", returnType.String())
}
- 封装接口调用:
反射常被用来处理空接口(interface{})类型的值,尤其是当需要根据具体类型调用不同函数时。
总的来说,在Go语言中,反射机制允许程序在运行时访问并操作函数的相关信息,包括但不限于调用函数、分析函数签名等。然而,由于反射操作相对常规编译期确定的操作来说较为复杂且可能影响性能,因此在设计代码时应当谨慎考虑是否真的有必要使用反射来处理函数。
小结
反射机制在Go语言中的作用范围主要体现在以下几个方面:
-
类型信息的获取:
反射允许程序在运行时动态地获取变量或类型的详细信息,包括但不限于:- 类型名称
- 类型是否为指针、数组、切片、映射、函数、结构体等不同种类(Kind)
- 结构体字段名、字段数量和字段类型
- 函数签名:参数列表及其类型以及返回值类型
- 标签信息,如JSON标签或其他自定义标签
-
值操作:
通过反射可以读取并可能修改任何可寻址变量的值,这包括基础类型、复合类型(如结构体)以及接口类型的值。只要该变量是可设置的(reflect.Value.CanSet()
返回true
),就可以进行赋值操作。 -
方法调用:
反射支持对任意具有方法的对象在运行时动态调用其方法,即使在编译时并不知道具体的对象类型。 -
动态创建类型实例:
使用反射创建新类型实例,例如动态生成一个结构体实例或者根据给定的类型描述符创建一个新的空接口(interface{})实例。 -
集合类型的动态处理:
对于数组、切片、映射等集合类型,反射可以用于遍历元素、添加或删除元素等操作。 -
实现通用工具与框架:
在API库封装、ORM框架、序列化/反序列化工具、测试框架等方面,反射被广泛应用于处理不同类型的数据结构,使得代码能够以统一的方式处理未知的具体类型。 -
跨包私有成员访问:
虽然不推荐这样做,但反射确实提供了在运行时访问甚至修改其他包中未导出(私有)字段的能力。
总之,反射机制的作用范围涵盖了几乎所有的类型和值的操作层面,它极大地增强了Go语言在运行时对于自身类型系统的探索和操作能力。然而,这种灵活性也带来了性能开销和安全性问题,因此应当谨慎使用,在保证代码简洁性和高效性的前提下选择性地利用反射特性。
相关文章:
编程笔记 Golang基础 033 反射的类型与种类
编程笔记 Golang基础 033 反射的类型与种类 一、反射的类型和种类二、切片与反射三、集合与反射四、结构体与反射五、指针与反射六、函数与反射小结 反射机制的作用范围涵盖了几乎所有的类型和值的操作层面,它极大地增强了Go语言在运行时对于自身类型系统的探索和操…...

MySQL进阶篇2-索引的创建和使用以及SQL的性能优化
索引 mkdir mysql tar -xvf mysqlxxxxx.tar -c myql cd mysql rpm -ivh .....rpm yum install openssl-devel systemctl start mysqld gerp temporary password /var/log/mysqld.log mysql -u root -p mysql> show variables like validate_password.% set glob…...

基于SVM的功率分类,基于支持向量机SVM的功率分类识别,Libsvm工具箱详解
目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 完整代码和数据下载链接:基于SVM的功率分类,基于支持向量机SVM的功率分类识别资源-CSDN文库 https://download.csdn.net/download/abc991835105/88862836 SVM应用实例, 基于…...

【IO流】FileWrite字符输出流
FileWrite字符输出流 1. 概述2. 作用3. 方法4. 细节5. 代码示例6. 注意事项 1. 概述 java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。 FileWriter 是用于写入字符数据到文件的字符输出流。 2. 作用 写入字符数据:…...

WPF 【十月的寒流】学习笔记(1):DataGrid过滤
文章目录 相关链接代码仓库前言环境DataGrid 数据筛选项目配置使用原理主要代码(详细代码可以看我的GitHub仓库)Models.PersonDataGirdViewDataGridViewModel 实现效果 DataGrid直接绑定CollectionViewxamlViewModel 总结 相关链接 十月的寒流 在 WPF 中…...

当Vue项目启动后,通过IP地址方式在相同网络段的其他电脑上无法访问前端页面?
当Vue项目启动后,通过IP地址方式在相同网络段的其他电脑上无法访问前端页面,可能是由以下几个原因造成的: 服务监听地址:默认情况下,许多开发服务器(如Vue CLI的vue-cli-service serve)只监听lo…...
native sql -ABAP开发从入门到精通笔记
Native SQL SQL概要 OPEN SQL读取数据 Select Select <lines> <columns>... Select signle <cols>.... where. 列去重数据 Select distinct <cols>... where... 当取多条数据时,select结果会保存到内表中。 Select ... into...语句的结果不…...
C++完美转发【gpt】
提问:用极其通俗易懂的文字解释C的完美转发 ChatGPT 好的,让我用非常通俗易懂的方式解释一下 C 中的完美转发。 假设你有一个房子(函数),你想把家具(参数)从一个房间(函数&#x…...

C++之类和对象(2)
目录 1.类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3.析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5.赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 2. 赋值运算符只能重载成类的成员函数不能重载成全局函数 3. 用户没有显式实现时&…...

时间序列分析实战(四):Holt-Winters建模及预测
🍉CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一|统计学|干货分享 擅长Python、Matlab、R等主流编程软件 累计十余项国家级比赛奖项,参与研究经费10w、40w级横向 文…...
Springboot之集成MongoDB无认证与开启认证的配置方式
Springboot之集成MongoDB无认证与开启认证的配置方式 文章目录 Springboot之集成MongoDB无认证与开启认证的配置方式1. application.yml中两种配置方式1. 无认证集成yaml配置2. 有认证集成yaml配置 2. 测试1. 实体类2. 单元测试3. 编写Controller测试 1. application.yml中两种…...

BLEU: a Method for Automatic Evaluation of Machine Translation
文章目录 BLEU: a Method for Automatic Evaluation of Machine Translation背景和意义技术原理考虑 n n n - gram中 n 1 n1 n1 的情况考虑 n n n - gram中 n > 1 n\gt 1 n>1 的情况考虑在文本中的评估初步实验评估和结论统一不同 n n n 值下的评估数值考虑句子长度…...
代码随想录算法训练营|day42
第九章 动态规划 416.分割等和子集代码随想录文章详解 背包类型求解方法0/1背包外循环nums,内循环target,target倒序且target>nums[i]完全背包外循环nums,内循环target,target正序且target>nums[i]组合背包外循环target,内循环nums,target正序且target>nums[i] 416.分…...

vscode与vue/react环境配置
一、下载并安装VScode 安装VScode 官网下载 二、配置node.js环境 安装node.js 官网下载 会自动配置环境变量和安装npm包(npm的作用就是对Node.js依赖的包进行管理),此时可以执行 node -v 和 npm -v 分别查看node和npm的版本号: 配置系统变量 因为在执…...

Vue前端对请假模块——请假开始时间和请假结束时间的校验处理
开发背景:Vueelement组件开发 业务需求:用户提交请假申请单,请假申请的业务逻辑处理 实现:用户选择开始时间需要大于本地时间,不得大于请假结束时间,请假时长根据每日工作时间实现累加计算 页面布局 在前…...
搭建freqtrade量化交易机器人
本文采用python量化机器人框架 freqtrade 开始操作! freqtrade官方文档 官方文档内容过多,请先跟随本文入门阅读,后续深入学习可参考官方文档~ 1. 准备云服务器 docker 环境 这里以云服务器选择 ubuntu 系统开始,先…...

php伪协议 [SWPUCTF 2022 新生赛]ez_ez_php(revenge)
打开题目 题目源代码如下 <?php error_reporting(0); if (isset($_GET[file])) {if ( substr($_GET["file"], 0, 3) "php" ) {echo "Nice!!!";include($_GET["file"]);} else {echo "Hacker!!";} }else {highlight_fi…...

1.1_1 计算机网络的概念、功能、组成和分类
文章目录 1.1_1 计算机网络的概念、功能、组成和分类(一)计算机网络的概念(二)计算机网络的功能(三)计算机网络的组成1.组成部分2.工作方式3.功能组成 (四)计算机网络的分类 总结 1.…...
pytorch中的各种计算
对tensor矩阵的维度变换,加减乘除等是深度学习中的常用操作,本文对一些常用方法进行总结 矩阵乘法 混合矩阵相乘,官网 torch.matmul(input, other, *, outNone) → Tensor这个方法执行矩阵相乘操作,需要第一个矩阵的最后一个维度…...

大数据技术之 Kafka
大数据技术之 Kafka 文章目录 大数据技术之 Kafka第 1 章 Kafka 概述1.1 定义1.2 消息队列1.2.1 传统消息队列的应用场景1.2.2 消息队列的两种模式 1.3 Kafka 基础架构 第 2 章 Kafka 快速入门2.1 安装部署2.1.1 集群规划2.1.2 集群部署2.1.3 集群启停脚本 2.2 Kafka 命令行操作…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...