回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,10 +1,10 @@
## 12.1. 何需要反射?
## 12.1. 何需要反射?
候我需要編寫一個函數能夠處理一類併不滿足普通公共接口的型的值,也可能是因爲它們併沒有確定的表示方式,或者是在我們設計該函數的時候還這些類型可能不存在,各種情況都有可能。
候我需要编写一个函数能够处理一类并不满足普通公共接口的型的值,也可能是因为它们并没有确定的表示方式,或者是在我们设计该函数的时候还这些类型可能不存在,各种情况都有可能。
大家熟悉的例子是fmt.Fprintf函提供的字符串格式化處理邏輯,它可以用例任意型的值格式化打印,甚至支持用自定義的類型。讓我們也來嚐試實現一個類似功能的函數。爲了簡單起見,我的函數隻接收一個參數,然後返迴和fmt.Sprint似的格式化的字符串。我們實現的函名也叫Sprint。
大家熟悉的例子是fmt.Fprintf函提供的字符串格式化处理逻辑,它可以用例任意型的值格式化打印,甚至支持用自定义的类型。让我们也来尝试实现一个类似功能的函数。为了简单起见,我的函数只接收一个参数,然后返回和fmt.Sprint似的格式化的字符串。我们实现的函名也叫Sprint。
使用了switch型分支首先來測試輸入參數是否實現了String方法如果是的就使用方法。然後繼續增加類型測試分支,檢査是否是每個基於string、int、bool等基礎類型的動態類型,在每種情況下執行相的格式化操作。
使用了switch型分支首先来测试输入参数是否实现了String方法如果是的就使用方法。然后继续增加类型测试分支,检查是否是每个基于string、int、bool等基础类型的动态类型,在每种情况下执行相的格式化操作。
```Go
func Sprint(x interface{}) string {
@@ -31,6 +31,6 @@ func Sprint(x interface{}) string {
}
```
但是我如何理其它似[]float64、map[string][]string等型呢?我們當然可以添加更多的測試分支,但是這些組合類型的目基本是無窮的。有如何理url.Values等命名的型呢?雖然類型分支可以别出底的基礎類型是map[string][]string但是它不匹配url.Values型,因爲它們是兩種不同的而且switch型分支也不可能包含每個類似url.Values的型,這會導致對這些庫的循環依賴
但是我如何理其它似[]float64、map[string][]string等型呢?我们当然可以添加更多的测试分支,但是这些组合类型的目基本是无穷的。有如何理url.Values等命名的型呢?虽然类型分支可以别出底的基础类型是map[string][]string但是它不匹配url.Values型,因为它们是两种不同的而且switch型分支也不可能包含每个类似url.Values的型,这会导致对这些库的循环依赖
有一方法來檢査未知型的表示方式,我被卡住了。就是我們爲何需要反射的原因。
有一方法来检查未知型的表示方式,我被卡住了。就是我们为何需要反射的原因。

View File

@@ -1,8 +1,8 @@
## 12.2. reflect.Type和reflect.Value
反射是由 reflect 包提供支持. 它定義了兩個重要的型, Type 和 Value. 一 Type 表示一Go型. 它是一接口, 有多方法來區分類型和檢査它們的組件, 例如一個結構體的成或一個函數的參數等. 唯一能反映 reflect.Type 實現的是接口的型描述信息(§7.5), 同樣的實體標識了動態類型的接口值.
反射是由 reflect 包提供支持. 它定义了两个重要的型, Type 和 Value. 一 Type 表示一Go型. 它是一接口, 有多方法来区分类型和检查它们的组件, 例如一个结构体的成或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的型描述信息(§7.5), 同样的实体标识了动态类型的接口值.
reflect.TypeOf 接受任意的 interface{} 型, 併返迴對應動態類型的reflect.Type:
reflect.TypeOf 接受任意的 interface{} 型, 并返回对应动态类型的reflect.Type:
```Go
t := reflect.TypeOf(3) // a reflect.Type
@@ -10,22 +10,22 @@ fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"
```
其中 TypeOf(3) 調用將值 3 作 interface{} 類型參數傳入. 到 7.5將一個具體的值轉爲接口類型會有一個隱式的接口轉換操作, 它會創建一包含兩個信息的接口值: 操作數的動態類型(里是int)和它的動態的值(里是3).
其中 TypeOf(3) 调用将值 3 作 interface{} 类型参数传入. 到 7.5将一个具体的值转为接口类型会有一个隐式的接口转换操作, 它会创建一包含两个信息的接口值: 操作数的动态类型(里是int)和它的动态的值(里是3).
reflect.TypeOf 返的是一個動態類型的接口值, 它是返迴具體的類型. 因此, 下面的代碼將打印 "*os.File" 而不是 "io.Writer". 稍, 我們將看到 reflect.Type 是具有别接口型的表方式功能的.
reflect.TypeOf 返的是一个动态类型的接口值, 它是返回具体的类型. 因此, 下面的代码将打印 "*os.File" 而不是 "io.Writer". 稍, 我们将看到 reflect.Type 是具有别接口型的表方式功能的.
```Go
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"
```
意的是 reflect.Type 接口是滿足 fmt.Stringer 接口的. 因打印動態類型值對於調試和日是有助的, fmt.Printf 提供了一個簡短的 %T 標誌參數, 部使用 reflect.TypeOf 的結果輸出:
意的是 reflect.Type 接口是足 fmt.Stringer 接口的. 因打印动态类型值对于调试和日是有助的, fmt.Printf 提供了一个简短的 %T 标志参数, 部使用 reflect.TypeOf 的结果输出:
```Go
fmt.Printf("%T\n", 3) // "int"
```
reflect 包中另一重要的型是 Value. 一 reflect.Value 可以持有一任意型的值. 函 reflect.ValueOf 接受任意的 interface{} 型, 併返迴對應動態類型的reflect.Value. 和 reflect.TypeOf 似, reflect.ValueOf 返迴的結果也是對於具體的類型, 但是 reflect.Value 也可以持有一接口值.
reflect 包中另一重要的型是 Value. 一 reflect.Value 可以持有一任意型的值. 函 reflect.ValueOf 接受任意的 interface{} 型, 并返回对应动态类型的reflect.Value. 和 reflect.TypeOf 似, reflect.ValueOf 返回的结果也是对于具体的类型, 但是 reflect.Value 也可以持有一接口值.
```Go
v := reflect.ValueOf(3) // a reflect.Value
@@ -34,16 +34,16 @@ fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"
```
和 reflect.Type 似, reflect.Value 也滿足 fmt.Stringer 接口, 但是除非 Value 持有的是字符串, 否 String 是返迴具體的類型. 相同, 使用 fmt 包的 %v 標誌參數, 使用 reflect.Values 的果格式化.
和 reflect.Type 似, reflect.Value 也足 fmt.Stringer 接口, 但是除非 Value 持有的是字符串, 否 String 是返回具体的类型. 相同, 使用 fmt 包的 %v 标志参数, 使用 reflect.Values 的果格式化.
調用 Value 的 Type 方法將返迴具體類型所對應的 reflect.Type:
用 Value 的 Type 方法将返回具体类型所对应的 reflect.Type:
```Go
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"
```
逆操作是調用 reflect.ValueOf 對應的 reflect.Value.Interface 方法. 它返迴一個 interface{} 型表示 reflect.Value 對應類型的具值:
逆操作是用 reflect.ValueOf 对应的 reflect.Value.Interface 方法. 它返回一个 interface{} 型表示 reflect.Value 对应类型的具值:
```Go
v := reflect.ValueOf(3) // a reflect.Value
@@ -52,9 +52,9 @@ i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"
```
reflect.Value 和 interface{} 都能保存任意的值. 所不同的是, 一空的接口藏了值對應的表示方式和所有的公的方法, 因此有我知道具體的動態類型才能使用類型斷言來訪問內部的值(就像上面那), 對於內部值併沒有特别可做的事情. 相比之下, 一 Value 有很多方法來檢査其內容, 無論它的具體類型是什. 讓我們再次嚐試實現我們的格式化函 format.Any.
reflect.Value 和 interface{} 都能保存任意的值. 所不同的是, 一空的接口藏了值对应的表示方式和所有的公的方法, 因此有我知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那), 对于内部值并没有特别可做的事情. 相比之下, 一 Value 有很多方法来检查其内容, 无论它的具体类型是什. 让我们再次尝试实现我们的格式化函 format.Any.
使用 reflect.Value 的 Kind 方法替代之前的型 switch. 雖然還是有無窮多的型, 但是它的kinds類型卻是有限的: Bool, String 和 所有數字類型的基礎類型; Array 和 Struct 對應的聚合型; Chan, Func, Ptr, Slice, 和 Map 對應的引用似; 接口型; 有表示空值的無效類型. (空的 reflect.Value 對應 Invalid 無效類型.)
使用 reflect.Value 的 Kind 方法替代之前的型 switch. 虽然还是有无穷多的型, 但是它的kinds类型却是有限的: Bool, String 和 所有数字类型的基础类型; Array 和 Struct 对应的聚合型; Chan, Func, Ptr, Slice, 和 Map 对应的引用似; 接口型; 有表示空值的无效类型. (空的 reflect.Value 对应 Invalid 无效类型.)
<u><i>gopl.io/ch12/format</i></u>
```Go
@@ -95,7 +95,7 @@ func formatAtom(v reflect.Value) string {
}
```
到目前止, 我的函數將每個值視作一不可分割沒有內部結構的, 因此它叫 formatAtom. 對於聚合型(結構體和數組)個接口是打印型的值, 對於引用型(channels, functions, pointers, slices, 和 maps), 它十六進製打印型的引用地址. 雖然還不夠理想, 但是依然是一重大的步, 且 Kind 隻關心底表示, format.Any 也支持新命名的型. 例如:
到目前止, 我的函数将每个值视作一不可分割没有内部结构的, 因此它叫 formatAtom. 对于聚合型(结构体和数组)个接口是打印型的值, 对于引用型(channels, functions, pointers, slices, 和 maps), 它十六进制打印型的引用地址. 虽然还不够理想, 但是依然是一重大的步, 且 Kind 只关心底表示, format.Any 也支持新命名的型. 例如:
```Go
var x int64 = 1

View File

@@ -1,13 +1,13 @@
## 12.3. Display遞歸打印
## 12.3. Display递归打印
接下來,讓我們看看如何改善聚合數據類型的示。我們併不想完全隆一fmt.Sprint函,我們隻是像建一個用於調式用的Display函數,給定一聚合型x打印這個值對應的完整的結構,同時記録每個發現的每元素的路徑。讓我們從一個例子始。
接下来,让我们看看如何改善聚合数据类型的示。我们并不想完全隆一fmt.Sprint函,我们只是像建一个用于调式用的Display函数,给定一聚合型x打印这个值对应的完整的结构,同时记录每个发现的每元素的路径。让我们从一个例子始。
```Go
e, _ := eval.Parse("sqrt(A / pi)")
Display("e", e)
```
在上面的調用中,入Display函數的參數是在7.9節一個表達式求值函數返迴的語法樹。Display函數的輸出如下:
在上面的用中,入Display函数的参数是在7.9节一个表达式求值函数返回的语法树。Display函数的输出如下:
```Go
Display e (eval.call):
@@ -20,7 +20,7 @@ e.args[0].value.y.type = eval.Var
e.args[0].value.y.value = "pi"
```
在可能的情下,你應該避免在一包中暴露和反射相的接口。我們將定義一個未導出的display函數用於遞歸處理工作,出的是Display函,它是display函數簡單的包以接受interface{}型的參數
在可能的情下,你应该避免在一包中暴露和反射相的接口。我们将定义一个未导出的display函数用于递归处理工作,出的是Display函,它是display函数简单的包以接受interface{}型的参数
<u><i>gopl.io/ch12/display</i></u>
```Go
@@ -30,9 +30,9 @@ 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包來簡化我的例子實現
为我们不再模fmt.Sprint函,我们将直接使用fmt包来简化我的例子实现
```Go
func display(path string, v reflect.Value) {
@@ -72,21 +72,21 @@ 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的可以。和往常一,遍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()來獲取接口對應的動態值,且打印對應的類型和值。
**接口:** 再一次,我使用IsNil方法来测试接口是否是nil如果不是可以用v.Elem()来获取接口对应的动态值,且打印对应的类型和值。
在我的Display函數總算完工了,讓我們看看它的表吧。下面的Movie型是在4.5節的電影類型上演變來的:
在我的Display函数总算完工了,让我们看看它的表吧。下面的Movie型是在4.5节的电影类型上演变来的:
```Go
type Movie struct {
@@ -99,7 +99,7 @@ type Movie struct {
}
```
讓我們聲明一個該類型的量,然看看Display函如何示它:
让我们声明一个该类型的量,然看看Display函如何示它:
```Go
strangelove := Movie{
@@ -125,7 +125,7 @@ strangelove := Movie{
}
```
Display("strangelove", strangelove)調用將顯strangelove電影對應的中文名是《奇博士》):
Display("strangelove", strangelove)调用将显strangelove电影对应的中文名是《奇博士》):
```Go
Display strangelove (display.Movie):
@@ -146,7 +146,7 @@ strangelove.Oscars[3] = "Best Picture (Nomin.)"
strangelove.Sequel = nil
```
也可以使用Display函數來顯示標準庫中類型的內部結構,例如`*os.File`型:
也可以使用Display函数来显示标准库中类型的内部结构,例如`*os.File`型:
```Go
Display("os.Stderr", os.Stderr)
@@ -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):
@@ -174,7 +174,7 @@ Display rV (reflect.Value):
...
```
察下面兩個例子的别:
察下面两个例子的别:
```Go
var i interface{} = 3
@@ -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是返回一个值的具体类型,因它是从一个接口值提取的容。
在第二例子中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
@@ -205,7 +205,7 @@ c = Cycle{42, &c}
Display("c", c)
```
Display會永遠不停地行深度遞歸打印:
Display会永远不停地行深度递归打印:
```Go
Display c (display.Cycle):
@@ -216,10 +216,10 @@ c.Value = 42
...ad infinitum...
```
多Go言程序都包含了一些循環的數據結果。Display支持這類帶環的數據結構是比棘手的,需要增加一個額外的記録訪問的路;代是昂的。一般的解方案是采用不安全的言特性,我們將在13.3看到具的解方案。
多Go言程序都包含了一些循环的数据结果。Display支持这类带环的数据结构是比棘手的,需要增加一个额外的记录访问的路;代是昂的。一般的解方案是采用不安全的言特性,我们将在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,我們會看到另一種探測數據結構是否存在的技。)
**练习 12.2**display函数的稳健性,通过记录边界的步数来确保在超出一定限前放弃递归在13.3,我们会看到另一种探测数据结构是否存在的技。)

View File

@@ -1,10 +1,10 @@
## 12.4. 示例: 編碼S表
## 12.4. 示例: 编码S表
Display是一個用於顯示結構化數據的調試工具,但是它不能任意的Go語言對象編碼爲通用消息然後用於進程間通信。
Display是一个用于显示结构化数据的调试工具,但是它不能任意的Go语言对象编码为通用消息然后用于进程间通信。
正如我在4.5中中看到的Go言的標準庫支持了包括JSON、XML和ASN.1等多種編碼格式。有另一依然被泛使用的格式是S表式格式,采用似Lisp言的法。但是和其他編碼格式不同的是Go言自帶的標準庫併不支持S表式,主要是因爲它沒有一個公認的標準規范。
正如我在4.5中中看到的Go言的标准库支持了包括JSON、XML和ASN.1等多种编码格式。有另一依然被广泛使用的格式是S表式格式,采用似Lisp言的法。但是和其他编码格式不同的是Go言自带的标准库并不支持S表式,主要是因为它没有一个公认的标准规范。
在本中,我們將定義一個包用於將Go言的對象編碼爲S表式格式,它支持以下結構
在本中,我们将定义一个包用于将Go言的对象编码为S表式格式,它支持以下结构
```
42 integer
@@ -13,13 +13,13 @@ foo symbol (an unquoted name)
(1 2 3) list (zero or more items enclosed in parentheses)
```
爾型習慣上使用t符表示true空列表或nil符表示false但是爲了簡單起見,我們暫時忽略布爾類型。同忽略的有chan管道和函,因爲通過反射併無法知道它們的確切狀態。我忽略的還浮點數、複數和interface。支持它們是練習12.3的任
尔型习惯上使用t符表示true空列表或nil符表示false但是为了简单起见,我们暂时忽略布尔类型。同忽略的有chan管道和函,因为通过反射并无法知道它们的确切状态。我忽略的还浮点数、复数和interface。支持它们是练习12.3的任
們將Go言的類型編碼爲S表式的方法如下。整和字符串以自然的方式編碼。Nil值編碼爲nil符號。數組和slice被編碼爲一個列表。
们将Go言的类型编码为S表式的方法如下。整和字符串以自然的方式编码。Nil值编码为nil符号。数组和slice被编码为一个列表。
結構體被編碼爲成員對象的列表,每個成員對象對應一個個僅有兩個元素的子列表,其中子列表的第一元素是成的名字,子列表的第二元素是成的值。Map被編碼爲鍵值對的列表。傳統S表式使用點狀符號列表(key . value)結構來表示key/value,而不是用一個含雙元素的列表,不過爲了簡單我們忽略了點狀符號列表。
结构体被编码为成员对象的列表,每个成员对象对应一个个仅有两个元素的子列表,其中子列表的第一元素是成的名字,子列表的第二元素是成的值。Map被编码为键值对的列表。传统S表式使用点状符号列表(key . value)结构来表示key/value,而不是用一个含双元素的列表,不过为了简单我们忽略了点状符号列表。
編碼是由一encode遞歸函數完成,如下所示。它的結構本質上和前面的Display函數類似:
编码是由一encode递归函数完成,如下所示。它的结构本质上和前面的Display函数类似:
<u><i>gopl.io/ch12/sexpr</i></u>
```Go
@@ -93,7 +93,7 @@ func encode(buf *bytes.Buffer, v reflect.Value) error {
}
```
Marshal函數是對encode的保以保持和encoding/...下其它包有着相似的API
Marshal函数是对encode的保以保持和encoding/...下其它包有着相似的API
```Go
// Marshal encodes a Go value in S-expression form.
@@ -106,7 +106,7 @@ func Marshal(v interface{}) ([]byte, error) {
}
```
下面是Marshal12.3的strangelove變量編碼後的結果:
下面是Marshal12.3的strangelove变量编码后的结果:
```
((Title "Dr. Strangelove") (Subtitle "How I Learned to Stop Worrying and Lo
@@ -118,7 +118,7 @@ ge C. Scott") ("Brig. Gen. Jack D. Ripper" "Sterling Hayden") ("Maj. T.J. \
omin.)" "Best Picture (Nomin.)")) (Sequel nil))
```
個輸出編碼爲一行中以減少輸出的大小,但是也很難閲讀。這里有一個對S表式格式化的定。編寫一個S表式的格式化函數將作爲一個具有挑性的練習任務;不 http://gopl.io 也提供了一個簡單的版本。
个输出编码为一行中以减少输出的大小,但是也很难阅读。这里有一个对S表式格式化的定。编写一个S表式的格式化函数将作为一个具有挑性的练习任务;不 http://gopl.io 也提供了一个简单的版本。
```
((Title "Dr. Strangelove")
@@ -137,16 +137,16 @@ omin.)" "Best Picture (Nomin.)")) (Sequel nil))
(Sequel nil))
```
和fmt.Print、json.Marshal、Display函數類sexpr.Marshal函數處理帶環的數據結構也會陷入死循
和fmt.Print、json.Marshal、Display函数类sexpr.Marshal函数处理带环的数据结构也会陷入死循
在12.6中,我們將給出S表式解器的實現步驟,但是在那之前,我們還需要先了解如果通反射技術來更新程序的量。
在12.6中,我们将给出S表式解器的实现步骤,但是在那之前,我们还需要先了解如果通反射技术来更新程序的量。
**練習 12.3** 實現encode函缺少的分支。將布爾類型編碼爲t和nil點數編碼爲Go言的格式,複數1+2i編碼爲#C(1.0 2.0)格式。接口編碼爲類型名和值,例如("[]int" (1 2 3)),但是這個形式可能造成歧reflect.Type.String方法對於不同的型可能返相同的果。
**练习 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.4** 改encode函,以上面的格式化形式出S表式。
**練習 12.5** 改encode函用JSON格式代替S表式格式。然使用標準庫提供的json.Unmarshal解碼器來驗證函數是正的。
**练习 12.5** 改encode函用JSON格式代替S表式格式。然使用标准库提供的json.Unmarshal解码器来验证函数是正的。
**練習 12.6** 改encode爲一個優化,忽略是零值象的編碼
**练习 12.6** 改encode为一个优化,忽略是零值象的编码
**練習 12.7** 建一個基於流式的APIS表式的解和json.Decoder(§4.5)函功能似。
**练习 12.7** 建一个基于流式的APIS表式的解和json.Decoder(§4.5)函功能似。

View File

@@ -1,10 +1,10 @@
## 12.5. 通reflect.Value改值
## 12.5. 通reflect.Value改值
到目前止,反射還隻是程序中量的另一種訪問方式。然而,在本中我們將重點討論如果通反射機製來脩改變量。
到目前止,反射还只是程序中量的另一种访问方式。然而,在本中我们将重点讨论如果通反射机制来修改变量。
想一下Go言中似x、x.f[1]和*p形式的表式都可以表示但是其它如x + 1和f(2)不是量。一個變量就是一個可尋址的存空,里面存了一值,且存的值可以通過內存地址更新。
想一下Go言中似x、x.f[1]和*p形式的表式都可以表示但是其它如x + 1和f(2)不是量。一个变量就是一个可寻址的存空,里面存了一值,且存的值可以通过内存地址更新。
對於reflect.Values也有似的别。有一些reflect.Values是可取地址的其它一些不可以。考以下的聲明語句:
对于reflect.Values也有似的别。有一些reflect.Values是可取地址的其它一些不可以。考以下的声明语句:
```Go
x := 2 // value type variable?
@@ -14,9 +14,9 @@ c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x)
```
其中a對應的變量則不可取地址。因a中的值僅僅是整2的拷副本。b中的值也同不可取地址。c中的值是不可取地址,它是一個指針`&x`的拷貝。實際上,所有通reflect.ValueOf(x)返的reflect.Value都是不可取地址的。但是對於d它是c的解引用方式生成的指向另一個變量,因此是可取地址的。我可以通過調用reflect.ValueOf(&x).Elem()來獲取任意量x對應的可取地址的Value。
其中a对应的变量则不可取地址。因a中的值仅仅是整2的拷副本。b中的值也同不可取地址。c中的值是不可取地址,它是一个指针`&x`的拷贝。实际上,所有通reflect.ValueOf(x)返的reflect.Value都是不可取地址的。但是对于d它是c的解引用方式生成的指向另一个变量,因此是可取地址的。我可以通过调用reflect.ValueOf(&x).Elem()来获取任意量x对应的可取地址的Value。
可以通過調用reflect.Value的CanAddr方法來判斷其是否可以被取地址:
可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址:
```Go
fmt.Println(a.CanAddr()) // "false"
@@ -25,9 +25,9 @@ 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{}型的接口強製環爲普通的型指針。這樣我們就可以通過這個普通指針來更新量了:
从变量对应的可取地址的reflect.Value来访问变量需要三个步骤。第一步是用Addr()方法,它返回一个Value里面保存了指向量的指。然是在Value上用Interface()方法,也就是返回一个interface{},里面通用包含指向量的指。最,如果我知道量的型,我可以使用型的断言机制将得到的interface{}型的接口强制环为普通的型指针。这样我们就可以通过这个普通指针来更新量了:
```Go
x := 2
@@ -37,20 +37,20 @@ 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))
fmt.Println(x) // "4"
```
Set方法將在運行時執行和編譯時類似的可值性束的檢査。以上代碼,變量和值都是int型,但是如果量是int64型,那程序將拋出一panic常,所以關鍵問題是要保改型的量可以接受對應的值:
Set方法将在运行时执行和编译时类似的可值性束的检查。以上代码,变量和值都是int型,但是如果量是int64型,那程序将抛出一panic常,所以关键问题是要保改型的量可以接受对应的值:
```Go
d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int
```
通用對一個不可取地址的reflect.Value調用Set方法也會導致panic常:
通用对一个不可取地址的reflect.Value用Set方法也会导致panic常:
```Go
x := 2
@@ -58,7 +58,7 @@ b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
```
里有很多用基本數據類型的Set方法SetInt、SetUint、SetString和SetFloat等。
里有很多用基本数据类型的Set方法SetInt、SetUint、SetString和SetFloat等。
```Go
d := reflect.ValueOf(&x).Elem()
@@ -66,7 +66,7 @@ d.SetInt(3)
fmt.Println(x) // "3"
```
從某種程度上説,這些Set方法總是盡可能地完成任。以SetInt例,隻要變量是某種類型的有符號整數就可以工作,使是一些命名的型,要底層數據類型是有符號整數就可以,而且如果對於變量類型值太大的話會被自動截斷。但需要慎的是:對於一個引用interface{}型的reflect.Value調用SetInt會導致panic常,使那interface{}變量對於整數類型也不行。
从某种程度上说,这些Set方法总是尽可能地完成任。以SetInt例,只要变量是某种类型的有符号整数就可以工作,使是一些命名的型,要底层数据类型是有符号整数就可以,而且如果对于变量类型值太大的话会被自动截断。但需要慎的是:对于一个引用interface{}型的reflect.Value用SetInt会导致panic常,使那interface{}变量对于整数类型也不行。
```Go
x := 1
@@ -84,7 +84,7 @@ ry.SetString("hello") // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"
```
當我們用Display示os.Stdout結構時,我們發現反射可以越Go言的導出規則的限製讀取結構體中未出的成,比如在Unix繫統上os.File結構體中的fd int成。然而,利用反射機製併不能脩改這些未出的成
当我们用Display示os.Stdout结构时,我们发现反射可以越Go言的导出规则的限制读取结构体中未出的成,比如在Unix系统上os.File结构体中的fd int成。然而,利用反射机制并不能修改这些未出的成
```Go
stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var
@@ -94,7 +94,7 @@ fmt.Println(fd.Int()) // "1"
fd.SetInt(2) // panic: unexported field
```
可取地址的reflect.Value會記録一個結構體成員是否是未出成,如果是的話則拒絶脩改操作。因此CanAddr方法不能正反映一個變量是否是可以被改的。另一個相關的方法CanSet是用於檢査對應的reflect.Value是否是可取地址可被改的:
可取地址的reflect.Value会记录一个结构体成员是否是未出成,如果是的话则拒绝修改操作。因此CanAddr方法不能正反映一个变量是否是可以被改的。另一个相关的方法CanSet是用于检查对应的reflect.Value是否是可取地址可被改的:
```Go
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"

View File

@@ -1,6 +1,6 @@
## 12.6. 示例: 解S表
## 12.6. 示例: 解S表
標準庫中encoding/...下每包中提供的Marshal編碼函數都有一個對應的Unmarshal函數用於解碼。例如,我在4.5中看到的,要包含JSON編碼格式的字slice數據解碼爲我們自己的Movie§12.3),我可以這樣做:
标准库中encoding/...下每包中提供的Marshal编码函数都有一个对应的Unmarshal函数用于解码。例如,我在4.5中看到的,要包含JSON编码格式的字slice数据解码为我们自己的Movie§12.3),我可以这样做:
```Go
data := []byte{/* ... */}
@@ -8,13 +8,13 @@ var movie Movie
err := json.Unmarshal(data, &movie)
```
Unmarshal函使用了反射機製類脩改movie量的每個成員,根據輸入的內容爲Movie成員創建對應的map、結構體和slice。
Unmarshal函使用了反射机制类修改movie量的每个成员,根据输入的内容为Movie成员创建对应的map、结构体和slice。
現在讓我們爲S表達式編碼實現一個簡易的Unmarshal類似於前面的json.Unmarshal標準庫函數,對應我們之前實現的sexpr.Marshal函的逆操作。我們必須提醒一下,一個健壯的和通用的實現通常需要比例子更多的代碼,爲了便演示我采用了精簡的實現。我們隻支持S表式有限的子集,同時處理錯誤的方式也比粗暴,代的目的是了演示反射的用法,而不是造一個實用的S表式的解器。
现在让我们为S表达式编码实现一个简易的Unmarshal类似于前面的json.Unmarshal标准库函数,对应我们之前实现的sexpr.Marshal函的逆操作。我们必须提醒一下,一个健壮的和通用的实现通常需要比例子更多的代码,为了便演示我采用了精简的实现。我们只支持S表式有限的子集,同时处理错误的方式也比粗暴,代的目的是了演示反射的用法,而不是造一个实用的S表式的解器。
法分析器lexer使用了標準庫中的text/scanner包將輸入流的字節數據解析爲一個個類似註釋、標識符、字符串面值和字面值之類的標記。輸入掃描器scanner的Scan方法提前描和返下一個記號,對於rune型。大多數記號,比如“(”,對應一個單一rune可表示的Unicode字符但是text/scanner也可以用小的負數表示記號標識符、字符串等由多字符成的記號。調用Scan方法將返迴這些記號的類型,接着調用TokenText方法將返迴記號對應的文本容。
法分析器lexer使用了标准库中的text/scanner包将输入流的字节数据解析为一个个类似注释、标识符、字符串面值和字面值之类的标记。输入扫描器scanner的Scan方法提前描和返下一个记号,对于rune型。大多数记号,比如“(”,对应一个单一rune可表示的Unicode字符但是text/scanner也可以用小的负数表示记号标识符、字符串等由多字符成的记号。调用Scan方法将返回这些记号的类型,接着用TokenText方法将返回记号对应的文本容。
爲每個解析器可能需要多次使用前的記號但是Scan一直向前描,所有我們包裝了一lexer描器輔助類型,用於跟蹤最近由Scan方法返迴的記號
为每个解析器可能需要多次使用前的记号但是Scan一直向前描,所有我们包装了一lexer描器辅助类型,用于跟踪最近由Scan方法返回的记号
<u><i>gopl.io/ch12/sexpr</i></u>
```Go
@@ -34,7 +34,7 @@ func (lex *lexer) consume(want rune) {
}
```
現在讓我們轉到語法解析器。它主要包含兩個功能。第一是read函,用於讀取S表式的當前標記,然後根據S表式的當前標記更新可取地址的reflect.Value對應的變量v。
现在让我们转到语法解析器。它主要包含两个功能。第一是read函,用于读取S表式的当前标记,然后根据S表式的当前标记更新可取地址的reflect.Value对应的变量v。
```Go
func read(lex *lexer, v reflect.Value) {
@@ -67,13 +67,13 @@ func read(lex *lexer, v reflect.Value) {
}
```
的S表式使用標識符區分兩個不同型,結構體成員名和nil值的指。read函數值處理nil型的標識符。遇到scanner.Ident“nil”是使用reflect.Zero函數將變量v設置爲零值。而其它任何型的標識符,我都作爲錯誤處理。面的readList函數將處理結構體的成名。
的S表式使用标识符区分两个不同型,结构体成员名和nil值的指。read函数值处理nil型的标识符。遇到scanner.Ident“nil”是使用reflect.Zero函数将变量v设置为零值。而其它任何型的标识符,我都作为错误处理。面的readList函数将处理结构体的成名。
“(”標記對應一個列表的始。第二個函數readList將一個列表解到一聚合型中map、結構體、slice或數組),具體類型依然於傳入待填充量的型。每次遇到這種情況,循環繼續解析每元素直到遇到於開始標記匹配的結束標記“)”endList函數用於檢測結束標記
“(”标记对应一个列表的始。第二个函数readList将一个列表解到一聚合型中map、结构体、slice或数组),具体类型依然于传入待填充量的型。每次遇到这种情况,循环继续解析每元素直到遇到于开始标记匹配的结束标记“)”endList函数用于检测结束标记
最有趣的部分是遞歸。最簡單的是對數組類型的理。直到遇到“)”結束標記,我使用Index函數來獲取數組每個元素的地址,然後遞歸調用read函數處理。和其它錯誤類似,如果輸入數據導致解器的引用超出了數組的范,解碼器將拋出panic常。slice也采用似方法解析,不同的是我們將爲每個元素建新的量,然後將元素添加到slice的末尾。
最有趣的部分是递归。最简单的是对数组类型的理。直到遇到“)”结束标记,我使用Index函数来获取数组每个元素的地址,然后递归调用read函数处理。和其它错误类似,如果输入数据导致解器的引用超出了数组的范,解码器将抛出panic常。slice也采用似方法解析,不同的是我们将为每个元素建新的量,然后将元素添加到slice的末尾。
在循環處理結構體和map每元素時必須解碼一個(key value)格式的對應子列表。對於結構體key部分對於成員的名字。和數組類似,我使用FieldByName找到結構體對應成員的變量然後遞歸調用read函數處理。對於mapkey可能是任意型,元素的理方式和slice似,我們創建一新的量,然後遞歸填充它,最後將新解析到的key/value添加到map。
在循环处理结构体和map每元素时必须解码一个(key value)格式的对应子列表。对于结构体key部分对于成员的名字。和数组类似,我使用FieldByName找到结构体对应成员的变量然后递归调用read函数处理。对于mapkey可能是任意型,元素的理方式和slice似,我们创建一新的量,然后递归填充它,最后将新解析到的key/value添加到map。
```Go
func readList(lex *lexer, v reflect.Value) {
@@ -130,7 +130,7 @@ func endList(lex *lexer) bool {
}
```
,我們將解析器包裝爲導出的Unmarshal解碼函數,隱藏了一些初始化和清理等邊緣處理。部解析器以panic的方式拋出錯誤但是Unmarshal函數通過在defer語句調用recover函數來捕獲內部panic§5.10),然後返迴一個對panic對應的錯誤信息。
,我们将解析器包装为导出的Unmarshal解码函数,隐藏了一些初始化和清理等边缘处理。部解析器以panic的方式抛出错误但是Unmarshal函数通过在defer语句调用recover函数来捕获内部panic§5.10),然后返回一个对panic对应的错误信息。
```Go
// Unmarshal parses S-expression data and populates the variable
@@ -150,10 +150,10 @@ func Unmarshal(data []byte, out interface{}) (err error) {
}
```
産實現不應該對任何輸入問題都用panic形式告,而且應該報告一些錯誤相關的信息,例如出現錯誤輸入的行和位置等。管如此,我希望通過這個例子展示似encoding/json等包底層代碼的實現思路,以及如何使用反射機製來填充數據結構
产实现不应该对任何输入问题都用panic形式告,而且应该报告一些错误相关的信息,例如出现错误输入的行和位置等。管如此,我希望通过这个例子展示似encoding/json等包底层代码的实现思路,以及如何使用反射机制来填充数据结构
**練習 12.8** sexpr.Unmarshal函和json.Unmarshal一,都要求在解碼前輸入完整的字slice。定義一個和json.Decoder似的sexpr.Decoder型,支持從一個io.Reader流解碼。脩改sexpr.Unmarshal函,使用這個新的類型實現
**练习 12.8** sexpr.Unmarshal函和json.Unmarshal一,都要求在解码前输入完整的字slice。定义一个和json.Decoder似的sexpr.Decoder型,支持从一个io.Reader流解码。修改sexpr.Unmarshal函,使用这个新的类型实现
**練習 12.9** 編寫一個基於標記的API用於解碼S表式,考xml.Decoder7.14)的格。你需要五種類型的標記Symbol、String、Int、StartList和EndList。
**练习 12.9** 编写一个基于标记的API用于解码S表式,考xml.Decoder7.14)的格。你需要五种类型的标记Symbol、String、Int、StartList和EndList。
**練習 12.10** 展sexpr.Unmarshal函,支持布型、浮點數和interface型的解,使用 **練習 12.3** 的方案。(提示:要解接口,你需要name映射到每支持型的reflect.Type。
**练习 12.10** 展sexpr.Unmarshal函,支持布型、浮点数和interface型的解,使用 **练习 12.3** 的方案。(提示:要解接口,你需要name映射到每支持型的reflect.Type。

View File

@@ -1,10 +1,10 @@
## 12.7. 獲取結構體字段標識
## 12.7. 获取结构体字段标识
在4.5節我們使用構體成員標籤用於設置對應JSON對應的名字。其中json成員標籤讓我們可以選擇成員的名字和抑零值成員的輸出。在本,我們將看到如果通反射機製類獲取成員標籤
在4.5节我们使用构体成员标签用于设置对应JSON对应的名字。其中json成员标签让我们可以选择成员的名字和抑零值成员的输出。在本,我们将看到如果通反射机制类获取成员标签
對於一個web服大部分HTTP理函要做的第一件事情就是展開請求中的參數到本地量中。我們定義了一工具函叫params.Unpack使用結構體成員標籤機製來讓HTTP理函解析請求參數更方便。
对于一个web服大部分HTTP理函要做的第一件事情就是展开请求中的参数到本地量中。我们定义了一工具函叫params.Unpack使用结构体成员标签机制来让HTTP理函解析请求参数更方便。
首先,我看看如何使用它。下面的search函是一HTTP請求處理函。它定了一匿名結構體類型的量,用結構體的每個成員表示HTTP求的參數。其中結構體成員標籤指明了對於請求參數的名字,爲了減少URL的長度這些參數名通常都是神祕的縮略詞。Unpack將請求參數填充到合適的結構體成員中,這樣我們可以方便地通過合適的類型類來訪問這些參數
首先,我看看如何使用它。下面的search函是一HTTP请求处理函。它定了一匿名结构体类型的量,用结构体的每个成员表示HTTP求的参数。其中结构体成员标签指明了对于请求参数的名字,为了减少URL的长度这些参数名通常都是神秘的缩略词。Unpack将请求参数填充到合适的结构体成员中,这样我们可以方便地通过合适的类型类来访问这些参数
<u><i>gopl.io/ch12/search</i></u>
```Go
@@ -28,9 +28,9 @@ func search(resp http.ResponseWriter, req *http.Request) {
}
```
下面的Unpack函主要完成三件事情。第一,它調用req.ParseForm()解析HTTP求。然req.Form包含所有的請求參數不管HTTP客端使用的是GET是POST求方法。
下面的Unpack函主要完成三件事情。第一,它用req.ParseForm()解析HTTP求。然req.Form包含所有的请求参数不管HTTP客端使用的是GET是POST求方法。
下一步Unpack函數將構建每個結構體成員有效參數名字到成員變量的映射。如果結構體成員有成員標籤的話,有效參數名字可能和實際的成名字不相同。reflect.Type的Field方法將返迴一個reflect.StructField里面含有每個成員的名字、型和可的成員標籤等信息。其中成員標籤信息對應reflect.StructTag型的字符串,且提供了Get方法用解析和根特定key提取的子串例如里的http:"..."形式的子串。
下一步Unpack函数将构建每个结构体成员有效参数名字到成员变量的映射。如果结构体成员有成员标签的话,有效参数名字可能和实际的成名字不相同。reflect.Type的Field方法将返回一个reflect.StructField里面含有每个成员的名字、型和可的成员标签等信息。其中成员标签信息对应reflect.StructTag型的字符串,且提供了Get方法用解析和根特定key提取的子串例如里的http:"..."形式的子串。
<u><i>gopl.io/ch12/params</i></u>
```Go
@@ -78,9 +78,9 @@ func Unpack(req *http.Request, ptr interface{}) error {
}
```
Unpack遍HTTP求的name/valu參數鍵值對,併且根更新相應的結構體成員。迴想一下,同一名字的參數可能出多次。如果發生這種情況,併且對應的結構體成員是一slice麽就將所有的參數添加到slice中。其它情況,對應的成員值將被覆蓋,隻有最一次出現的參數值才是起作用的。
Unpack遍HTTP求的name/valu参数键值对,并且根更新相应的结构体成员。回想一下,同一名字的参数可能出多次。如果发生这种情况,并且对应的结构体成员是一slice么就将所有的参数添加到slice中。其它情况,对应的成员值将被覆盖,只有最一次出现的参数值才是起作用的。
populate函小心用求的字符串類型參數值來填充一的成v或者是slice型成中的一的元素)。目前,它支持字符串、有符號整數和布型。其中其它的類型將留做練習任務
populate函小心用求的字符串类型参数值来填充一的成v或者是slice型成中的一的元素)。目前,它支持字符串、有符号整数和布型。其中其它的类型将留做练习任务
```Go
func populate(v reflect.Value, value string) error {
@@ -109,7 +109,7 @@ func populate(v reflect.Value, value string) error {
}
```
如果我上上面的理程序添加到一web服器,可以生以下的會話
如果我上上面的理程序添加到一web服器,可以生以下的会话
```
$ go build gopl.io/ch12/search
@@ -128,8 +128,8 @@ $ ./fetch 'http://localhost:12345/search?q=hello&max=lots'
max: strconv.ParseInt: parsing "lots": invalid syntax
```
**練習 12.11** 編寫相應的Pack函給定一個結構體值Pack函數將返迴合併了所有結構體成員和值的URL。
**练习 12.11** 编写相应的Pack函给定一个结构体值Pack函数将返回合并了所有结构体成员和值的URL。
**練習 12.12** 展成員標籤以表示一個請求參數的有效值規則。例如,一字符串可以是有效的email地址或一信用卡號碼,還有一個整數可能需要是有效的郵政編碼。脩改Unpack函數以檢査這些規則
**练习 12.12** 展成员标签以表示一个请求参数的有效值规则。例如,一字符串可以是有效的email地址或一信用卡号码,还有一个整数可能需要是有效的邮政编码。修改Unpack函数以检查这些规则
**練習 12.13** 改S表式的編碼§12.4)和解§12.6采用和encoding/json包§4.5似的方式使用成員標籤中的sexpr:"..."字串。
**练习 12.13** 改S表式的编码§12.4)和解§12.6采用和encoding/json包§4.5似的方式使用成员标签中的sexpr:"..."字串。

View File

@@ -1,6 +1,6 @@
## 12.8. 示一個類型的方法集
## 12.8. 示一个类型的方法集
的最後一個例子是使用reflect.Type打印任意值的型和枚它的方法:
的最后一个例子是使用reflect.Type打印任意值的型和枚它的方法:
<u><i>gopl.io/ch12/methods</i></u>
```Go
@@ -18,9 +18,9 @@ func Print(x interface{}) {
}
```
reflect.Type和reflect.Value都提供了一Method方法。每次t.Method(i)調用將一個reflect.Method的例,對應一個用於描述一方法的名稱和類型的結構體。每次v.Method(i)方法調用都返迴一個reflect.Value以表示對應的值§6.4),也就是一方法是到它的接收者的。使用reflect.Value.Call方法們之類沒有演示),可以調用一Func型的Value但是這個例子中用到了它的型。
reflect.Type和reflect.Value都提供了一Method方法。每次t.Method(i)调用将一个reflect.Method的例,对应一个用于描述一方法的名称和类型的结构体。每次v.Method(i)方法用都返回一个reflect.Value以表示对应的值§6.4),也就是一方法是到它的接收者的。使用reflect.Value.Call方法们之类没有演示),可以用一Func型的Value但是这个例子中用到了它的型。
這是屬於time.Duration和`*strings.Replacer`兩個類型的方法:
这是属于time.Duration和`*strings.Replacer`两个类型的方法:
```Go
methods.Print(time.Hour)

View File

@@ -1,20 +1,20 @@
## 12.9. 幾點忠告
## 12.9. 几点忠告
然反射提供的API遠多於我們講到的,我前面的例子主要是出了一方向,通反射可以實現哪些功能。反射是一個強大併富有表力的工具,但是它應該被小心地使用,原因有三。
然反射提供的API远多于我们讲到的,我前面的例子主要是出了一方向,通反射可以实现哪些功能。反射是一个强大并富有表力的工具,但是它应该被小心地使用,原因有三。
第一原因是,基反射的代是比脆弱的。對於每一個會導致編譯器報告類型錯誤的問題,在反射中都有之相對應的問題,不同的是編譯器會在構建時馬上報告錯誤,而反射是在眞正運行到的候才會拋出panic常,可能是完代很久之後的時候了,而且程序也可能行了很長的時間
第一原因是,基反射的代是比脆弱的。对于每一个会导致编译器报告类型错误的问题,在反射中都有之相对应的问题,不同的是编译器会在构建时马上报告错误,而反射是在真正运行到的候才会抛出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)"
```
反射同降低了程序的安全性,還影響了自化重和分析工具的準確性,因爲它們無法識别運行時才能確認的類型信息。
反射同降低了程序的安全性,还影响了自化重和分析工具的准确性,因为它们无法识别运行时才能确认的类型信息。
避免使用反射的第二原因是,卽使對應類型提供了相同文,但是反射的操作不能做靜態類型檢査,而且大量反射的代通常以理解。是需要小心翼翼地爲每個導出的型和其它接受interface{}或reflect.Value類型參數的函數維護説明文
避免使用反射的第二原因是,即使对应类型提供了相同文,但是反射的操作不能做静态类型检查,而且大量反射的代通常以理解。是需要小心翼翼地为每个导出的型和其它接受interface{}或reflect.Value类型参数的函数维护说明文
第三原因,基反射的代通常比正常的代碼運行速度慢一到兩個數量級。對於一個典型的目,大部分函的性能和程序的整性能關繫不大,所以使用反射可能使程序更加清晰。測試是一特别合使用反射的景,因爲每個測試的數據集都很小。但是對於性能關鍵路徑的函,最好避免使用反射。
第三原因,基反射的代通常比正常的代码运行速度慢一到两个数量级。对于一个典型的目,大部分函的性能和程序的整性能关系不大,所以使用反射可能使程序更加清晰。测试是一特别合使用反射的景,因为每个测试的数据集都很小。但是对于性能关键路径的函,最好避免使用反射。

View File

@@ -1,5 +1,5 @@
# 第十二章 反射
Go音提供了一種機製在運行時更新量和檢査它們的值、調用它的方法和它支持的在操作,但是在編譯時併不知道這些變量的具體類型。這種機製被稱爲反射。反射也可以讓我們將類型本身作第一的值類型處理。
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都有公反射相的接口。