mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-04 06:51:43 +00:00
make
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
## 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 {
|
||||
@@ -32,8 +32,8 @@ 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,9 +1,9 @@
|
||||
## 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
|
||||
@@ -11,22 +11,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
|
||||
@@ -35,16 +35,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
|
||||
@@ -53,9 +53,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 無效類型.)
|
||||
|
||||
```Go
|
||||
gopl.io/ch12/format
|
||||
@@ -96,7 +96,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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# 第十二章 反射
|
||||
|
||||
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都沒有公開反射相關的接口.
|
||||
|
||||
|
Reference in New Issue
Block a user