第12章,部分字词修订。

pull/26/head
zhliner 2017-08-24 22:31:28 +08:00
parent 560d22cccc
commit ed194fc3cf
4 changed files with 9 additions and 9 deletions

View File

@ -74,7 +74,7 @@ func display(path string, v reflect.Value) {
让我们针对不同类型分别讨论。
**Slice和数组** 两种的处理逻辑是一样的。Len方法返回slice或数组值中的元素个数Index(i)活动索引i对应的元素返回的也是一个reflect.Value如果索引i超出范围的话将导致panic异常这与数组或slice类型内建的len(a)和a[i]操作类似。display针对序列中的每个元素递归调用自身处理我们通过在递归处理时向path附加“[i]”来表示访问路径。
**Slice和数组** 两种的处理逻辑是一样的。Len方法返回slice或数组值中的元素个数Index(i)获得索引i对应的元素返回的也是一个reflect.Value如果索引i超出范围的话将导致panic异常这与数组或slice类型内建的len(a)和a[i]操作类似。display针对序列中的每个元素递归调用自身处理我们通过在递归处理时向path附加“[i]”来表示访问路径。
虽然reflect.Value类型带有很多方法但是只有少数的方法能对任意值都安全调用。例如Index方法只能对Slice、数组或字符串类型的值调用如果对其它类型调用则会导致panic异常。
@ -218,9 +218,9 @@ c.Value = 42
许多Go语言程序都包含了一些循环的数据。让Display支持这类带环的数据结构需要些技巧需要额外记录迄今访问的路径相应会带来成本。通用的解决方案是采用 unsafe 的语言特性我们将在13.3节看到具体的解决方案。
带环的数据结构很少会对fmt.Sprint函数造成问题因为它很少尝试打印完整的数据结构。例如当它遇到一个指针的时候它只是简单打印指针的数字值。在打印包含自身的slice或map时可能卡住但是这种情况很罕见不值得付出为了处理回环所需的开销。
带环的数据结构很少会对fmt.Sprint函数造成问题因为它很少尝试打印完整的数据结构。例如当它遇到一个指针的时候它只是简单打印指针的数字值。在打印包含自身的slice或map时可能卡住但是这种情况很罕见不值得付出为了处理回环所需的开销。
**练习 12.1** 扩展Displayhans使它可以显示包含以结构体或数组作为map的key类型的值。
**练习 12.1** 扩展Display函数使它可以显示包含以结构体或数组作为map的key类型的值。
**练习 12.2** 增强display函数的稳健性通过记录边界的步数来确保在超出一定限制放弃递归。在13.3节,我们会看到另一种探测数据结构是否存在环的技术。)
**练习 12.2** 增强display函数的稳健性通过记录边界的步数来确保在超出一定限制放弃递归。在13.3节,我们会看到另一种探测数据结构是否存在环的技术。)

View File

@ -25,7 +25,7 @@ fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
```
每当我们通过指针间接地获取的reflect.Value都是可取地址的即使开始的是一个不可取地址的Value。在反射机制中所有关于是否支持取地址的规则都是类似的。例如slice的索引表达式e[i]将隐式地包含一个指针它就是可取地址的即使开始的e表达式不支持也没有关系。以此类推reflect.ValueOf(e).Index(i)对的值也是可取地址的即使原始的reflect.ValueOf(e)不支持也没有关系。
每当我们通过指针间接地获取的reflect.Value都是可取地址的即使开始的是一个不可取地址的Value。在反射机制中所有关于是否支持取地址的规则都是类似的。例如slice的索引表达式e[i]将隐式地包含一个指针它就是可取地址的即使开始的e表达式不支持也没有关系。以此类推reflect.ValueOf(e).Index(i)对的值也是可取地址的即使原始的reflect.ValueOf(e)不支持也没有关系。
要从变量对应的可取地址的reflect.Value来访问变量需要三个步骤。第一步是调用Addr()方法它返回一个Value里面保存了指向变量的指针。然后是在Value上调用Interface()方法也就是返回一个interface{}里面包含指向变量的指针。最后如果我们知道变量的类型我们可以使用类型的断言机制将得到的interface{}类型的接口强制转为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了:
@ -37,7 +37,7 @@ px := d.Addr().Interface().(*int) // px := &x
fmt.Println(x) // "3"
```
或者不使用指针而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对的值:
或者不使用指针而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对的值:
```Go
d.Set(reflect.ValueOf(4))

View File

@ -4,9 +4,9 @@
第一个原因是基于反射的代码是比较脆弱的。对于每一个会导致编译器报告类型错误的问题在反射中都有与之相对应的误用问题不同的是编译器会在构建时马上报告错误而反射则是在真正运行到的时候才会抛出panic异常可能是写完代码很久之后了而且程序也可能运行了很长的时间。
以前面的readList函数§12.6为例为了从输入读取字符串并填充int类型的变量而调用的reflect.Value.SetString方法可能导致panic异常。绝大多数使用反射的程序都有类似的风险需要非常小心地检查每个reflect.Value的对值的类型、是否可取地址,还有是否可以被修改等。
以前面的readList函数§12.6为例为了从输入读取字符串并填充int类型的变量而调用的reflect.Value.SetString方法可能导致panic异常。绝大多数使用反射的程序都有类似的风险需要非常小心地检查每个reflect.Value的对值的类型、是否可取地址,还有是否可以被修改等。
避免这种因反射而导致的脆弱性的问题的最好方法是将所有的反射相关的使用控制在包的内部如果可能的话避免在包的API中直接暴露reflect.Value类型这样可以限制一些非法输入。如果无法做到这一点在每个有风险的操作前指向额外的类型检查。以标准库中的代码为例当fmt.Printf收到一个非法的操作数它并不会抛出panic异常而是打印相关的错误信息。程序虽然还有BUG但是会更加容易诊断。
避免这种因反射而导致的脆弱性的问题的最好方法是将所有的反射相关的使用控制在包的内部如果可能的话避免在包的API中直接暴露reflect.Value类型这样可以限制一些非法输入。如果无法做到这一点在每个有风险的操作前指向额外的类型检查。以标准库中的代码为例当fmt.Printf收到一个非法的操作数它并不会抛出panic异常而是打印相关的错误信息。程序虽然还有BUG但是会更加容易诊断。
```Go
fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)"

View File

@ -2,4 +2,4 @@
Go语言提供了一种机制能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
在本章我们将探讨Go语言的反射特性看看它可以给语言增加哪些表达力以及在两个至关重要的API是如何使用反射机制的一个是fmt包提供的字符串格式功能另一个是类似encoding/json和encoding/xml提供的针对特定协议的编解码功能。对于我们在4.6节中看到过的text/template和html/template包它们的实现也是依赖反射技术的。然后反射是一个复杂的内省技术不应该随意使用因此尽管上面这些包内部都是用反射技术实现的但是它们自己的API都没有公开反射相关的接口。
在本章我们将探讨Go语言的反射特性看看它可以给语言增加哪些表达力以及在两个至关重要的API是如何使用反射机制的一个是fmt包提供的字符串格式功能另一个是类似encoding/json和encoding/xml提供的针对特定协议的编解码功能。对于我们在4.6节中看到过的text/template和html/template包它们的实现也是依赖反射技术的。然后反射是一个复杂的内省技术不应该随意使用因此尽管上面这些包内部都是用反射技术实现的但是它们自己的API都没有公开反射相关的接口。