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 |
或者,不使用指针,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对于的值:
d.Set(reflect.ValueOf(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。)