mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
ch12: fix code path
This commit is contained in:
parent
617ef87432
commit
0c1b9762d9
@ -1,6 +1,5 @@
|
|||||||
## 12.1. 爲何需要反射?
|
## 12.1. 爲何需要反射?
|
||||||
|
|
||||||
|
|
||||||
有時候我們需要編寫一個函數能夠處理一類併不滿足普通公共接口的類型的值, 也可能它們併沒有確定的表示方式, 或者在我們設計該函數的時候還這些類型可能還不存在, 各種情況都有可能.
|
有時候我們需要編寫一個函數能夠處理一類併不滿足普通公共接口的類型的值, 也可能它們併沒有確定的表示方式, 或者在我們設計該函數的時候還這些類型可能還不存在, 各種情況都有可能.
|
||||||
|
|
||||||
一個大家熟悉的例子是 fmt.Fprintf 函數提供的字符串格式化處理邏輯, 它可以用例對任意類型的值格式化打印, 甚至是用戶自定義的類型. 讓我們來嚐試實現一個類似功能的函數. 簡單起見, 我們的函數隻接收一個參數, 然後返迴和 fmt.Sprint 類似的格式化後的字符串, 我們的函數名也叫 Sprint.
|
一個大家熟悉的例子是 fmt.Fprintf 函數提供的字符串格式化處理邏輯, 它可以用例對任意類型的值格式化打印, 甚至是用戶自定義的類型. 讓我們來嚐試實現一個類似功能的函數. 簡單起見, 我們的函數隻接收一個參數, 然後返迴和 fmt.Sprint 類似的格式化後的字符串, 我們的函數名也叫 Sprint.
|
||||||
@ -35,5 +34,3 @@ 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 的類型, 這會導致對這些庫的依賴.
|
||||||
|
|
||||||
沒有一種方法來檢査未知類型的表示方式, 我們被卡住了. 這就是我們爲何需要反射的原因.
|
沒有一種方法來檢査未知類型的表示方式, 我們被卡住了. 這就是我們爲何需要反射的原因.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
## 12.2. reflect.Type和reflect.Value
|
## 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:
|
||||||
@ -57,8 +56,8 @@ fmt.Printf("%d\n", i) // "3"
|
|||||||
|
|
||||||
我們使用 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
|
```Go
|
||||||
gopl.io/ch12/format
|
|
||||||
package format
|
package format
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -106,6 +105,3 @@ fmt.Println(format.Any(d)) // "1"
|
|||||||
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
|
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
|
||||||
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
|
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,9 +22,8 @@ e.args[0].value.y.value = "pi"
|
|||||||
|
|
||||||
在可能的情況下,你應該避免在一個包中暴露和反射相關的接口。我們將定義一個未導出的display函數用於遞歸處理工作,導出的是Display函數,它隻是display函數簡單的包裝以接受interface{}類型的參數:
|
在可能的情況下,你應該避免在一個包中暴露和反射相關的接口。我們將定義一個未導出的display函數用於遞歸處理工作,導出的是Display函數,它隻是display函數簡單的包裝以接受interface{}類型的參數:
|
||||||
|
|
||||||
|
<u><i>gopl.io/ch12/display</i></u>
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch12/display
|
|
||||||
|
|
||||||
func Display(name string, x interface{}) {
|
func Display(name string, x interface{}) {
|
||||||
fmt.Printf("Display %s (%T):\n", name, x)
|
fmt.Printf("Display %s (%T):\n", name, x)
|
||||||
display(name, reflect.ValueOf(x))
|
display(name, reflect.ValueOf(x))
|
||||||
@ -224,5 +223,3 @@ c.Value = 42
|
|||||||
**練習 12.1:** 擴展Displayhans,以便它可以顯示包含以結構體或數組作爲map的key類型的值。
|
**練習 12.1:** 擴展Displayhans,以便它可以顯示包含以結構體或數組作爲map的key類型的值。
|
||||||
|
|
||||||
**練習 12.2:** 增強display函數的穩健性,通過記録邊界的步數來確保在超出一定限製前放棄遞歸。(在13.3節,我們會看到另一種探測數據結構是否存在環的技術。)
|
**練習 12.2:** 增強display函數的穩健性,通過記録邊界的步數來確保在超出一定限製前放棄遞歸。(在13.3節,我們會看到另一種探測數據結構是否存在環的技術。)
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,9 +21,8 @@ foo symbol (an unquoted name)
|
|||||||
|
|
||||||
編碼是由一個encode遞歸函數完成,如下所示。它的結構本質上和前面的Display函數類似:
|
編碼是由一個encode遞歸函數完成,如下所示。它的結構本質上和前面的Display函數類似:
|
||||||
|
|
||||||
|
<u><i>gopl.io/ch12/sexpr</i></u>
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch12/sexpr
|
|
||||||
|
|
||||||
func encode(buf *bytes.Buffer, v reflect.Value) error {
|
func encode(buf *bytes.Buffer, v reflect.Value) error {
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.Invalid:
|
case reflect.Invalid:
|
||||||
@ -151,5 +150,3 @@ omin.)" "Best Picture (Nomin.)")) (Sequel nil))
|
|||||||
**練習 12.6:** 脩改encode,作爲一個優化,忽略對是零值對象的編碼。
|
**練習 12.6:** 脩改encode,作爲一個優化,忽略對是零值對象的編碼。
|
||||||
|
|
||||||
**練習 12.7:** 創建一個基於流式的API,用於S表達式的解碼,和json.Decoder(§4.5)函數功能類似。
|
**練習 12.7:** 創建一個基於流式的API,用於S表達式的解碼,和json.Decoder(§4.5)函數功能類似。
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,9 +16,8 @@ Unmarshal函數使用了反射機製類脩改movie變量的每個成員,根據
|
|||||||
|
|
||||||
因爲每個解析器可能需要多次使用當前的記號,但是Scan會一直向前掃描,所有我們包裝了一個lexer掃描器輔助類型,用於跟蹤最近由Scan方法返迴的記號。
|
因爲每個解析器可能需要多次使用當前的記號,但是Scan會一直向前掃描,所有我們包裝了一個lexer掃描器輔助類型,用於跟蹤最近由Scan方法返迴的記號。
|
||||||
|
|
||||||
|
<u><i>gopl.io/ch12/sexpr</i></u>
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch12/sexpr
|
|
||||||
|
|
||||||
type lexer struct {
|
type lexer struct {
|
||||||
scan scanner.Scanner
|
scan scanner.Scanner
|
||||||
token rune // the current token
|
token rune // the current token
|
||||||
@ -158,5 +157,3 @@ func Unmarshal(data []byte, out interface{}) (err error) {
|
|||||||
**練習 12.9:** 編寫一個基於標記的API用於解碼S表達式,參考xml.Decoder(7.14)的風格。你將需要五種類型的標記:Symbol、String、Int、StartList和EndList。
|
**練習 12.9:** 編寫一個基於標記的API用於解碼S表達式,參考xml.Decoder(7.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。)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,9 +6,8 @@
|
|||||||
|
|
||||||
首先,我們看看如何使用它。下面的search函數是一個HTTP請求處理函數。它定義了一個匿名結構體類型的變量,用結構體的每個成員表示HTTP請求的參數。其中結構體成員標籤指明了對於請求參數的名字,爲了減少URL的長度這些參數名通常都是神祕的縮略詞。Unpack將請求參數填充到合適的結構體成員中,這樣我們可以方便地通過合適的類型類來訪問這些參數。
|
首先,我們看看如何使用它。下面的search函數是一個HTTP請求處理函數。它定義了一個匿名結構體類型的變量,用結構體的每個成員表示HTTP請求的參數。其中結構體成員標籤指明了對於請求參數的名字,爲了減少URL的長度這些參數名通常都是神祕的縮略詞。Unpack將請求參數填充到合適的結構體成員中,這樣我們可以方便地通過合適的類型類來訪問這些參數。
|
||||||
|
|
||||||
|
<u><i>gopl.io/ch12/search</i></u>
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch12/search
|
|
||||||
|
|
||||||
import "gopl.io/ch12/params"
|
import "gopl.io/ch12/params"
|
||||||
|
|
||||||
// search implements the /search URL endpoint.
|
// search implements the /search URL endpoint.
|
||||||
@ -33,9 +32,8 @@ func search(resp http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
下一步,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
|
```Go
|
||||||
gopl.io/ch12/params
|
|
||||||
|
|
||||||
// Unpack populates the fields of the struct pointed to by ptr
|
// Unpack populates the fields of the struct pointed to by ptr
|
||||||
// from the HTTP request parameters in req.
|
// from the HTTP request parameters in req.
|
||||||
func Unpack(req *http.Request, ptr interface{}) error {
|
func Unpack(req *http.Request, ptr interface{}) error {
|
||||||
@ -135,6 +133,3 @@ max: strconv.ParseInt: parsing "lots": invalid syntax
|
|||||||
**練習 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:"..."字串。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
我們的最後一個例子是使用reflect.Type來打印任意值的類型和枚舉它的方法:
|
我們的最後一個例子是使用reflect.Type來打印任意值的類型和枚舉它的方法:
|
||||||
|
|
||||||
|
<u><i>gopl.io/ch12/methods</i></u>
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch12/methods
|
|
||||||
|
|
||||||
// Print prints the method set of the value x.
|
// Print prints the method set of the value x.
|
||||||
func Print(x interface{}) {
|
func Print(x interface{}) {
|
||||||
v := reflect.ValueOf(x)
|
v := reflect.ValueOf(x)
|
||||||
@ -39,6 +38,3 @@ methods.Print(new(strings.Replacer))
|
|||||||
// func (*strings.Replacer) Replace(string) string
|
// func (*strings.Replacer) Replace(string) string
|
||||||
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
|
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user