fix typo and optimize.

Change-Id: I7b6938936231fd722814984678ffa30402539fd9
This commit is contained in:
fuyc
2016-08-11 17:08:38 +08:00
parent ed57986ea7
commit 8fda418f3a
33 changed files with 128 additions and 126 deletions

View File

@@ -1,6 +1,6 @@
## 12.3. Display递归打印
## 12.3. Display,一个递归的值打印
接下来让我们看看如何改善聚合数据类型的显示。我们并不想完全克隆一个fmt.Sprint函数我们只是构建一个用于调用的Display函数给定一个聚合类型x打印这个值对应的完整结构,同时记录每个发现的每个元素的路径。让我们从一个例子开始。
接下来让我们看看如何改善聚合数据类型的显示。我们并不想完全克隆一个fmt.Sprint函数我们只是构建一个用于调用的Display函数给定任意一个复杂类型 x打印这个值对应的完整结构同时记每个元素的发现路径。让我们从一个例子开始。
```Go
e, _ := eval.Parse("sqrt(A / pi)")
@@ -20,7 +20,7 @@ e.args[0].value.y.type = eval.Var
e.args[0].value.y.value = "pi"
```
在可能的情况下,你应该避免在一个包中暴露和反射相关的接口。我们将定义一个未导出的display函数用于递归处理工作导出的是Display函数它只是display函数简单的包装以接受interface{}类型的参数:
你应该尽量避免在一个包的API中暴露涉及反射的接口。我们将定义一个未导出的display函数用于递归处理工作导出的是Display函数它只是display函数简单的包装以接受interface{}类型的参数:
<u><i>gopl.io/ch12/display</i></u>
```Go
@@ -30,7 +30,7 @@ func Display(name string, x interface{}) {
}
```
在display函数中我们使用了前面定义的打印基础类型——基本类型、函数和chan等——元素值的formatAtom函数但是我们会使用reflect.Value的方法来递归显示聚合类型的每一个成员或元素。在递归下降过程中path字符串从最开始传入的起始值这里是“e”将逐步增长表示如何达到当前值例如“e.args[0].value”
在display函数中我们使用了前面定义的打印基础类型——基本类型、函数和chan等——元素值的formatAtom函数但是我们会使用reflect.Value的方法来递归显示复杂类型的每一个成员。在递归下降过程中path字符串从最开始传入的起始值这里是“e”将逐步增长表示如何达到当前值例如“e.args[0].value”
因为我们不再模拟fmt.Sprint函数我们将直接使用fmt包来简化我们的例子实现。
@@ -74,15 +74,15 @@ 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异常。
虽然reflect.Value类型带有很多方法但是只有少数的方法对任意值都安全调用。例如Index方法只能对Slice、数组或字符串类型的值调用如果对其它类型调用则会导致panic异常。
**结构体:** NumField方法报告结构体中成员的数量Field(i)以reflect.Value类型返回第i个成员的值。成员列表包含了匿名成员在内的全部成员。通过在path添加“.f”来表示成员路径我们必须获得结构体对应的reflect.Type类型信息包含结构体类型和第i个成员的名字。
**结构体:** NumField方法报告结构体中成员的数量Field(i)以reflect.Value类型返回第i个成员的值。成员列表也包括通过匿名字段提升上来的成员。为了在path添加“.f”来表示成员路径我们必须获得结构体对应的reflect.Type类型信息然后访问结构体第i个成员的名字。
**Maps:** MapKeys方法返回一个reflect.Value类型的slice每一个对应map的可以。和往常一样遍历map时顺序是随机的。MapIndex(key)返回map中key对应的value。我们向path添加“[key]”来表示访问路径。我们这里有一个未完成的工作。其实map的key的类型并不局限于formatAtom能完美处理的类型数组、结构体和接口都可以作为map的key。针对这种类型完善key的显示信息是练习12.1的任务。)
**Maps:** MapKeys方法返回一个reflect.Value类型的slice每一个元素对应map的一个key。和往常一样遍历map时顺序是随机的。MapIndex(key)返回map中key对应的value。我们向path添加“[key]”来表示访问路径。我们这里有一个未完成的工作。其实map的key的类型并不局限于formatAtom能完美处理的类型数组、结构体和接口都可以作为map的key。针对这种类型完善key的显示信息是练习12.1的任务。)
**指针:** Elem方法返回指针指向的变量是reflect.Value类型。技术指针是nil这个操作也是安全的在这种情况下指针是Invalid无效类型但是我们可以用IsNil方法来显式地测试一个空指针这样我们可以打印更合适的信息。我们在path前面添加“*”,并用括弧包含以避免歧义。
**指针:** Elem方法返回指针指向的变量依然是reflect.Value类型。即使指针是nil这个操作也是安全的在这种情况下指针是Invalid类型但是我们可以用IsNil方法来显式地测试一个空指针这样我们可以打印更合适的信息。我们在path前面添加“*”,并用括弧包含以避免歧义。
**接口:** 再一次我们使用IsNil方法来测试接口是否是nil如果不是我们可以调用v.Elem()来获取接口对应的动态值,并且打印对应的类型和值。
@@ -157,7 +157,7 @@ Display("os.Stderr", os.Stderr)
// (*(*os.Stderr).file).nepipe = 0
```
要注意的是,结构体中未导出的成员对反射也是可见的。需要当心的是这个例子的输出在不同操作系统上可能是不同的,并且随着标准库的发展也可能导致结果不同。(这也是将这些成员定义为私有成员的原因之一!)我们深圳可以用Display函数来显示reflect.Value,来查看`*os.File`类型的内部表示方式`Display("rV", reflect.ValueOf(os.Stderr))`调用的输出如下,当然不同环境得到的结果可能有差异:
可以看出,反射能够访问到结构体中未导出的成员。需要当心的是这个例子的输出在不同操作系统上可能是不同的,并且随着标准库的发展也可能导致结果不同。(这也是将这些成员定义为私有成员的原因之一!)我们甚至可以用Display函数来显示reflect.Value 的内部构造(在这里设置为`*os.File`类型描述体)`Display("rV", reflect.ValueOf(os.Stderr))`调用的输出如下,当然不同环境得到的结果可能有差异:
```Go
Display rV (reflect.Value):
@@ -191,11 +191,11 @@ Display("&i", &i)
// (*&i).value = 3
```
在第一个例子中Display函数调用reflect.ValueOf(i)它返回一个Int类型的值。正如我们在12.2节中提到的reflect.ValueOf总是返回一个值的具体类型,因为它是从一个接口值提取的内容。
在第一个例子中Display函数调用reflect.ValueOf(i)它返回一个Int类型的值。正如我们在12.2节中提到的reflect.ValueOf总是返回一个具体类型的 Value,因为它是从一个接口值提取的内容。
在第二个例子中Display函数调用的是reflect.ValueOf(&i)它返回一个指向i的指针对应Ptr类型。在switch的Ptr分支中通过调用Elem来返回这个值返回一个Value来表示i对应Interface类型。一个间接获得的Value就像这一个,可能代表任意类型的值,包括接口类型。内部的display函数递归调用自身这次它将打印接口的动态类型和值。
在第二个例子中Display函数调用的是reflect.ValueOf(&i)它返回一个指向i的指针对应Ptr类型。在switch的Ptr分支中对这个值调用 Elem 方法返回一个Value来表示变量 i 本身对应Interface类型。像这样一个间接获得的Value可能代表任意类型的值包括接口类型。display函数递归调用自身这次它分别打印了这个接口的动态类型和值。
目前的实现,Display如果显示一个带环的数据结构将会陷入死循环,例如首位项链的链表:
对于目前的实现,如果遇到对象图中含有回环Display将会陷入死循环,例如下面这个首尾相连的链表:
```Go
// a struct that points to itself
@@ -216,10 +216,11 @@ c.Value = 42
...ad infinitum...
```
许多Go语言程序都包含了一些循环的数据结果。Display支持这类带环的数据结构是比较棘手的,需要增加一个额外记录访问的路径;代价是昂贵的。一般的解决方案是采用不安全的语言特性我们将在13.3节看到具体的解决方案。
许多Go语言程序都包含了一些循环的数据。Display支持这类带环的数据结构需要些技巧,需要额外记录迄今访问的路径;相应会带来成本。通用的解决方案是采用 unsafe 的语言特性我们将在13.3节看到具体的解决方案。
带环的数据结构很少会对fmt.Sprint函数造成问题因为它很少尝试打印完整的数据结构。例如当它遇到一个指针的时候它只是简单第打印指针的数值。虽然,在打印包含自身的slice或map时可能遇到困难,但是不保证处理这种是罕见情况却可以避免额外的麻烦
带环的数据结构很少会对fmt.Sprint函数造成问题因为它很少尝试打印完整的数据结构。例如当它遇到一个指针的时候它只是简单第打印指针的数值。在打印包含自身的slice或map时可能卡住,但是这种情况很罕见,不值得付出为了处理回环所需的开销
**练习 12.1** 扩展Displayhans以便它可以显示包含以结构体或数组作为map的key类型的值。
**练习 12.1** 扩展Displayhans使它可以显示包含以结构体或数组作为map的key类型的值。
**练习 12.2** 增强display函数的稳健性通过记录边界的步数来确保在超出一定限制前放弃递归。在13.3节,我们会看到另一种探测数据结构是否存在环的技术。)