Go语言圣经 .11.反射

反射

go语言提供了一种机制,能够在运行时更新变量和检查他们的值、调用他们的方法和他们支持的内在操作,而不需要在编译的时候就知道这些变量的具体类型。这种机制被称作反射

reflect.Type 和 reflect.Value

虽然还是有无穷多的类型, 但是它们的kinds类型却是有限的:

  • Bool, String 和 所有数字类型的基础类型;
  • Array 和 Struct 对应的聚合类型;
  • Chan, Func, Ptr, Slice, 和 Map 对应的引用类型;
  • interface 类型;
  • 还有表示空值的Invalid 类型. (空的 reflect.Value 的 kind 即为 Invalid.)

Display 递归打印

  • slice和数组:v.Len(), v.Index(i)
  • 结构体:v.NumField(), v.Type().Field(i)
  • map:v.MapKeys(), v.MapIndex(key)
  • 指针:v.Elem() v.IsNil()
  • 接口:v.Elem() v.IsNil()

示例:编码S表达式

通过reflect.Value修改值

我们可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址

x := 2
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // "3"

或者,不使用指针,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对于的值:

d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"

这里有很多用于基本数据类型的Set方法:SetInt、SetUint、SetString和SetFloat等。

示例:编码S表达式

获取结构体字段标识

显示一个类型的方法集

几点忠告

练习

练习 12.1:

扩展Displayhans,使它可以显示包含以结构体或数组作为map的key类型的值。

练习 12.2:

增强display函数的稳健性,通过记录边界的步数来确保在超出一定限制前放弃递 归。(在13.3节,我们会看到另一种探测数据结构是否存在环的技术。)

练习 12.3:

实现encode函数缺少的分支。将布尔类型编码为t和nil,浮点数编码为Go语言的格式,复数1+2i编码为#C(1.0 2.0)格式。接口编码为类型名和值对,例如("[]int" (1 2 3)),但是这个形式可能会造成歧义:reflect.Type.String方法对于不同的类型可能返回相同的结果。

练习 12.4:

修改encode函数,以上面的格式化形式输出S表达式。

练习 12.5:

修改encode函数,用JSON格式代替S表达式格式。然后使用标准库提供的json.Unmarshal解码器来验证函数是正确的。

练习 12.6:

修改encode,作为一个优化,忽略对是零值对象的编码。

练习 12.7:

创建一个基于流式的API,用于S表达式的解码,和json.Decoder(§4.5)函数功能类似。

练习 12.8

sexpr.Unmarshal函数和json.Unmarshal一样,都要求在解码前输入完整的字节 slice。定义一个和json.Decoder类似的sexpr.Decoder类型,支持从一个io.Reader流解码。修改sexpr.Unmarshal函数,使用这个新的类型实现。

练习 12.9

编写一个基于标记的API用于解码S表达式,参考xml.Decoder(7.14)的风格。你将需要五种类型的标记:Symbol、String、Int、StartList和EndList。

练习 12.10

扩展sexpr.Unmarshal函数,支持布尔型、浮点数和interface类型的解码,使用练习 12.3: 的方案。(提示:要解码接口,你需要将name映射到每个支持类型的 reflect.Type。)