3.23 Go之反射
什么是反射
反射是指在程序编译期将变量的信息整合到可执行文件中,给程序提供接口访问反射信息.可以在程序运行期获取类型的反射信息,并且有能力修改它们
反射流行时间:
在java
出现之后流行起来
反射的缺点
反射功能强大但是代码可读性不理想
需要反射的原因
程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
其他语言的反射
-
C/C++
没有支持反射,只能通过typeid
提供非常弱化的程序运行时类型信息 -
java、c#
支持完整的反射功能 -
Lua、JS
类动态语言,由于本身语言特性,不需要反射
Go反射的特点
Go
程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST
)对源码进行扫描后获得这些信息
Go反射(reflection)
Go反射的特点
Go
提供了在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作。但是在编译时不知道这些变量的具体类型
Go的反射包
go
的反射由reflect
包提供,该包提供了函数去获取对应的变量类型和值
该反射包实现的方法
-
定义了两个类型
Type
和Value
,任意接口值在反射中都可以理解为由reflect.Type
和reflect.Vlaue
组成 -
提供
reflect.TypeOf
和reflect.ValueOf
获取任意对象的Type
和Value
反射类型对象获取实例:
package main
import (
"fmt"
"reflect"
)
/*
调用reflect包下的函数获取到变量的值和变量的类型
*/
func main() {
// 声明变量
var a int
// 获取变量的类型
typeOfa := reflect.TypeOf(a)
fmt.Println(typeOfa.Name(), typeOfa.Kind())
}
反射的类型与种类
反射种类(kind)的定义
种类(Kind
)指的是对象归属的品种
示例代码:
package main
import (
"fmt"
"reflect"
)
/*
调用reflect包下的函数获取到变量的值和变量的类型
*/
type i int
func main() {
// 声明变量
var a i
// 获取变量的类型
typeOfa := reflect.TypeOf(a)
fmt.Println(typeOfa.Name(), typeOfa.Kind())
}
分析:
-
首先这段代码的打印结果和上面的代码并不一样.输出的是
i int
-
可以理解为
type
关键字为int
类型起了一个别名,打印的时候只打印了别名 -
使用
kind
获得变量的种类的时候打印的是种类
类型对象获取类型名称和种类
package main
import (
"fmt"
"reflect"
)
/*
从类型对象中获取类型的名称和种类
*/
// 声明一个枚举类型
type Enum int
// 使用该类型定义一个常量
const (
Zero Enum = 0
)
// 声明一个cat结构体类型.分别通过反射获取他们的名称和种类
func main() {
// 声明一个空结构体
type cat struct {
}
// 反射获取结构体的信息
typeOfCat := reflect.TypeOf(cat{})
// 打印名称和种类
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
// 反射获取常量信息
typeOfCon := reflect.TypeOf(Zero)
// 打印名称和种类
fmt.Println(typeOfCon.Name(), typeOfCon.Kind())
}
指针与指针指向的元素
获取指针指向的元素类型--->reflect.Elem()
函数--->等效于对指针类型变量做了一个*
操作
package main
import (
"fmt"
"reflect"
)
/*
调用reflect包下的Elem()函数,取出指针指向的元素类型
*/
func main() {
// 声明一个空结构体
type cat struct {
}
// 声明结构体指针引用变量
ins := &cat{}
// 调用reflect包下的elem()函数获取指针的元素类型
/*
对指针变量获取反射类型信息
*/
typeOfCat := reflect.TypeOf(ins)
// 打印名称和种类
/*
Go语言的反射中对所有指针变量的种类都是Ptr,但需要注意的是,指针变量的类型名称是空,不是 *cat.
*/
fmt.Printf("名称为:%v, 种类为:%v\n", typeOfCat.Name(), typeOfCat.Kind())
// 取元素
/*
取指针类型的元素类型,也就是cat类型.这个操作不可逆,不可以通过一个非指针类型获取它的指针类型.
*/
typeOfCatPointer := typeOfCat.Elem()
// 打印反射类型对象的名称和种类
fmt.Printf("名称为:%v, 种类为:%v\n", typeOfCatPointer.Name(), typeOfCatPointer.Kind())
}
反射获取结构体的成员类型
反射获取到的对象是结构可以通过以下函数获取结构体成员的详细信息
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引返回索引对应的结构体字段的信息,当值不是结构体或索引超界时发生宕机 |
NumField() int | 返回结构体成员字段数量,当类型不是结构体或索引超界时发生宕机 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值。当类型不是结构体或索引超界时发生宕机 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段,当值不是结构体或索引超界时发生宕机 |
结构体字段类型
定义结构体
/*
定义一个结构体
*/
type structField struct {
Name string// 字段名
PkgPath string// 字段路径
Offset uintptr// 字段在结构体中的相对偏移
Index []int// Type.FieldByIndex中的返回的索引值--->FieldByIndex中的索引顺序.
Anonymous bool // 是否为匿名字段
}
获取成员反射信息
示例代码:
-
实例化结构体
-
通过
reflect.Type
的FieldByName()
方法查找结构体中指定字段名
package main
import (
"fmt"
"reflect"
)
/*
定义一个结构体
*/
type structField struct {
Name string// 字段名
PkgPath string// 字段路径
//Type Type// 字段反射类型对象
//Tag StructTag// 字段的结构体标签
Offset uintptr// 字段在结构体中的相对偏移
Index []int// Type.FieldByIndex中的返回的索引值--->FieldByIndex中的索引顺序.
Anonymous bool // 是否为匿名字段
}
func main() {
// 声明结构体
type cat struct {
Name string
Type int `json:"type" id:"100"` //这是一个标签
}
// 创建cat的实例
ins := cat{Name: "Mikey", Type: 1}
// 获取实例和反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 循环遍历结构体的成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取结构体内每个成员的字段类型
fieldType := typeOfCat.Field(i)
// 输出名字和标签
fmt.Printf("名称:%v 标签:%v\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 从标签中取出需要的标签
fmt.Printf("标签值1:%v, 标签值2:%v\n", catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
结构体标签(Struct Tag)
什么是标签?
结构体标签是对结构体字段得额外信息标签
标签的使用场景:
JSON、BSON
等格式进行序列化及对象关系映射(Object Relational Mapping
,简称ORM
)系统都会用到
声明结构体标签的格式:
在结构体字段后:
`key1:"value1" key2:"value2"`
说明:
-
结构体标签由一个或多个键值对组成;
-
键与值使用冒号分隔,值用双引号括起来;
-
键值对之间使用一个空格分隔。
从结构体标签中获取标签值:
StructTag
有一些函数,可以进行Tag
的解析和提取:
-
func (tag StructTag) Get(key string) string
-
func (tag StructTag) Lookup(key string) (value string, ok bool)
/*
根据key判断value是否存在
*/
结构体标签格式错误导致无法获取到对应得值:
json
: 和"type"
之间增加了一个空格,这种写法没有遵守结构体标签的规则,因此无法通过Tag.Get
获取到正确的json
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律