mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-18 03:34:19 +08:00
@@ -43,7 +43,7 @@ io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。
|
||||
|
||||
因爲fmt.Fprintf函數沒有對具體操作的值做任何假設而是僅僅通過io.Writer接口的約定來保證行爲,所以第一個參數可以安全地傳入一個任何具體類型的值隻需要滿足io.Writer接口。一個類型可以自由的使用另一個滿足相同接口的類型來進行替換被稱作可替換性(LSP里氏替換)。這是一個面向對象的特徵。
|
||||
|
||||
讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法,僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中,讓len(p)的類型和\*c的類型匹配的轉換是必鬚的。)
|
||||
讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法,僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中,讓len(p)的類型和\*c的類型匹配的轉換是必須的。)
|
||||
|
||||
```go
|
||||
// gopl.io/ch7/bytecounter
|
||||
|
||||
@@ -23,7 +23,7 @@ rwc = w // compile error: io.Writer lacks Close method
|
||||
|
||||
因爲ReadWriter和ReadWriteCloser包含所有Writer的方法,所以任何實現了ReadWriter和ReadWriteCloser的類型必定也實現了Writer接口
|
||||
|
||||
在進一步學習前,必鬚先解釋表示一個類型持有一個方法當中的細節。迴想在6.2章中,對於每一個命名過的具體類型T;它一些方法的接收者是類型T本身然而另一些則是一個*T的指針。還記得在T類型的參數上調用一個*T的方法是合法的,隻要這個參數是一個變量;編譯器隱式的獲取了它的地址。但這僅僅是一個語法醣:T類型的值不擁有所有*T指針的方法,那這樣它就可能隻實現更少的接口。
|
||||
在進一步學習前,必須先解釋表示一個類型持有一個方法當中的細節。迴想在6.2章中,對於每一個命名過的具體類型T;它一些方法的接收者是類型T本身然而另一些則是一個*T的指針。還記得在T類型的參數上調用一個*T的方法是合法的,隻要這個參數是一個變量;編譯器隱式的獲取了它的地址。但這僅僅是一個語法糖:T類型的值不擁有所有*T指針的方法,那這樣它就可能隻實現更少的接口。
|
||||
|
||||
舉個例子可能會更清晰一點。在第6.5章中,IntSet類型的String方法的接收者是一個指針類型,所以我們不能在一個不能尋址的IntSet值上調用這個方法:
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ func (f *celsiusFlag) Set(s string) error {
|
||||
}
|
||||
```
|
||||
|
||||
調用fmt.Sscanf函數從輸入s中解析一個浮點數(value)和一個字符串(unit)。雖然通常必鬚檢査Sscanf的錯誤返迴,但是在這個例子中我們不需要因爲如果有錯誤發生,就沒有switch case會匹配到。
|
||||
調用fmt.Sscanf函數從輸入s中解析一個浮點數(value)和一個字符串(unit)。雖然通常必須檢査Sscanf的錯誤返迴,但是在這個例子中我們不需要因爲如果有錯誤發生,就沒有switch case會匹配到。
|
||||
|
||||
下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中,有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必鬚的方法。
|
||||
下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中,有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必須的方法。
|
||||
|
||||
```go
|
||||
// CelsiusFlag defines a Celsius flag with the specified name,
|
||||
|
||||
@@ -42,7 +42,7 @@ w = os.Stdout
|
||||
w.Write([]byte("hello")) // "hello"
|
||||
```
|
||||
|
||||
通常在編譯期,我們不知道接口值的動態類型是什麽,所以一個接口上的調用必鬚使用動態分配。因爲不是直接進行調用,所以編譯器必鬚把代碼生成在類型描述符的方法Write上,然後間接調用那個地址。這個調用的接收者是一個接口動態值的拷貝,os.Stdout。效果和下面這個直接調用一樣:
|
||||
通常在編譯期,我們不知道接口值的動態類型是什麽,所以一個接口上的調用必須使用動態分配。因爲不是直接進行調用,所以編譯器必須把代碼生成在類型描述符的方法Write上,然後間接調用那個地址。這個調用的接收者是一個接口動態值的拷貝,os.Stdout。效果和下面這個直接調用一樣:
|
||||
|
||||
```go
|
||||
os.Stdout.Write([]byte("hello")) // "hello"
|
||||
@@ -93,7 +93,7 @@ var x interface{} = []int{1, 2, 3}
|
||||
fmt.Println(x == x) // panic: comparing uncomparable type []int
|
||||
```
|
||||
|
||||
考慮到這點,接口類型是非常與衆不同的。其它類型要麽是安全的可比較類型(如基本類型和指針)要麽是完全不可比較的類型(如切片,映射類型,和函數),但是在比較接口值或者包含了接口值的聚合類型時,我們必鬚要意識到潛在的panic。同樣的風險也存在於使用接口作爲map的鍵或者switch的操作數。隻能比較你非常確定它們的動態值是可比較類型的接口值。
|
||||
考慮到這點,接口類型是非常與衆不同的。其它類型要麽是安全的可比較類型(如基本類型和指針)要麽是完全不可比較的類型(如切片,映射類型,和函數),但是在比較接口值或者包含了接口值的聚合類型時,我們必須要意識到潛在的panic。同樣的風險也存在於使用接口作爲map的鍵或者switch的操作數。隻能比較你非常確定它們的動態值是可比較類型的接口值。
|
||||
|
||||
當我們處理錯誤或者調試的過程中,得知接口值的動態類型是非常有幫助的。所以我們使用fmt包的%T動作:
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func printTracks(tracks []*Track) {
|
||||
}
|
||||
```
|
||||
|
||||
爲了能按照Artist字段對播放列表進行排序,我們會像對StringSlice那樣定義一個新的帶有必鬚Len,Less和Swap方法的切片類型。
|
||||
爲了能按照Artist字段對播放列表進行排序,我們會像對StringSlice那樣定義一個新的帶有必須Len,Less和Swap方法的切片類型。
|
||||
|
||||
```go
|
||||
type byArtist []*Track
|
||||
@@ -89,7 +89,7 @@ func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
|
||||
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
```
|
||||
|
||||
爲了調用通用的排序程序,我們必鬚先將tracks轉換爲新的byArtist類型,它定義了具體的排序:
|
||||
爲了調用通用的排序程序,我們必須先將tracks轉換爲新的byArtist類型,它定義了具體的排序:
|
||||
|
||||
```go
|
||||
sort.Sort(byArtist(tracks))
|
||||
@@ -137,7 +137,7 @@ func Reverse(data Interface) Interface { return reverse{data} }
|
||||
|
||||
reverse的另外兩個方法Len和Swap隱式地由原有內嵌的sort.Interface提供。因爲reverse是一個不公開的類型,所以導出函數Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
|
||||
|
||||
爲了可以按照不同的列進行排序,我們必鬚定義一個新的類型例如byYear:
|
||||
爲了可以按照不同的列進行排序,我們必須定義一個新的類型例如byYear:
|
||||
|
||||
```go
|
||||
type byYear []*Track
|
||||
|
||||
@@ -175,7 +175,7 @@ mux.HandleFunc("/list", db.list)
|
||||
mux.HandleFunc("/price", db.price)
|
||||
```
|
||||
|
||||
從上面的代碼很容易看出應該怎麽構建一個程序,它有兩個不同的web服務器監聽不同的端口的,併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe(可能併行的)。但是在大多數程序中,一個web服務器就足夠了。此外,在一個應用程序的多個文件中定義HTTP handler也是非常典型的,如果它們必鬚全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。
|
||||
從上面的代碼很容易看出應該怎麽構建一個程序,它有兩個不同的web服務器監聽不同的端口的,併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe(可能併行的)。但是在大多數程序中,一個web服務器就足夠了。此外,在一個應用程序的多個文件中定義HTTP handler也是非常典型的,如果它們必須全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。
|
||||
|
||||
所以爲了方便,net/http包提供了一個全局的ServeMux實例DefaultServerMux和包級别的http.Handle和http.HandleFunc函數。現在,爲了使用DefaultServeMux作爲服務器的主handler,我們不需要將它傳給ListenAndServe函數;nil值就可以工作。
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ type call struct {
|
||||
type Env map[Var]float64
|
||||
```
|
||||
|
||||
我們也需要每個表示式去定義一個Eval方法,這個方法會根據給定的environment變量返迴表達式的值。因爲每個表達式都必鬚提供這個方法,我們將它加入到Expr接口中。這個包隻會對外公開Expr,Env,和Var類型。調用方不需要獲取其它的表達式類型就可以使用這個求值器。
|
||||
我們也需要每個表示式去定義一個Eval方法,這個方法會根據給定的environment變量返迴表達式的值。因爲每個表達式都必須提供這個方法,我們將它加入到Expr接口中。這個包隻會對外公開Expr,Env,和Var類型。調用方不需要獲取其它的表達式類型就可以使用這個求值器。
|
||||
|
||||
```go
|
||||
type Expr interface {
|
||||
@@ -248,7 +248,7 @@ log(10) unknown function "log"
|
||||
sqrt(1, 2) call to sqrt has 2 args, want 1
|
||||
```
|
||||
|
||||
Check方法的參數是一個Var類型的集合,這個集合聚集從表達式中找到的變量名。爲了保證成功的計算,這些變量中的每一個都必鬚出現在環境變量中。從邏輯上講,這個集合就是調用Check方法返迴的結果,但是因爲這個方法是遞歸調用的,所以對於Check方法填充結果到一個作爲參數傳入的集合中會更加的方便。調用方在初始調用時必鬚提供一個空的集合。
|
||||
Check方法的參數是一個Var類型的集合,這個集合聚集從表達式中找到的變量名。爲了保證成功的計算,這些變量中的每一個都必須出現在環境變量中。從邏輯上講,這個集合就是調用Check方法返迴的結果,但是因爲這個方法是遞歸調用的,所以對於Check方法填充結果到一個作爲參數傳入的集合中會更加的方便。調用方在初始調用時必須提供一個空的集合。
|
||||
|
||||
在第3.2節中,我們繪製了一個在編譯器才確定的函數f(x,y)。現在我們可以解析,檢査和計算在字符串中的表達式,我們可以構建一個在運行時從客戶端接收表達式的web應用併且它會繪製這個函數的表示的麴面。我們可以使用集合vars來檢査表達式是否是一個隻有兩個變量,x和y的函數——實際上是3個,因爲我們爲了方便會提供半徑大小r。併且我們會在計算前使用Check方法拒絶有格式問題的表達式,這樣我們就不會在下面函數的40000個計算過程(100x100個柵格,每一個有4個角)重複這些檢査。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user