applicator的英语复数是什么啊?

出现后流行起来的,通过反射可以在运行时获取丰富的类型信息以及更新操作,支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期进行获取,并且有能力修改它们。但是反射带来的问题是代码的可读性差和性能问题,下文会讲到。

Go 语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 /golang/protobuf/proto 使用,在包文档中有详细说明。

4.2 结构体标签的格式

Tag 在结构体字段后方书写的格式如下:

结构体标签由一个或多个键值对组成;键与值使用冒号分隔,值用双引号括起来;键值对之间使用一个空格分隔。

4.3 从结构体标签中获取值

StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:

4.4 结构体标签格式错误导致的问题

编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,示例代码如下:

运行上面的代码会输出一个空字符串,并不会输出期望的 type。

原因:代码第 11 行中,在 json: 和 "type" 之间增加了一个空格,这种写法没有遵守结构体标签的规则,因此无法通过 Tag.Get 获取到正确的 json 对应的值。这个错误在开发中非常容易被疏忽,造成难以察觉的错误。

所以将第 12 行代码修改为下面的样子,则可以正常打印:

5.1 判断反射值对象 value 是否可修改

Go语言中类似 x、x.f[1] 和 *p 形式的表达式都可以表示变量,但是其它如 x + 1 和 f(2) 则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。

对于 reflect.Value 也有类似的区别。有一些 reflect.Value 是可取地址的;其它一些则不可以。考虑以下的声明语句

每当我们通过指针间接地获取的 reflect.Value 都是可取地址的,即使开始的是一个不可取地址的 Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,slice 的索引表达式 e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。

5.2 判定及获取元素的相关方法

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

取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value
对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
返回值能否被修改。要求值可寻址且是导出的字段

返回持有v持有的指针指向的值的Value。如果v持有nil指针,会返回Value零值;如果v不持有指针,会返回v。

5.3 值修改相关方法

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

将值设置为传入的反射值对象的值
使用 bool 设置值。当值的类型不是 bod 时会发生宕机
设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
设置字符串值。当值的类型不是 string 时会发生宕机

1)值可修改条件之一:可被寻址

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

// 声明整型变量a并赋初值 // 获取变量a的反射值对象 // 尝试将a修改为1(此处会发生崩溃)

程序运行崩溃,打印错误:

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

// 声明整型变量a并赋初值 // 获取变量a的反射值对象(a的地址) // 取出a地址的元素(a的值)

当 reflect.Value 不可寻址时,使用 Addr() 方法也是无法取到值的地址的,同时会发生宕机。虽然说 reflect.Value 的 Addr() 方法类似于语言层的&操作;Elem() 方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。

2)值可修改条件之一:被导出

结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:

// 获取dog实例的反射值对象

报错的意思是:SetInt() 使用的值来自于一个未导出的字段。

为了能修改这个值,需要将该字段导出。将 dog 中的 legCount 的成员首字母大写,导出 LegCount 让反射可以访问,修改后的代码如下:

然后根据字段名获取字段的值时,将字符串的字段首字母大写,修改后的代码如下:

再次运行程序,发现仍然报错:

这个错误表示第 13 行构造的 valueOfDog 这个结构体实例不能被寻址,因此其字段也不能被修改。修改代码,取结构体的指针,再通过 reflect.Value 的 Elem() 方法取到值的反射值对象。修改后的完整代码如下:

// 获取dog实例地址的反射值对象 // 取出dog实例地址的元素

值的修改从表面意义上叫可寻址,换一种说法就是值必须“可被设置”。那么,想修改变量值,一般的步骤是:

  1. 取这个变量的地址或者这个变量所在的结构体已经是指针类型。

  2. 通过 Value.Elem() 获得指针值指向的元素值对象(Value),因为值对象(Value)内部对象为指针时,使用 set 设置时会报出宕机错误。

5.4 结构体成员变量的值修改

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

下面是一个解析结构体变量 t 的例子,用结构体的地址创建反射变量,再修改它。

T 中字段名之所以大写,是因为结构体中只有可导出的字段是“可设置”的。

如果我们修改了程序让 s 由 t(而不是 &t)创建,程序就会在调用 SetInt 和 SetString 的地方失败,因为 t 的字段是不可设置的。

Go 反射包中提供了以下几种类型的创建:

reflect 包的相关接口文档参考:

Go语言通过反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用:

// 将函数包装为反射值对象 // 构造函数参数, 传入两个整型值 // 获取第一个返回值, 取整数值
  • 第 14 行,将 add 函数包装为反射值对象。

  • 第 23 行,调用成功后,通过 retList[0] 取返回值的第一个参数,使用 Int 取返回值的整数值。

反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。

反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”

我们调用 reflect.TypeOf(x) 时,x 被存储在一个空接口变量中被传递过去,然后 reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。

Type 和 Value 都有一个名为 Kind 的方法,它会返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice 等。

Value 类型也有一些类似于 Int、Float 的方法,用来提取底层的数据:

其次,反射对象的 Kind 方法描述的是基础类型,而不是静态类型。如果一个反射对象包含了用户定义类型的值,如下所示:

还有一些用来修改数据的方法,比如 SetInt、SetFloat。在介绍它们之前,我们要先理解“可修改性”(settability),这一特性会在下面进行详细说明。

反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”

根据一个 reflect.Value 的反射值对象,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。

然后,我们可以通过断言,恢复底层的具体值:

Go的反射机制可以将“接口类型的变量”转换为“反射类型的对象”,然后再将“反射类型对象”转换过去。

反射第三定律:如果要修改“反射值对象”其值必须是“可写的”

如果运行这段代码,它会抛出异常:

因为变量 v 是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。

反射的工作机制与此相同,如果想通过反射修改变量 x,就要把想要修改的变量的指针传递给反射库。

反射对象 p 是不可写的,因为它是指针类型。需要通过解引用来修改它指向的值。可以调用 Value 类型的 Elem 方法。Elem 方法能够对指针进行“解引用”。

反射规则可以总结为如下几条:

  • 反射可以将“接口类型变量”转换为“反射类型对象”;

  • 反射可以将“反射类型对象”转换为“接口类型变量”;

  • 如果要修改“反射类型对象”,其值必须是“可写的”。

// 通过名字查询类型对象 反射函数调用的参数构造过程非常复杂,构建很多对象会造成很大的内存回收负担。 Call() 方法内部就更为复杂,需要将参数列表的每个值从 reflect.Value 类型转换为内存。 调用完毕后,还要将函数返回值重新转换为 reflect.Value 类型返回。 因此,反射调用函数的性能堪忧。
  • 能使用原生代码时,尽量避免反射操作。
  • 提前缓冲反射值对象,对性能有很大的帮助。
  • 避免反射函数调用,实在需要调用时,先提前缓冲函数参数列表,并且尽量少地使用返回值

我要回帖

更多关于 operator的复数 的文章

 

随机推荐