设为首页 加入收藏

TOP

Go语言反射reflect深入理解(一)
2019-03-19 14:09:35 】 浏览:196
Tags:语言 反射 reflect 深入 理解

反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。


支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。


Go程序在运行期使用reflect包访问程序的反射信息


reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。


Go 程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。


通过反射获取类型信息:(reflect.TypeOf()和reflect.Type)


使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。下面通过例子来理解获取类型对象的过程:


代码输出如下:


代码说明如下:


在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。


Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。


种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:


Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。


type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。


Go 语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。


类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。


下面的代码中会对常量和结构体进行类型信息获取。


代码输出如下:


代码说明如下:


通过反射获取指针指向的元素类型:reflect.Elem()


Go 程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:


代码输出如下:


代码说明如下:


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


Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。StructField 的结构如下:


字段说明如下。


下面代码??,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。


反射访问结构体成员类型及信息:


代码输出如下:


代码说明如下:


反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。Go语言中使用 reflect.Value 获取和设置变量的值。


变量、interface{}和reflect.Value是可以相互转换的。这点在实际开发中,会经常碰到。



Go 语言中,使用 reflect.ValueOf() 函数获得值的反射值对象(reflect.Value)。书写格式如下:


reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。reflect.Value 与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。


Go 语言中可以通过 reflect.Value 重新获得原始值。


可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。


下面代码中,将整型变量中的值使用 reflect.Value 获取反射值对象(reflect.Value)。再通过 reflect.Value 的 Interface() 方法获得 interface{} 类型的原值,通过 int 类型对应的 reflect.Value 的 Int() 方法获得整型值。


代码输出如下:


代码说明如下:


反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。


下面代码构造一个结构体包含不同类型的成员。通过 reflect.Value 提供的成员访问函数,可以获得结构体值的各种数据。


反射访问结构体成员的值:


输出结果为:


代码说明如下:


IsNil()和IsValid() -- 判断反射值的空和有效性


反射值对象(reflect.Value)提供一系列方法进行零值和空判定,如下表所示。


下面的例子将会对各种方式的空指针进行 IsNil() 和 IsValid() 的返回值判定检测。同时对结构体成员及方法查找 map 键值对的返回值进行 IsValid() 判定,参考下面的代码。


反射值对象的零值和有效性判断:


输出结果:


代码说明如下:


IsNil() 常被用于判断指针是否为空;IsValid() 常被用于判定返回值是否有效。


使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。


使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。


使用 reflect.Value 修改值的相关方法如下表所示。


以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。


在已知值的类型时,应尽量使用值对应类型的反射设置值。


通过反射修改变量值的前提条件之一:这个值必须可以被寻址。简单地说就是这个变量必须能被修改。示例代码如下:


程序运行崩溃,打印错误


报错意思是:SetInt正在使用一个不能被寻址的值。从 reflect.ValueOf 传入的是 a 的值,而不是 a 的地址,这个 reflect.Value 当然是不能被寻址的。将代码修改一下,重新运行:


代码输出


下面是对代码的分析:


提示


当 reflect.Value 不可寻址时,使用 Addr() 方法也是无法取到值的地址的,同时会发生宕机。虽然说 reflect.Valu

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇C语言实现输出一个回型递增的螺旋.. 下一篇Go语言接口interface深入理解

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目