回到简体

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 @@
## 7.1. 接口
## 7.1. 接口
目前止,我看到的型都是具體的類型。一個具體的類型可以準確的描述它所代表的值且展示出對類型本身的一些操作方式就像數字類型的算操作,切片型的索引、附加和取范操作。具體的類型還可以通它的方法提供外的行操作。總的來説,當你拿到一個具體的類型時你就知道它的本身是什和你可以用它做什
目前止,我看到的型都是具体的类型。一个具体的类型可以准确的描述它所代表的值且展示出对类型本身的一些操作方式就像数字类型的算操作,切片型的索引、附加和取范操作。具体的类型还可以通它的方法提供外的行操作。总的来说,当你拿到一个具体的类型时你就知道它的本身是什和你可以用它做什
在Go言中存在着另外一種類型:接口型。接口型是一抽象的型。它不暴露出它所代表的象的部值的結構和這個對象支持的基操作的集合;它們隻會展示出它自己的方法。也就是説當你有看到一接口型的值,你不知道它是什,唯一知道的就是可以通它的方法做什
在Go言中存在着另外一种类型:接口型。接口型是一抽象的型。它不暴露出它所代表的象的部值的结构和这个对象支持的基操作的集合;它们只会展示出它自己的方法。也就是说当你有看到一接口型的值,你不知道它是什,唯一知道的就是可以通它的方法做什
在本中,我一直使用兩個相似的函數來進行字符串的格式化fmt.Printf它會把結果寫到標準輸出和fmt.Sprintf它會把結果以字符串的形式返。得益使用接口,我不必可悲的因爲返迴結果在使用方式上的一些淺顯不同就必需把格式化這個最睏難的過程複製一份。實際上,這兩個函數都使用了另一個函數fmt.Fprintf來進行封。fmt.Fprintf這個函數對它的計算結果會被怎使用是完全不知道的。
在本中,我一直使用两个相似的函数来进行字符串的格式化fmt.Printf它会把结果写到标准输出和fmt.Sprintf它会把结果以字符串的形式返。得益使用接口,我不必可悲的因为返回结果在使用方式上的一些浅显不同就必需把格式化这个最困难的过程复制一份。实际上,这两个函数都使用了另一个函数fmt.Fprintf来进行封。fmt.Fprintf这个函数对它的计算结果会被怎使用是完全不知道的。
``` go
package fmt
@@ -20,10 +20,10 @@ func Sprintf(format string, args ...interface{}) string {
}
```
Fprintf的前F表示文件(File)也表明格式化輸出結果應該被寫入第一個參數提供的文件中。在Printf函中的第一個參數os.Stdout是*os.File在Sprintf函中的第一個參數&buf是一指向可以入字節的內存緩衝區,然而它
不是一文件類型盡管它在某種意義上和文件型相似。
Fprintf的前F表示文件(File)也表明格式化输出结果应该被写入第一个参数提供的文件中。在Printf函中的第一个参数os.Stdout是*os.File在Sprintf函中的第一个参数&buf是一指向可以入字节的内存缓冲区,然而它
不是一文件类型尽管它在某种意义上和文件型相似。
使Fprintf函中的第一個參數也不是一文件型。它是io.Writer類型這是一接口型定如下:
使Fprintf函中的第一个参数也不是一文件型。它是io.Writer类型这是一接口型定如下:
``` go
package io
@@ -41,11 +41,11 @@ type Writer interface {
}
```
io.Writer型定了函Fprintf和這個函數調用者之間的約定。一方面這個約定需要調用者提供具體類型的值就像\*os.File和\*bytes.Buffer這些類型都有一特定名和行的Write的函。另一方面這個約定保了Fprintf接受任何滿足io.Writer接口的值都可以工作。Fprintf函可能有假定入的是一文件或是一段存,而是入一可以調用Write函的值。
io.Writer型定了函Fprintf和这个函数调用者之间的约定。一方面这个约定需要用者提供具体类型的值就像\*os.File和\*bytes.Buffer这些类型都有一特定名和行的Write的函。另一方面这个约定保了Fprintf接受任何足io.Writer接口的值都可以工作。Fprintf函可能有假定入的是一文件或是一段存,而是入一可以用Write函的值。
fmt.Fprintf函數沒有對具體操作的值做任何假而是僅僅通過io.Writer接口的約定來保證行爲,所以第一個參數可以安全地入一任何具體類型的值需要滿足io.Writer接口。一個類型可以自由的使用另一個滿足相同接口的類型來進行替換被稱作可替性(LSP里氏替)。是一面向象的特
fmt.Fprintf函数没有对具体操作的值做任何假而是仅仅通过io.Writer接口的约定来保证行为,所以第一个参数可以安全地入一任何具体类型的值需要足io.Writer接口。一个类型可以自由的使用另一个满足相同接口的类型来进行替换被称作可替性(LSP里氏替)。是一面向象的特
讓我們通過一個新的類型來進行校,下面\*ByteCounter型里的Write方法僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中,len(p)的型和\*c的型匹配的轉換是必的。)
让我们通过一个新的类型来进行校,下面\*ByteCounter型里的Write方法仅仅在丢失写向它的字节前统计它们的长度。(在这个+=赋值语句中,len(p)的型和\*c的型匹配的转换是必的。)
<u><i>gopl.io/ch7/bytecounter</i></u>
```go
@@ -57,7 +57,7 @@ func (c *ByteCounter) Write(p []byte) (int, error) {
}
```
*ByteCounter滿足io.Writer的定,我可以把它入Fprintf函Fprintf函數執行字符串格式化的程不會去關註ByteCounter正確的纍加結果的度。
*ByteCounter足io.Writer的定,我可以把它入Fprintf函Fprintf函数执行字符串格式化的程不会去关注ByteCounter正确的累加结果的度。
```go
var c ByteCounter
@@ -69,7 +69,7 @@ fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c) // "12", = len("hello, Dolly")
```
除了io.Writer這個接口型,有另一個對fmt包很重要的接口型。Fprintf和Fprintln函數向類型提供了一種控製它們值輸出的途。在2.5中,我們爲Celsius型提供了一String方法以便可以打印成這樣"100°C" 在6.5中我們給*IntSet添加一String方法這樣集合可以用傳統的符號來進行表示就像"{1 2 3}"。給一個類型定String方法可以讓它滿足最泛使用之一的接口型fmt.Stringer
除了io.Writer这个接口型,有另一个对fmt包很重要的接口型。Fprintf和Fprintln函数向类型提供了一种控制它们值输出的途。在2.5中,我们为Celsius型提供了一String方法以便可以打印成这样"100°C" 在6.5中我们给*IntSet添加一String方法这样集合可以用传统的符号来进行表示就像"{1 2 3}"。给一个类型定String方法可以让它满足最广泛使用之一的接口型fmt.Stringer
```go
package fmt
@@ -82,14 +82,14 @@ type Stringer interface {
}
```
們會在7.10節解釋fmt包怎麽發現哪些值是滿足這個接口型的。
们会在7.10节解释fmt包怎么发现哪些值是满足这个接口型的。
**練習 7.1** 使用自ByteCounter的思路實現一個針對對單詞和行數的計數器。你會發現bufio.ScanWords非常的有用。
**练习 7.1** 使用自ByteCounter的思路实现一个针对对单词和行数的计数器。你会发现bufio.ScanWords非常的有用。
**練習 7.2** 寫一個帶有如下函數籤名的函CountingWriter入一io.Writer接口型,返迴一個新的Writer型把原的Writer封在里面和一表示入新的Writer字節數的int64型指
**练习 7.2** 写一个带有如下函数签名的函CountingWriter入一io.Writer接口型,返回一个新的Writer型把原的Writer封在里面和一表示入新的Writer字节数的int64型指
```go
func CountingWriter(w io.Writer) (io.Writer, *int64)
```
**練習 7.3** 在gopl.io/ch4/treesort (§4.4)的*tree類型實現一個String方法去展示tree型的值序列。
**练习 7.3** 在gopl.io/ch4/treesort (§4.4)的*tree类型实现一个String方法去展示tree型的值序列。

View File

@@ -1,8 +1,8 @@
## 7.2. 接口
## 7.2. 接口
接口型具描述了一列方法的集合,一個實現了這些方法的具體類型是這個接口型的例。
接口型具描述了一列方法的集合,一个实现了这些方法的具体类型是这个接口型的例。
io.Writer型是用的最泛的接口之一,因它提供了所有的類型寫入bytes的抽象包括文件型,內存緩衝區,網絡鏈HTTP客端,壓縮工具哈希等等。io包中定了很多其它有用的接口型。Reader可以代表任意可以取bytes的Closer可以是任意可以關閉的值,例如一文件或是網絡鏈接。(到在你可能意到了很多Go言中方法接口的命名習慣
io.Writer型是用的最广泛的接口之一,因它提供了所有的类型写入bytes的抽象包括文件型,内存缓冲区,网络链HTTP客端,压缩工具哈希等等。io包中定了很多其它有用的接口型。Reader可以代表任意可以取bytes的Closer可以是任意可以关闭的值,例如一文件或是网络链接。(到在你可能意到了很多Go言中方法接口的命名习惯
```go
package io
@@ -14,7 +14,7 @@ type Closer interface {
}
```
在往下看,我們發現有些新的接口型通過組合已有的接口來定義。下面是兩個例子:
在往下看,我们发现有些新的接口型通过组合已有的接口来定义。下面是两个例子:
```go
type ReadWriter interface {
@@ -27,7 +27,7 @@ type ReadWriteCloser interface {
Closer
}
```
上面用到的法和結構內嵌相似,我可以用這種方式以一個簡寫命名另一接口,而不用明它所有的方法。這種方式本稱爲接口嵌。管略失簡潔,我可以像下面這樣,不使用內嵌來聲明io.Writer接口。
上面用到的法和结构内嵌相似,我可以用这种方式以一个简写命名另一接口,而不用明它所有的方法。这种方式本称为接口嵌。管略失简洁,我可以像下面这样,不使用内嵌来声明io.Writer接口。
```go
type ReadWriter interface {
@@ -36,7 +36,7 @@ type ReadWriter interface {
}
```
或者甚至使用混合的格:
或者甚至使用混合的格:
```go
type ReadWriter interface {
@@ -45,11 +45,11 @@ type ReadWriter interface {
}
```
上面3種定義方式都是一的效果。方法的順序變化也有影,唯一重要的就是這個集合里面的方法。
上面3种定义方式都是一的效果。方法的顺序变化也有影,唯一重要的就是这个集合里面的方法。
**練習 7.4** strings.NewReader函數通過讀取一string參數返迴一個滿足io.Reader接口型的值(和其它值)。實現一個簡單版本的NewReader用它來構造一接收字符串入的HTML解析器§5.2
**练习 7.4** strings.NewReader函数通过读取一string参数返回一个满足io.Reader接口型的值(和其它值)。实现一个简单版本的NewReader用它来构造一接收字符串入的HTML解析器§5.2
**練習 7.5** io包里面的LimitReader函接收一io.Reader接口型的r和字節數n且返另一個從r中取字但是當讀完n個字節後就表示到文件束的Reader。實現這個LimitReader函
**练习 7.5** io包里面的LimitReader函接收一io.Reader接口型的r和字节数n且返另一个从r中取字但是当读完n个字节后就表示到文件束的Reader。实现这个LimitReader函
```go
func LimitReader(r io.Reader, n int64) io.Reader

View File

@@ -1,8 +1,8 @@
## 7.3. 實現接口的
## 7.3. 实现接口的
個類型如果有一接口需要的所有方法,那麽這個類型就實現了這個接口。例如,\*os.File類型實現了io.ReaderWriterCloser和ReadWriter接口。\*bytes.Buffer實現了ReaderWriter和ReadWriter些接口,但是它沒有實現Closer接口因它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一特定的接口型。舉個例子,\*bytes.Buffer是io.Writer\*os.Files是io.ReadWriter。
个类型如果有一接口需要的所有方法,那么这个类型就实现了这个接口。例如,\*os.File类型实现了io.ReaderWriterCloser和ReadWriter接口。\*bytes.Buffer实现了ReaderWriter和ReadWriter些接口,但是它没有实现Closer接口因它不具有Close方法。Go的程序员经常会简要的把一个具体的类型描述成一特定的接口型。举个例子,\*bytes.Buffer是io.Writer\*os.Files是io.ReadWriter。
接口指定的規則非常簡單:表達一個類型屬於某個接口隻要這個類型實現這個接口。所以:
接口指定的规则非常简单:表达一个类型属于某个接口只要这个类型实现这个接口。所以:
```go
var w io.Writer
@@ -15,18 +15,18 @@ rwc = os.Stdout // OK: *os.File has Read, Write, Close methods
rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
```
這個規則甚至適用於等式右本身也是一接口
这个规则甚至适用于等式右本身也是一接口
```go
w = rwc // OK: io.ReadWriteCloser has Write method
rwc = w // compile error: io.Writer lacks Close method
```
ReadWriter和ReadWriteCloser包含所有Writer的方法所以任何實現了ReadWriter和ReadWriteCloser的型必定也實現了Writer接口
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值上調用這個方法:
举个例子可能更清晰一。在第6.5章中IntSet型的String方法的接收者是一个指针类型,所以我不能在一不能址的IntSet值上调用这个方法:
```go
type IntSet struct { /* ... */ }
@@ -34,23 +34,23 @@ func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
```
但是我可以在一IntSet值上調用這個方法:
但是我可以在一IntSet值上调用这个方法:
```go
var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method
```
然而,由於隻有*IntSet型有String方法所有也有*IntSet類型實現了fmt.Stringer接口
然而,由于只有*IntSet型有String方法所有也有*IntSet类型实现了fmt.Stringer接口
```go
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
```
12.8章包含了一打印出任意值的所有方法的程序,然可以使用godoc -analysis=type tool(§10.7.4)展示每個類型的方法和具體類型和接口之間的關繫
12.8章包含了一打印出任意值的所有方法的程序,然可以使用godoc -analysis=type tool(§10.7.4)展示每个类型的方法和具体类型和接口之间的关系
就像信封封裝和隱藏信件起來一樣,接口型封裝和隱藏具體類型和它的值。使具體類型有其它的方法也有接口型暴露出的方法會被調用到:
就像信封封装和隐藏信件起来一样,接口型封装和隐藏具体类型和它的值。使具体类型有其它的方法也有接口型暴露出的方法会被调用到:
```go
os.Stdout.Write([]byte("hello")) // OK: *os.File has Write method
@@ -62,9 +62,9 @@ w.Write([]byte("hello")) // OK: io.Writer has Write method
w.Close() // compile error: io.Writer lacks Close method
```
有更多方法的接口比如io.ReadWriter和少一些方法的接口型,例如io.Reader進行對比;更多方法的接口類型會告訴我們更多關於它的值持有的信息,併且對實現它的型要求更加格。那麽關於interface{}型,它有任何方法,請講出哪些具體的類型實現了它?
有更多方法的接口比如io.ReadWriter和少一些方法的接口型,例如io.Reader进行对比;更多方法的接口类型会告诉我们更多关于它的值持有的信息,并且对实现它的型要求更加格。那么关于interface{}型,它有任何方法,请讲出哪些具体的类型实现了它?
看上去好像有用,但實際上interface{}被稱爲空接口型是不可或缺的。因空接口類型對實現它的類型沒有要求,所以我可以任意一個值賦給空接口型。
看上去好像有用,但实际上interface{}被称为空接口型是不可或缺的。因空接口类型对实现它的类型没有要求,所以我可以任意一个值赋给空接口型。
```go
var any interface{}
@@ -75,29 +75,29 @@ any = map[string]int{"one": 1}
any = new(bytes.Buffer)
```
管不是很明顯,從本書最早的的例子中我就已在使用空接口型。它允像fmt.Println或者5.7章中的errorf函接受任何型的參數
管不是很明显,从本书最早的的例子中我就已在使用空接口型。它允像fmt.Println或者5.7章中的errorf函接受任何型的参数
對於創建的一interface{}值持有一booleanfloatstringmappointer或者任意其它的型;我們當然不能直接它持有的值做操作,因interface{}有任何方法。我們會在7.10章中到一種用類型斷言來獲取interface{}中值的方法。
对于创建的一interface{}值持有一booleanfloatstringmappointer或者任意其它的型;我们当然不能直接它持有的值做操作,因interface{}有任何方法。我们会在7.10章中到一种用类型断言来获取interface{}中值的方法。
接口實現隻依賴於判斷的兩個類型的方法,所以有必要定義一個具體類型和它實現的接口之間的關繫。也就是説,嚐試文檔化和斷言這種關繫幾乎沒有用,所以併沒有通程序強製定義。下面的定義在編譯期斷言一*bytes.Buffer的值實現了io.Writer接口型:
接口实现只依赖于判断的两个类型的方法,所以有必要定义一个具体类型和它实现的接口之间的关系。也就是说,尝试文档化和断言这种关系几乎没有用,所以并没有通程序强制定义。下面的定义在编译期断言一*bytes.Buffer的值实现了io.Writer接口型:
```go
// *bytes.Buffer must satisfy io.Writer
var w io.Writer = new(bytes.Buffer)
```
任意*bytes.Buffer的值甚至包括nil通(*bytes.Buffer)(nil)進行顯示的轉換都實現了這個接口,所以我不必分配一新的量。且因爲我們絶不會引用量w可以使用空標識符來來進行代替。的看,這些變化可以讓我們得到一個更樸素的版本:
任意*bytes.Buffer的值甚至包括nil通(*bytes.Buffer)(nil)进行显示的转换都实现了这个接口,所以我不必分配一新的量。且因为我们绝不会引用量w可以使用空标识符来来进行代替。的看,这些变化可以让我们得到一个更朴素的版本:
```go
// *bytes.Buffer must satisfy io.Writer
var _ io.Writer = (*bytes.Buffer)(nil)
```
非空的接口型比如io.Writer常被指針類型實現,尤其當一個或多接口方法像Write方法那樣隱式的接收者帶來變化的候。一個結構體的指是非常常的承方法的型。
非空的接口型比如io.Writer常被指针类型实现,尤其当一个或多接口方法像Write方法那样隐式的接收者带来变化的候。一个结构体的指是非常常的承方法的型。
但是不意味着有指針類型滿足接口型,甚至一些有置方法的接口型也可能被Go言中其它的引用類型實現。我們已經看過slice型的方法(geometry.Path, §6.1)和map型的方法(url.Values, §6.2.1)後面還會看到函數類型的方法的例子(http.HandlerFunc, §7.7)。甚至基本的型也可能會實現一些接口;就如我在7.4章中看到的time.Duration類型實現了fmt.Stringer接口。
但是不意味着有指针类型满足接口型,甚至一些有置方法的接口型也可能被Go言中其它的引用类型实现。我们已经看过slice型的方法(geometry.Path, §6.1)和map型的方法(url.Values, §6.2.1)后面还会看到函数类型的方法的例子(http.HandlerFunc, §7.7)。甚至基本的型也可能会实现一些接口;就如我在7.4章中看到的time.Duration类型实现了fmt.Stringer接口。
個具體的類型可能實現了很多不相的接口。考在一個組織出售字文化品比如音樂,電影和籍的程序中可能定了下列的具體類型:
个具体的类型可能实现了很多不相的接口。考在一个组织出售字文化品比如音乐,电影和籍的程序中可能定了下列的具体类型:
```
Album
@@ -109,7 +109,7 @@ TVEpisode
Track
```
可以把每抽象的特用接口表示。一些特性對於所有的些文化品都是共通的,例如標題,創作日期和作者列表。
可以把每抽象的特用接口表示。一些特性对于所有的些文化品都是共通的,例如标题,创作日期和作者列表。
```go
type Artifact interface {
@@ -118,7 +118,7 @@ type Artifact interface {
Created() time.Time
}
```
其它的一些特性隻對特定型的文化品才有。和文字排版特性相關的隻有books和magazines還有隻有movies和TV集和屏幕分辨率相
其它的一些特性只对特定型的文化品才有。和文字排版特性相关的只有books和magazines还有只有movies和TV集和屏幕分辨率相
```go
type Text interface {
@@ -139,7 +139,7 @@ type Video interface {
}
```
些接口不止是一有用的方式來分組相關的具體類型和表示他們之間的共同特定。我們後面可能會發現其它的分組。舉例,如果我們發現我們需要以同的方式理Audio和Video可以定義一個Streamer接口代表它們之間相同的部分而不必對已經存在的型做改
些接口不止是一有用的方式来分组相关的具体类型和表示他们之间的共同特定。我们后面可能会发现其它的分组。举例,如果我们发现我们需要以同的方式理Audio和Video可以定义一个Streamer接口代表它们之间相同的部分而不必对已经存在的型做改
```go
type Streamer interface {
@@ -149,4 +149,4 @@ type Streamer interface {
}
```
每一個具體類型的組基於它們相同的行可以表示成一接口型。不像基於類的語言,他們一個類實現的接口集合需要進行顯式的定在Go言中我可以在需要的候定義一個新的抽象或者特定特點的組,而不需要改具體類型的定義。當具體的類型來自不同的作者時這種方式特别有用。然也確實沒有必要在具體的類型中指出些共性。
每一个具体类型的组基于它们相同的行可以表示成一接口型。不像基于类的语言,他们一个类实现的接口集合需要进行显式的定在Go言中我可以在需要的候定义一个新的抽象或者特定特点的组,而不需要改具体类型的定义。当具体的类型来自不同的作者时这种方式特别有用。然也确实没有必要在具体的类型中指出些共性。

View File

@@ -1,6 +1,6 @@
## 7.4. flag.Value接口
在本章,我們會學到另一個標準的接口型flag.Value是怎麽幫助命令行標記定義新的符的。思考下面這個會休眠特定時間的程序:
在本章,我们会学到另一个标准的接口型flag.Value是怎么帮助命令行标记定义新的符的。思考下面这个会休眠特定时间的程序:
<u></i>gopl.io/ch7/sleep</i></u>
```go
@@ -14,7 +14,7 @@ func main() {
}
```
在它休眠前它打印出休眠的時間週期。fmt包調用time.Duration的String方法打印這個時間週期是以用友好的解方式,而不是一個納秒數字:
在它休眠前它打印出休眠的时间周期。fmt包用time.Duration的String方法打印这个时间周期是以用友好的解方式,而不是一个纳秒数字:
```
$ go build gopl.io/ch7/sleep
@@ -22,7 +22,7 @@ $ ./sleep
Sleeping for 1s...
```
認情況下,休眠期是一秒,但是可以通 -period 這個命令行標記來控製。flag.Duration函數創建一time.Duration型的標記變量併且允許用戶通過多種用戶友好的方式來設置這個變量的大小,這種方式包括和String方法相同的符排版形式。這種對稱設計使得用交互良好。
认情况下,休眠期是一秒,但是可以通 -period 这个命令行标记来控制。flag.Duration函数创建一time.Duration型的标记变量并且允许用户通过多种用户友好的方式来设置这个变量的大小,这种方式包括和String方法相同的符排版形式。这种对称设计使得用交互良好。
```
$ ./sleep -period 50ms
@@ -35,7 +35,7 @@ $ ./sleep -period "1 day"
invalid value "1 day" for flag -period: time: invalid duration 1 day
```
爲時間週期標記值非常的有用,所以這個特性被建到了flag包中但是我們爲我們自己的數據類型定新的標記符號是簡單容易的。我們隻需要定義一個實現flag.Value接口的型,如下:
为时间周期标记值非常的有用,所以这个特性被建到了flag包中但是我们为我们自己的数据类型定新的标记符号是简单容易的。我们只需要定义一个实现flag.Value接口的型,如下:
```go
package flag
@@ -47,9 +47,9 @@ type Value interface {
}
```
String方法格式化標記的值用在命令行幫組消息中;這樣每一flag.Value也是一fmt.Stringer。Set方法解析它的字符串參數併且更新標記變量的值。實際Set方法和String是兩個相反的操作,所以最好的法就是對他們使用相同的解方式。
String方法格式化标记的值用在命令行帮组消息中;这样每一flag.Value也是一fmt.Stringer。Set方法解析它的字符串参数并且更新标记变量的值。实际Set方法和String是两个相反的操作,所以最好的法就是对他们使用相同的解方式。
讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定度的celsiusFlag型。意celsiusFlag嵌了一Celsius型(§2.5),因此不用實現本身就已有String方法了。爲了實現flag.Value們隻需要定Set方法
让我们定义一个允许通过摄氏度或者华氏温度变换的形式指定度的celsiusFlag型。意celsiusFlag嵌了一Celsius型(§2.5),因此不用实现本身就已有String方法了。为了实现flag.Value们只需要定Set方法
<u><i>gopl.io/ch7/tempconv</i></u>
```go
@@ -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,
@@ -87,7 +87,7 @@ func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
}
```
在我可以始在我的程序中使用新的標記
在我可以始在我的程序中使用新的标记
<u><i>gopl.io/ch7/tempflag</i></u>
```go
@@ -99,7 +99,7 @@ func main() {
}
```
下面是典型的景:
下面是典型的景:
```
$ go build gopl.io/ch7/tempflag
@@ -120,6 +120,6 @@ Usage of ./tempflag:
the temperature (default 20°C)
```
**練習 7.6** tempFlag加入支持開爾文溫度。
**练习 7.6** tempFlag加入支持开尔文温度。
**練習 7.7**釋爲什麽幫助信息在它的默值是20.0有包含°C的情況下輸出了°C。
**练习 7.7**释为什么帮助信息在它的默值是20.0有包含°C的情况下输出了°C。

View File

@@ -1,8 +1,8 @@
### 7.5.1. 警告:一包含nil指的接口不是nil接口
### 7.5.1. 警告:一包含nil指的接口不是nil接口
不包含任何值的nil接口值和一個剛好包含nil指的接口值是不同的。這個細微區别産生了一容易倒每Go程序的陷阱。
不包含任何值的nil接口值和一个刚好包含nil指的接口值是不同的。这个细微区别产生了一容易倒每Go程序的陷阱。
思考下面的程序。debug變量設置爲truemain函數會將f函數的輸出收集到一bytes.Buffer型中。
思考下面的程序。debug变量设置为truemain函数会将f函数的输出收集到一bytes.Buffer型中。
```go
const debug = true
@@ -27,7 +27,7 @@ func f(out io.Writer) {
}
```
可能會預計當把變量debug設置爲false可以禁止對輸出的收集,但是實際上在out.Write方法調用時程序生了panic
可能会预计当把变量debug设置为false可以禁止对输出的收集,但是实际上在out.Write方法调用时程序生了panic
```go
if out != nil {
@@ -35,13 +35,13 @@ if out != nil {
}
```
main函數調用函數f時,它f函的out參數賦了一\*bytes.Buffer的空指所以out的動態值是nil。然而它的動態類型是\*bytes.Buffer意思就是out量是一包含空指值的非空接口(如7.5),所以防禦性檢査out!=nil的果依然是true。
main函数调用函数f时,它f函的out参数赋了一\*bytes.Buffer的空指所以out的动态值是nil。然而它的动态类型是\*bytes.Buffer意思就是out量是一包含空指值的非空接口(如7.5),所以防御性检查out!=nil的果依然是true。
![](../images/ch7-05.png)
動態分配機製依然定(\*bytes.Buffer).Write的方法會被調用,但是次的接收者的值是nil。對於一些如\*os.File的nil是一有效的接收者(§6.2.1),但是\*bytes.Buffer型不在這些類型中。這個方法會被調用,但是當它嚐試去獲取緩衝區時會發生panic。
动态分配机制依然定(\*bytes.Buffer).Write的方法会被调用,但是次的接收者的值是nil。对于一些如\*os.File的nil是一有效的接收者(§6.2.1),但是\*bytes.Buffer型不在这些类型中。这个方法会被调用,但是当它尝试去获取缓冲区时会发生panic。
問題在於盡管一nil的\*bytes.Buffer指針有實現這個接口的方法,它也不滿足這個接口具的行上的要求。特别是這個調用違反了(\*bytes.Buffer).Write方法的接收者非空的含先覺條件,所以nil指針賦給這個接口是錯誤的。解方案就是main函中的量buf的型改io.Writer因此可以避免一始就將一個不完全的值賦值給這個接口:
问题在于尽管一nil的\*bytes.Buffer指针有实现这个接口的方法,它也不满足这个接口具的行上的要求。特别是这个调用违反了(\*bytes.Buffer).Write方法的接收者非空的含先觉条件,所以nil指针赋给这个接口是错误的。解方案就是main函中的量buf的型改io.Writer因此可以避免一始就将一个不完全的值赋值给这个接口:
```go
var buf io.Writer
@@ -51,4 +51,4 @@ if debug {
f(buf) // OK
```
在我們已經把接口值的技巧都完了,讓我們來看更多的一些在Go標準庫中的重要接口型。在下面的三章中,我們會看到接口型是怎用在排序web服務,錯誤處理中的。
在我们已经把接口值的技巧都完了,让我们来看更多的一些在Go标准库中的重要接口型。在下面的三章中,我们会看到接口型是怎用在排序web服务,错误处理中的。

View File

@@ -1,7 +1,7 @@
## 7.5. 接口值
概念上講一個接口的值,接口值,由兩個部分成,一個具體的類型和那個類型的值。它們被稱爲接口的動態類型和動態值。對於像Go語言這種靜態類型的言,型是編譯期的概念;因此一個類型不是一值。在我的概念模型中,一些提供每個類型信息的值被稱爲類型描述符,比如型的名和方法。在一接口值中,型部分代表之相關類型的描述符。
概念上讲一个接口的值,接口值,由两个部分成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。对于像Go语言这种静态类型的言,型是编译期的概念;因此一个类型不是一值。在我的概念模型中,一些提供每个类型信息的值被称为类型描述符,比如型的名和方法。在一接口值中,型部分代表之相关类型的描述符。
下面4個語句中,量w得到了3不同的值。(始和最的值是相同的)
下面4个语句中,量w得到了3不同的值。(始和最的值是相同的)
```go
var w io.Writer
@@ -10,92 +10,92 @@ w = new(bytes.Buffer)
w = nil
```
讓我們進一步察在每一個語句後的w量的值和動態行爲。第一個語句定義了變量w:
让我们进一步察在每一个语句后的w量的值和动态行为。第一个语句定义了变量w:
```go
var w io.Writer
```
在Go言中,變量總是被一個定義明確的值初始化,使接口型也不例外。對於一個接口的零值就是它的型和值的部分都是nil7.1)。
在Go言中,变量总是被一个定义明确的值初始化,使接口型也不例外。对于一个接口的零值就是它的型和值的部分都是nil7.1)。
![](../images/ch7-01.png)
接口值基它的動態類型被描述空或非空,所以是一空的接口值。你可以通使用w==nil或者w!=nil來判讀接口值是否空。調用一空接口值上的任意方法都會産生panic:
接口值基它的动态类型被描述空或非空,所以是一空的接口值。你可以通使用w==nil或者w!=nil来判读接口值是否空。用一空接口值上的任意方法都会产生panic:
```go
w.Write([]byte("hello")) // panic: nil pointer dereference
```
第二個語句將一個*os.File型的值賦給變量w:
第二个语句将一个*os.File型的值赋给变量w:
```go
w = os.Stdout
```
這個賦值過程調用了一個具體類型到接口型的隱式轉換,這和顯式的使用io.Writer(os.Stdout)是等的。這類轉換不管是式的還是隱式的,都會刻畵出操作到的型和值。這個接口值的動態類型被設爲*os.Stdout指針的類型描述符,它的動態值持有os.Stdout的拷貝;這是一代表處理標準輸出的os.File類型變量的指針(圖7.2)。
这个赋值过程调用了一个具体类型到接口型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等的。这类转换不管是式的还是隐式的,都会刻画出操作到的型和值。这个接口值的动态类型被设为*os.Stdout指针的类型描述符,它的动态值持有os.Stdout的拷贝;这是一代表处理标准输出的os.File类型变量的指针(图7.2)。
![](../images/ch7-02.png)
調用一包含\*os.File型指的接口值的Write方法使得(\*os.File).Write方法被調用。這個調用輸出“hello”。
用一包含\*os.File型指的接口值的Write方法使得(\*os.File).Write方法被用。这个调用输出“hello”。
```go
w.Write([]byte("hello")) // "hello"
```
通常在編譯期,我不知道接口值的動態類型是什,所以一接口上的調用必使用動態分配。因不是直接進行調用,所以編譯器必把代生成在型描述符的方法Write上後間接調用那地址。這個調用的接收者是一接口動態值的拷os.Stdout。效果和下面這個直接調用一
通常在编译期,我不知道接口值的动态类型是什,所以一接口上的用必使用动态分配。因不是直接进行调用,所以编译器必把代生成在型描述符的方法Write上后间接调用那地址。这个调用的接收者是一接口动态值的拷os.Stdout。效果和下面这个直接用一
```go
os.Stdout.Write([]byte("hello")) // "hello"
```
第三個語句給接口值了一*bytes.Buffer型的值
第三个语句给接口值了一*bytes.Buffer型的值
```go
w = new(bytes.Buffer)
```
現在動態類型是*bytes.Buffer併且動態值是一指向新分配的緩衝區的指針(圖7.3)。
现在动态类型是*bytes.Buffer并且动态值是一指向新分配的缓冲区的指针(图7.3)。
![](../images/ch7-03.png)
Write方法的調用也使用了和之前一樣的機製
Write方法的用也使用了和之前一样的机制
```go
w.Write([]byte("hello")) // writes "hello" to the bytes.Buffers
```
這次類型描述符是\*bytes.Buffer所以調用了(\*bytes.Buffer).Write方法且接收者是該緩衝區的地址。這個調用把字符串“hello”添加到緩衝區中。
这次类型描述符是\*bytes.Buffer所以用了(\*bytes.Buffer).Write方法且接收者是该缓冲区的地址。这个调用把字符串“hello”添加到缓冲区中。
,第四個語句將nil賦給了接口值:
,第四个语句将nil赋给了接口值:
```go
w = nil
```
這個重置它所有的部分都設爲nil值量w恢到和它之前定義時相同的狀態圖,在7.1中可以看到。
这个重置它所有的部分都设为nil值量w恢到和它之前定义时相同的状态图,在7.1中可以看到。
接口值可以持有任意大的動態值。例如,表示時間實例的time.Time型,這個類型有幾個對外不公的字段。我們從它上面建一接口值,
接口值可以持有任意大的动态值。例如,表示时间实例的time.Time型,这个类型有几个对外不公的字段。我们从它上面建一接口值,
```go
var x interface{} = time.Now()
```
果可能和7.4相似。概念上,不接口值多大,動態值總是可以容下它。(這隻是一概念上的模型;具體的實現可能非常不同)
果可能和7.4相似。概念上,不接口值多大,动态值总是可以容下它。(这只是一概念上的模型;具体的实现可能非常不同)
![](../images/ch7-04.png)
接口值可以使用==和!=來進行比較。兩個接口值相等僅當它們都是nil值或者它們的動態類型相同併且動態值也根據這個動態類型的==操作相等。因接口值是可比的,所以它可以用在map的或者作switch句的操作
接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。因接口值是可比的,所以它可以用在map的或者作switch句的操作
然而,如果兩個接口值的動態類型相同,但是這個動態類型是不可比的(比如切片),將它們進行比較就會失敗併且panic:
然而,如果两个接口值的动态类型相同,但是这个动态类型是不可比的(比如切片),将它们进行比较就会失败并且panic:
```go
var x interface{} = []int{1, 2, 3}
fmt.Println(x == x) // panic: comparing uncomparable type []int
```
慮到這點,接口型是非常與衆不同的。其它型要是安全的可比較類型(如基本型和指)要是完全不可比較的類型(如切片,映射型,和函),但是在比接口值或者包含了接口值的聚合類型時,我們必須要意識到潛在的panic。同樣的風險也存在使用接口作map的或者switch的操作數。隻能比你非常定它們的動態值是可比較類型的接口值。
虑到这点,接口型是非常与众不同的。其它型要是安全的可比较类型(如基本型和指)要是完全不可比较的类型(如切片,映射型,和函),但是在比接口值或者包含了接口值的聚合类型时,我们必须要意识到潜在的panic。同样的风险也存在使用接口作map的或者switch的操作数。只能比你非常定它们的动态值是可比较类型的接口值。
當我們處理錯誤或者調試的過程中,得知接口值的動態類型是非常有助的。所以我使用fmt包的%T作:
当我们处理错误或者调试的过程中,得知接口值的动态类型是非常有助的。所以我使用fmt包的%T作:
```go
var w io.Writer
@@ -106,6 +106,6 @@ w = new(bytes.Buffer)
fmt.Printf("%T\n", w) // "*bytes.Buffer"
```
在fmt包部,使用反射來獲取接口動態類型的名。我們會在第12章中到反射相的知
在fmt包部,使用反射来获取接口动态类型的名。我们会在第12章中到反射相的知
{% include "./ch7-05-1.md" %}

View File

@@ -1,9 +1,9 @@
## 7.6. sort.Interface接口
排序操作和字符串格式化一是很多程序常使用的操作。管一最短的快排程序要15行就可以搞定但是一個健壯的實現需要更多的代碼,併且我不希望每次我需要的候都重或者拷貝這些代
排序操作和字符串格式化一是很多程序常使用的操作。管一最短的快排程序要15行就可以搞定但是一个健壮的实现需要更多的代码,并且我不希望每次我需要的候都重或者拷贝这些代
的是sort包置的提供了根一些排序函數來對任何序列排序的功能。它的設計非常到。在很多言中,排序算法都是和序列數據類型關聯,同排序函和具體類型元素關聯。相比之下Go言的sort.Sort函數不會對具體的序列和它的元素做任何假。相反,它使用了一接口型sort.Interface指定通用的排序算法和可能被排序到的序列型之間的約定。這個接口的實現由序列的具表示和它希望排序的元素定,序列的表示常是一切片。
的是sort包置的提供了根一些排序函数来对任何序列排序的功能。它的设计非常到。在很多言中,排序算法都是和序列数据类型关联,同排序函和具体类型元素关联。相比之下Go言的sort.Sort函数不会对具体的序列和它的元素做任何假。相反,它使用了一接口型sort.Interface指定通用的排序算法和可能被排序到的序列型之间的约定。这个接口的实现由序列的具表示和它希望排序的元素定,序列的表示常是一切片。
個內置的排序算法需要知道三個東西:序列的度,表示兩個元素比較的結果,一種交換兩個元素的方式;就是sort.Interface的三方法:
个内置的排序算法需要知道三个东西:序列的度,表示两个元素比较的结果,一种交换两个元素的方式;就是sort.Interface的三方法:
```go
package sort
@@ -15,7 +15,7 @@ type Interface interface {
}
```
爲了對序列行排序,我需要定義一個實現了這三個方法的型,然後對這個類型的一個實例應用sort.Sort函。思考對一個字符串切片行排序,可能是最簡單的例子了。下面是這個新的型StringSlice和它的LenLess和Swap方法
为了对序列行排序,我需要定义一个实现了这三个方法的型,然后对这个类型的一个实例应用sort.Sort函。思考对一个字符串切片行排序,可能是最简单的例子了。下面是这个新的型StringSlice和它的LenLess和Swap方法
```go
type StringSlice []string
@@ -24,21 +24,21 @@ func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
```
在我可以通像下面這樣將一個切片轉換爲一個StringSlice類型來進行排序:
在我可以通像下面这样将一个切片转换为一个StringSlice类型来进行排序:
```go
sort.Sort(StringSlice(names))
```
這個轉換得到一相同度,容量,和基names數組的切片值;併且這個切片值的型有三排序需要的方法。
这个转换得到一相同度,容量,和基names数组的切片值;并且这个切片值的型有三排序需要的方法。
字符串切片的排序是很常用的需要所以sort包提供了StringSlice也提供了Strings函數能讓上面這些調用簡化成sort.Strings(names)。
字符串切片的排序是很常用的需要所以sort包提供了StringSlice也提供了Strings函数能让上面这些调用简化成sort.Strings(names)。
里用到的技很容易用到其它排序序列中,例如我可以忽略大些或者含有特殊的字符。(本使用Go程序索引詞和頁碼進行排序也用到了這個技術,對羅馬數字做了額外邏輯處理。)對於更複雜的排序,我使用相同的方法,但是用更複雜的數據結構和更複雜地實現sort.Interface的方法。
里用到的技很容易用到其它排序序列中,例如我可以忽略大些或者含有特殊的字符。(本使用Go程序索引词和页码进行排序也用到了这个技术,对罗马数字做了额外逻辑处理。)对于更复杂的排序,我使用相同的方法,但是用更复杂的数据结构和更复杂地实现sort.Interface的方法。
們會運行上面的例子來對一個表格中的音播放列表行排序。每track都是單獨的一行,每一列都是這個track的性像藝術家,標題,和運行時間。想象一個圖形用界面來呈現這個表格,併且點擊一個屬性的頂部會使這個列表按照這個屬性進行排序;再一次點擊相同性的頂部會進行逆向排序。讓我們看下每個點擊會發生什麽響應
们会运行上面的例子来对一个表格中的音播放列表行排序。每track都是单独的一行,每一列都是这个track的性像艺术家,标题,和运行时间。想象一个图形用界面来呈现这个表格,并且点击一个属性的顶部会使这个列表按照这个属性进行排序;再一次点击相同性的顶部会进行逆向排序。让我们看下每个点击会发生什么响应
下面的量tracks包好了一播放列表。One of the authors apologizes for the other authors musical tastes.)每元素都不是Track本身而是指向它的指針。盡管我在下面的代中直接存Tracks也可以工作sort函數會交換很多元素,所以如果每元素都是指針會更快而不是全部Track型,指是一個機器字碼長度而Track型可能是八或更多。
下面的量tracks包好了一播放列表。One of the authors apologizes for the other authors musical tastes.)每元素都不是Track本身而是指向它的指针。尽管我在下面的代中直接存Tracks也可以工作sort函数会交换很多元素,所以如果每元素都是指针会更快而不是全部Track型,指是一个机器字码长度而Track型可能是八或更多。
<u><i>gopl.io/ch7/sorting</i></u>
```go
@@ -66,7 +66,7 @@ func length(s string) time.Duration {
}
```
printTracks函數將播放列表打印成一表格。一個圖形化的展示可能更好,但是這個小程序使用text/tabwriter包生成一列是整齊對齊和隔的表格,像下面展示的這樣。註意到*tabwriter.Writer是滿足io.Writer接口的。它收集每一片向它的數據它的Flush方法格式化整表格併且將它寫向os.Stdout標準輸出)。
printTracks函数将播放列表打印成一表格。一个图形化的展示可能更好,但是这个小程序使用text/tabwriter包生成一列是整齐对齐和隔的表格,像下面展示的这样。注意到*tabwriter.Writer是足io.Writer接口的。它收集每一片向它的数据它的Flush方法格式化整表格并且将它写向os.Stdout标准输出)。
```go
func printTracks(tracks []*Track) {
@@ -81,7 +81,7 @@ func printTracks(tracks []*Track) {
}
```
了能按照Artist字段播放列表行排序,我們會像對StringSlice那樣定義一個新的有必LenLess和Swap方法的切片型。
了能按照Artist字段播放列表行排序,我们会像对StringSlice那样定义一个新的有必LenLess和Swap方法的切片型。
```go
type byArtist []*Track
@@ -90,13 +90,13 @@ 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))
```
在按照artist對這個切片行排序printTrack的出如下
在按照artist对这个切片行排序printTrack的出如下
```
Title Artist Album Year Length
@@ -107,13 +107,13 @@ Ready 2 Go Martin Solveig Smash 2011 4m24s
Go Moby Moby 1992 3m37s
```
如果用第二次求“按照artist排序”們會對tracks行逆向排序。然而我不需要定義一個有顛倒Less方法的新型byReverseArtistsort包中提供了Reverse函數將排序順序轉換成逆序。
如果用第二次求“按照artist排序”们会对tracks行逆向排序。然而我不需要定义一个有颠倒Less方法的新型byReverseArtistsort包中提供了Reverse函数将排序顺序转换成逆序。
```go
sort.Sort(sort.Reverse(byArtist(tracks)))
```
在按照artist對這個切片行逆向排序printTrack的出如下
在按照artist对这个切片行逆向排序printTrack的出如下
```
Title Artist Album Year Length
@@ -124,7 +124,7 @@ Go Delilah From the Roots Up 2012 3m38s
Go Ahead Alicia Keys As I Am 2007 4m36s
```
sort.Reverse函值得行更近一步的學習因爲它使用了(§6.3)章中的合,是一重要的思路。sort包定了一不公的struct型reverse它嵌入了一sort.Interface。reverse的Less方法調用了嵌的sort.Interface值的Less方法但是通過交換索引的方式使排序結果變成逆序。
sort.Reverse函值得行更近一步的学习因为它使用了(§6.3)章中的合,是一重要的思路。sort包定了一不公的struct型reverse它嵌入了一sort.Interface。reverse的Less方法用了嵌的sort.Interface值的Less方法但是通过交换索引的方式使排序结果变成逆序。
```go
package sort
@@ -136,9 +136,9 @@ func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
func Reverse(data Interface) Interface { return reverse{data} }
```
reverse的另外兩個方法Len和Swap式地由原有嵌的sort.Interface提供。因reverse是一不公開的類型,所以出函Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
reverse的另外两个方法Len和Swap式地由原有嵌的sort.Interface提供。因reverse是一不公开的类型,所以出函Reverse函数返回一个包含原有sort.Interface值的reverse类型实例。
了可以按照不同的列行排序,我們必須定義一個新的型例如byYear
了可以按照不同的列行排序,我们必须定义一个新的型例如byYear
```go
type byYear []*Track
@@ -147,7 +147,7 @@ func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
```
在使用sort.Sort(byYear(tracks))按照年tracks行排序printTrack展示了一個按時間先後順序的列表:
在使用sort.Sort(byYear(tracks))按照年tracks行排序printTrack展示了一个按时间先后顺序的列表:
```
Title Artist Album Year Length
@@ -158,7 +158,7 @@ Ready 2 Go Martin Solveig Smash 2011 4m24s
Go Delilah From the Roots Up 2012 3m38s
```
對於我們需要的每切片元素型和每排序函,我需要定義一個新的sort.Interface實現。如你所Len和Swap方法對於所有的切片型都有相同的定。下例子,具體的類型customSort會將一個切片和函數結合,使我們隻需要寫比較函數就可以定義一個新的排序。順便説下,實現了sort.Interface的具體類型不一定是切片customSort是一個結構體類型。
对于我们需要的每切片元素型和每排序函,我需要定义一个新的sort.Interface实现。如你所Len和Swap方法对于所有的切片型都有相同的定。下例子,具体的类型customSort会将一个切片和函数结合,使我们只需要写比较函数就可以定义一个新的排序。顺便说下,实现了sort.Interface的具体类型不一定是切片customSort是一个结构体类型。
```go
type customSort struct {
@@ -171,7 +171,7 @@ func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
```
讓我們定義一個多層的排序函,它主要的排序鍵是標題,第二個鍵是年,第三個鍵是運行時間Length。下面是排序的調用,其中這個排序使用了匿名排序函
让我们定义一个多层的排序函,它主要的排序键是标题,第二个键是年,第三个键是运行时间Length。下面是排序的用,其中这个排序使用了匿名排序函
```go
sort.Sort(customSort{tracks, func(x, y *Track) bool {
@@ -188,7 +188,7 @@ sort.Sort(customSort{tracks, func(x, y *Track) bool {
}})
```
下面是排序的果。意到兩個標題是“Go”的track按照標題排序是相同的但是在按照year排序上更久的那track先。
下面是排序的果。意到两个标题是“Go”的track按照标题排序是相同的但是在按照year排序上更久的那track先。
```
Title Artist Album Year Length
@@ -199,7 +199,7 @@ Go Ahead Alicia Keys As I Am 2007 4m36s
Ready 2 Go Martin Solveig Smash 2011 4m24s
```
盡管對長度爲n的序列排序需要 O(n log n)次比操作,檢査一個序列是否已有序至少需要n1次比。sort包中的IsSorted函數幫我們做這樣的檢査。像sort.Sort一它也使用sort.Interface對這個序列和它的排序函數進行抽象,但是它從不會調用Swap方法段代示范了IntsAreSorted和Ints函和IntSlice型的使用:
尽管对长度为n的序列排序需要 O(n log n)次比操作,检查一个序列是否已有序至少需要n1次比。sort包中的IsSorted函数帮我们做这样的检查。像sort.Sort一它也使用sort.Interface对这个序列和它的排序函数进行抽象,但是它从不会调用Swap方法段代示范了IntsAreSorted和Ints函和IntSlice型的使用:
```go
values := []int{3, 1, 4, 1}
@@ -212,10 +212,10 @@ fmt.Println(values) // "[4 3 1 1]"
fmt.Println(sort.IntsAreSorted(values)) // "false"
```
了使用方便sort包[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他型,例如[]int64或者[]uint管路也很簡單,還是依賴我們自己實現
了使用方便sort包[]int,[]string和[]float64的正常排序提供了特定版本的函数和类型。对于其他型,例如[]int64或者[]uint管路也很简单,还是依赖我们自己实现
**練習 7.8** 很多形界面提供了一個有狀態的多重排序表格插件:主要的排序是最近一次點擊過列頭的列,第二排序是第二最近點擊過列頭的列,等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重使用sort.Stable排序的方式。
**练习 7.8** 很多形界面提供了一个有状态的多重排序表格插件:主要的排序是最近一次点击过列头的列,第二排序是第二最近点击过列头的列,等等。定义一个sort.Interface的实现用在这样的表格中。比较这个实现方式和重使用sort.Stable排序的方式。
**練習 7.9** 使用html/template包 (§4.6) 替代printTrackstracks展示成一HTML表格。將這個解決方案用在前一個練習中,每次點擊一個列的頭部産生一HTTP請求來排序這個表格。
**练习 7.9** 使用html/template包 (§4.6) 替代printTrackstracks展示成一HTML表格。将这个解决方案用在前一个练习中,每次点击一个列的头部产生一HTTP请求来排序这个表格。
**練習 7.10** sort.Interface型也可以用在其它地方。編寫一個IsPalindrome(s sort.Interface) bool函表明序列s是否是文序列,換句話説反向排序不會改變這個序列。假如果!s.Less(i, j) && !s.Less(j, i)索引i和j上的元素相等。
**练习 7.10** sort.Interface型也可以用在其它地方。编写一个IsPalindrome(s sort.Interface) bool函表明序列s是否是文序列,换句话说反向排序不会改变这个序列。假如果!s.Less(i, j) && !s.Less(j, i)索引i和j上的元素相等。

View File

@@ -1,6 +1,6 @@
## 7.7. http.Handler接口
在第一章中,我粗略的了解了怎用net/http包去實現網絡客戶端(§1.5)和服器(§1.7)。在這個小節中,我們會對那些基http.Handler接口的服器API做更一步的學習
在第一章中,我粗略的了解了怎用net/http包去实现网络客户端(§1.5)和服器(§1.7)。在这个小节中,我们会对那些基http.Handler接口的服器API做更一步的学习
<u><i>net/http</i></u>
```go
@@ -13,9 +13,9 @@ type Handler interface {
func ListenAndServe(address string, h Handler) error
```
ListenAndServe函需要一例如“localhost:8000”的服器地址,和一所有求都可以分派的Handler接口例。它一直行,直到這個服務因爲一個錯誤而失(或者啟動失敗),它的返值一定是一非空的錯誤
ListenAndServe函需要一例如“localhost:8000”的服器地址,和一所有求都可以分派的Handler接口例。它一直行,直到这个服务因为一个错误而失(或者启动失败),它的返值一定是一非空的错误
想象一個電子商務網站,爲了銷售它的數據庫將它物品的格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清模型化爲一個命名database的map型,我們給這個類型一ServeHttp方法這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。
想象一个电子商务网站,为了销售它的数据库将它物品的格映射成美元。下面这个程序可能是能想到的最简单的实现了。它将库存清模型化为一个命名database的map型,我们给这个类型一ServeHttp方法这样它可以足http.Handler接口。这个handler会遍历整个map并输出物品信息。
<u><i>gopl.io/ch7/http1</i></u>
```go
@@ -37,14 +37,14 @@ func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
```
如果我們啟動這個服務
如果我们启动这个服务
```
$ go build gopl.io/ch7/http1
$ ./http1 &
```
用1.5中的取程序(如果你更喜可以使用web瀏覽器)來連接服器,我得到下面的出:
用1.5中的取程序(如果你更喜可以使用web浏览器)来连接服器,我得到下面的出:
```
$ go build gopl.io/ch1/fetch
@@ -53,7 +53,7 @@ shoes: $50.00
socks: $5.00
```
目前止,這個服務器不考URL隻能爲每個請求列出它全部的存清。更眞實的服務器會定義多個不同的URL每一個都會觸發一個不同的行爲。讓我們使用/list來調用已存在的這個行爲併且增加另一/price調用表明單個貨品的格,像這樣/price?item=socks指定一個請求參數
目前止,这个服务器不考URL只能为每个请求列出它全部的存清。更真实的服务器会定义多个不同的URL每一个都会触发一个不同的行为。让我们使用/list来调用已存在的这个行为并且增加另一/price用表明单个货品的格,像这样/price?item=socks指定一个请求参数
<u><i>gopl.io/ch7/http2</i></u>
```go
@@ -79,16 +79,16 @@ func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
```
在handler基URL的路部分req.URL.Path來決定執行什麽邏輯。如果這個handler不能識别這個路徑,它會通過調用w.WriteHeader(http.StatusNotFound)返迴客戶端一HTTP錯誤;這個檢査應該在向w入任何值前完成。(便提一下http.ResponseWriter是另一接口。它在io.Writer上增加了送HTTP相應頭的方法。)等效地,我可以使用用的http.Error函
在handler基URL的路部分req.URL.Path来决定执行什么逻辑。如果这个handler不能识别这个路径,它会通过调用w.WriteHeader(http.StatusNotFound)返回客户端一HTTP错误;这个检查应该在向w入任何值前完成。(便提一下http.ResponseWriter是另一接口。它在io.Writer上增加了送HTTP相应头的方法。)等效地,我可以使用用的http.Error函
```go
msg := fmt.Sprintf("no such page: %s\n", req.URL)
http.Error(w, msg, http.StatusNotFound) // 404
```
/price的case會調用URL的Query方法來將HTTP請求參數解析爲一個map或者更準確地説一個net/url包中url.Values(§6.2.1)型的多重映射。然找到第一item參數併査找它的格。如果這個貨品沒有找到會返迴一個錯誤
/price的case会调用URL的Query方法来将HTTP请求参数解析为一个map或者更准确地说一个net/url包中url.Values(§6.2.1)型的多重映射。然找到第一item参数并查找它的格。如果这个货品没有找到会返回一个错误
里是一和新服務器會話的例子:
里是一和新服务器会话的例子:
```
$ go build gopl.io/ch7/http2
@@ -107,12 +107,12 @@ $ ./fetch http://localhost:8000/help
no such page: /help
```
然我可以繼續向ServeHTTP方法中添加case但在一個實際的應用中將每個case中的邏輯定義到一個分開的方法或函數中會很實用。此外相近的URL可能需要相似的邏輯;例如幾個圖片文件可能有形如/images/\*.png的URL。因爲這些原因net/http包提供了一個請求多路器ServeMux來簡化URL和handlers的聯繫。一ServeMux一批http.Handler聚集到一個單一的http.Handler中。再一次可以看到滿足同一接口的不同型是可替web服務器將請求指派任意的http.Handler
而不需要考慮它後面的具體類型。
然我可以继续向ServeHTTP方法中添加case但在一个实际的应用中将每个case中的逻辑定义到一个分开的方法或函数中会很实用。此外相近的URL可能需要相似的逻辑;例如几个图片文件可能有形如/images/\*.png的URL。因为这些原因net/http包提供了一个请求多路器ServeMux来简化URL和handlers的联系。一ServeMux一批http.Handler聚集到一个单一的http.Handler中。再一次可以看到足同一接口的不同型是可替web服务器将请求指派任意的http.Handler
而不需要考虑它后面的具体类型。
對於更複雜的應一些ServeMux可以通過組合來處理更加錯綜複雜的路由需求。Go言目前有一個權威的web框架就像Ruby言有Rails和python有Django。這併不是説這樣的框架不存在而是Go語言標準庫中的建模就已非常活以至於這些框架都是不必要的。此外,管在一個項目早期使用框架是非常方便的,但是它們帶來額外的複雜度會使長期的維護更加睏難
对于更复杂的应一些ServeMux可以通过组合来处理更加错综复杂的路由需求。Go言目前有一个权威的web框架就像Ruby言有Rails和python有Django。这并不是说这样的框架不存在而是Go语言标准库中的建模就已非常活以至于这些框架都是不必要的。此外,管在一个项目早期使用框架是非常方便的,但是它们带来额外的复杂度会使长期的维护更加困难
在下面的程序中,我們創建一ServeMux且使用它URL和相應處理/list和/price操作的handler聯繫起來,這些操作邏輯都已被分到不同的方法中。然後我門在調用ListenAndServe函中使用ServeMux最主要的handler。
在下面的程序中,我们创建一ServeMux且使用它URL和相应处理/list和/price操作的handler联系起来,这些操作逻辑都已被分到不同的方法中。然后我门在调用ListenAndServe函中使用ServeMux最主要的handler。
<u><i>gopl.io/ch7/http3</i></u>
```go
@@ -144,15 +144,15 @@ func (db database) price(w http.ResponseWriter, req *http.Request) {
}
```
讓我們關註這兩個註冊到handlers上的調用。第一db.list是一方法值 (§6.4),它是下面這個類型的值
让我们关注这两个注册到handlers上的用。第一db.list是一方法值 (§6.4),它是下面这个类型的值
```go
func(w http.ResponseWriter, req *http.Request)
```
也就是db.list的調用會援引一接收者是db的database.list方法。所以db.list是一個實現了handler似行的函,但是因爲它沒有方法,所以它不滿足http.Handler接口且不能直接傳給mux.Handle。
也就是db.list的调用会援引一接收者是db的database.list方法。所以db.list是一个实现了handler似行的函,但是因为它没有方法,所以它不足http.Handler接口且不能直接传给mux.Handle。
句http.HandlerFunc(db.list)是一個轉換而非一個函數調用,因http.HandlerFunc是一個類型。它有如下的定
句http.HandlerFunc(db.list)是一个转换而非一个函数调用,因http.HandlerFunc是一个类型。它有如下的定
<u><i>net/http</i></u>
```go
@@ -165,9 +165,9 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
}
```
HandlerFunc示了在Go言接口機製中一些不同常的特點。這是一個有實現了接口http.Handler方法的函數類型。ServeHTTP方法的行爲調用了它本身的函。因此HandlerFunc是一個讓函數值滿足一接口的配器,里函數和這個接口有的方法有相同的函數籤名。實際上,這個技巧讓一個單一的型例如database以多方式滿足http.Handler接口種通過它的list方法種通過它的price方法等等。
HandlerFunc示了在Go言接口机制中一些不同常的特点。这是一个有实现了接口http.Handler方法的函数类型。ServeHTTP方法的行为调用了它本身的函。因此HandlerFunc是一个让函数值满足一接口的配器,里函数和这个接口有的方法有相同的函数签名。实际上,这个技巧让一个单一的型例如database以多方式足http.Handler接口种通过它的list方法种通过它的price方法等等。
handler通過這種方式註冊非常普遍ServeMux有一方便的HandleFunc方法幫我們簡化handler註冊代碼成這樣
handler通过这种方式注册非常普遍ServeMux有一方便的HandleFunc方法帮我们简化handler注册代码成这样
<u><i>gopl.io/ch7/http3a</i></u>
```go
@@ -175,11 +175,11 @@ 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值就可以工作。
所以了方便net/http包提供了一全局的ServeMux例DefaultServerMux和包别的http.Handle和http.HandleFunc函数。现在,了使用DefaultServeMux作为服务器的主handler不需要将它传给ListenAndServe函nil值就可以工作。
後服務器的主函可以化成:
后服务器的主函可以化成:
<u><i>gopl.io/ch7/http4</i></u>
```go
@@ -191,8 +191,8 @@ func main() {
}
```
,一重要的提示:就像我在1.7中提到的web服器在一新的程中調用每一handler所以handler取其它程或者這個handler本身的其它求也可以訪問的變量時一定要使用防措施比如鎖機製。我們後面的章中講到併發相關的知
,一重要的提示:就像我在1.7中提到的web服器在一新的程中用每一handler所以handler取其它程或者这个handler本身的其它求也可以访问的变量时一定要使用防措施比如锁机制。我们后面的章中讲到并发相关的知
**練習 7.11** 增加外的handler客服端可以建,取,更新和刪除數據庫記録。例如,一形如 `/update?item=socks&price=6`請求會更新存清里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(意:這個脩改會引入量同更新的問題
**练习 7.11** 增加外的handler客服端可以建,取,更新和删除数据库记录。例如,一形如 `/update?item=socks&price=6`请求会更新存清里一个货品的价格并且当这个货品不存在或价格无效时返回一个错误值。(意:这个修改会引入量同更新的问题
**練習 7.12** 改/list的handler它把出打印成一HTML的表格而不是文本。html/template包(§4.6)可能會對你有助。
**练习 7.12** 改/list的handler它把出打印成一HTML的表格而不是文本。html/template包(§4.6)可能会对你有助。

View File

@@ -1,6 +1,6 @@
## 7.8. error接口
從本書的開始,我就已經創建和使用過神祕的預定義error型,而且有解它究竟是什麽。實際上它就是interface型,這個類型有一個返迴錯誤信息的一方法:
从本书的开始,我就已经创建和使用过神秘的预定义error型,而且有解它究竟是什么。实际上它就是interface型,这个类型有一个返回错误信息的一方法:
```go
type error interface {
@@ -8,7 +8,7 @@ type error interface {
}
```
建一error最簡單的方法就是調用errors.New函,它會根據傳入的錯誤信息返迴一個新的error。整errors包僅隻有4行
建一error最简单的方法就是用errors.New函,它会根据传入的错误信息返回一个新的error。整errors包仅只有4行
```go
package errors
@@ -20,13 +20,13 @@ type errorString struct { text string }
func (e *errorString) Error() string { return e.text }
```
errorString的型是一個結構體而非一字符串,這是爲了保它表示的錯誤避免粗心(或有意)的更新。且因是指針類型*errorString滿足error接口而非errorString型,所以每New函數的調用都分配了一個獨特的和其他錯誤不相同的例。我也不想要重要的error例如io.EOF和一個剛好有相同錯誤消息的error比較後相等。
errorString的型是一个结构体而非一字符串,这是为了保它表示的错误避免粗心(或有意)的更新。且因是指针类型*errorString足error接口而非errorString型,所以每New函数的调用都分配了一个独特的和其他错误不相同的例。我也不想要重要的error例如io.EOF和一个刚好有相同错误消息的error比较后相等。
```go
fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false"
```
調用errors.New函是非常稀少的,因有一方便的封裝函數fmt.Errorf還會處理字符串格式化。我曾多次在第5章中用到它。
用errors.New函是非常稀少的,因有一方便的封装函数fmt.Errorf还会处理字符串格式化。我曾多次在第5章中用到它。
```go
package fmt
@@ -38,7 +38,7 @@ func Errorf(format string, args ...interface{}) error {
}
```
然*errorString可能是最簡單的錯誤類型,但遠非隻有它一。例如syscall包提供了Go言底層繫統調用API。在多平台上,它定義一個實現error接口的數字類型Errno且在Unix平台上Errno的Error方法會從一個字符串表中査找錯誤消息,如下面展示的這樣
然*errorString可能是最简单的错误类型,但远非只有它一。例如syscall包提供了Go言底层系统调用API。在多平台上,它定义一个实现error接口的数字类型Errno且在Unix平台上Errno的Error方法会从一个字符串表中查找错误消息,如下面展示的这样
```go
package syscall
@@ -60,7 +60,7 @@ func (e Errno) Error() string {
}
```
下面的語句創建了一持有Errno值2的接口值表示POSIX ENOENT狀況
下面的语句创建了一持有Errno值2的接口值表示POSIX ENOENT状况
```go
var err error = syscall.Errno(2)
@@ -68,8 +68,8 @@ fmt.Println(err.Error()) // "no such file or directory"
fmt.Println(err) // "no such file or directory"
```
err的值形化的呈現在圖7.6中。
err的值形化的呈现在图7.6中。
![](../images/ch7-06.png)
Errno是一個繫統調用錯誤的高效表示方式,它通過一個有限的集合行描述,且它滿足標準的錯誤接口。我們會在第7.11了解到其它滿足這個接口的型。
Errno是一个系统调用错误的高效表示方式,它通过一个有限的集合行描述,且它满足标准的错误接口。我们会在第7.11了解到其它满足这个接口的型。

View File

@@ -1,13 +1,13 @@
## 7.9. 示例: 表式求值
## 7.9. 示例: 表式求值
在本中,我們會構建一個簡單算術表達式的求值器。我們將使用一接口Expr表示Go言中任意的表式。現在這個接口不需要有方法,但是我們後面會爲它增加一些。
在本中,我们会构建一个简单算术表达式的求值器。我们将使用一接口Expr表示Go言中任意的表式。现在这个接口不需要有方法,但是我们后面会为它增加一些。
```go
// An Expr is an arithmetic expression.
type Expr interface{}
```
的表達式語言由浮點數符號(小數點);二元操作符+-\* 和/;一元操作符-x和+x調用pow(x,y)sin(x)和sqrt(x)的函例如x和pi的量;然也有括號和標準的優先級運算符。所有的值都是float64型。下面是一些表式的例子:
的表达式语言由浮点数符号(小数点);二元操作符+-\* 和/;一元操作符-x和+x用pow(x,y)sin(x)和sqrt(x)的函例如x和pi的量;然也有括号和标准的优先级运算符。所有的值都是float64型。下面是一些表式的例子:
```go
sqrt(A / pi)
@@ -15,7 +15,7 @@ pow(x, 3) + pow(y, 3)
(F - 32) * 5 / 9
```
下面的五個具體類型表示了具的表達式類型。Var型表示對一個變量的引用。(我很快知道爲什麽它可以被出。literal型表示一個浮點型常量。unary和binary型表示有一到兩個運算對象的算符表式,些操作可以是任意的Expr型。call型表示對一個函數的調用;我們限製它的fn字段能是powsin或者sqrt。
下面的五个具体类型表示了具的表达式类型。Var型表示对一个变量的引用。(我很快知道为什么它可以被出。literal型表示一个浮点型常量。unary和binary型表示有一到两个运算对象的算符表式,些操作可以是任意的Expr型。call型表示对一个函数的调用;我们限制它的fn字段能是powsin或者sqrt。
<u><i>gopl.io/ch7/eval</i></u>
```go
@@ -44,13 +44,13 @@ type call struct {
}
```
爲了計算一包含量的表式,我需要一environment變量將變量的名字映射成對應的值:
为了计算一包含量的表式,我需要一environment变量将变量的名字映射成对应的值:
```go
type Env map[Var]float64
```
也需要每表示式去定義一個Eval方法這個方法會根據給定的environment量返迴表達式的值。因爲每個表達式都必提供這個方法,我們將它加入到Expr接口中。這個包隻會對外公ExprEnv和Var型。調用方不需要取其它的表達式類型就可以使用這個求值器。
也需要每表示式去定义一个Eval方法这个方法会根据给定的environment量返回表达式的值。因为每个表达式都必提供这个方法,我们将它加入到Expr接口中。这个包只会对外公ExprEnv和Var型。用方不需要取其它的表达式类型就可以使用这个求值器。
```go
type Expr interface {
@@ -59,7 +59,7 @@ type Expr interface {
}
```
下面大家展示一個具體的Eval方法。Var型的這個方法對一個environment變量進行査找,如果這個變量沒有在environment中定義過這個方法會返迴一個零值literal型的這個方法簡單的返迴它眞實的值。
下面大家展示一个具体的Eval方法。Var型的这个方法对一个environment变量进行查找,如果这个变量没有在environment中定义过这个方法会返回一个零值literal型的这个方法简单的返回它真实的值。
```go
func (v Var) Eval(env Env) float64 {
@@ -71,7 +71,7 @@ func (l literal) Eval(_ Env) float64 {
}
```
unary和binary的Eval方法會遞歸的計算它的運算對象,然後將運算符op作用到它上。我們不將被零或無窮數除作爲一個錯誤,因爲它們都會産生一固定的結果無限。最call的這個方法會計算對於powsin或者sqrt函數的參數值,然後調用對應在math包中的函
unary和binary的Eval方法会递归的计算它的运算对象,然后将运算符op作用到它上。我们不将被零或无穷数除作为一个错误,因为它们都会产生一固定的结果无限。最call的这个方法会计算对于powsin或者sqrt函数的参数值,然后调用对应在math包中的函
```go
func (u unary) Eval(env Env) float64 {
@@ -111,9 +111,9 @@ func (c call) Eval(env Env) float64 {
}
```
一些方法會失敗。例如,一call表式可能未知的函或者錯誤的參數個數。用一個無效的算符如!或者<建一unary或者binary表式也是可能會發生的管下面提到的Parse函數不會這樣)。這些錯誤會讓Eval方法panic其它的錯誤算一個沒有在environment量中出現過的Var隻會讓Eval方法返迴一個錯誤的結所有的這些錯誤都可以通過在計算前檢査Expr來發現是我接下來要講的Check方法的工作但是讓我們先測試Eval方法
一些方法会失败。例如,一call表式可能未知的函或者错误的参数个数。用一个无效的算符如!或者<建一unary或者binary表式也是可能会发生的管下面提到的Parse函数不会这样)。这些错误会让Eval方法panic其它的错误算一个没有在environment量中出现过的Var只会让Eval方法返回一个错误的结所有的这些错误都可以通过在计算前检查Expr来发现是我接下来要讲的Check方法的工作但是让我们先测试Eval方法
下面的TestEval函數是對evaluator的一個測試它使用了我們會在第11章解的testing包但是在知道調用t.Errof會報告一個錯誤就足這個函數循環遍歷一個表格中的這個表格中定了三個表達式和針對每個表達式不同的環境變第一個表達式根據給定圓的面積A計算它的半第二個表達式通過兩個變量x和y計算兩個立方體的體積之和第三個表達式將華氏溫度F轉換成攝氏度
下面的TestEval函数是对evaluator的一个测试它使用了我们会在第11章解的testing包但是在知道用t.Errof会报告一个错误就足这个函数循环遍历一个表格中的这个表格中定了三个表达式和针对每个表达式不同的环境变第一个表达式根据给定圆的面积A计算它的半第二个表达式通过两个变量x和y计算两个立方体的体积之和第三个表达式将华氏温度F转换成摄氏度
```go
func TestEval(t *testing.T) {
@@ -151,15 +151,15 @@ func TestEval(t *testing.T) {
}
```
對於表格中的每一條記録這個測試會解析它的表式然後在環境變量中算它輸出結里我們沒有空間來展示Parse函但是如果你使用go get下載這個包你就可以看到這個函數
对于表格中的每一条记录这个测试会解析它的表式然后在环境变量中算它输出结里我们没有空间来展示Parse函但是如果你使用go get下载这个包你就可以看到这个函数
go test11.1) 命令會運行一包的測試用例
go test11.1) 命令会运行一包的测试用例
```
$ go test -v gopl.io/ch7/eval
```
這個-v標識可以讓我們看到測試用例打印的正常情下像這個一樣成功的測試用例阻止打印果的里是測試用例里fmt.Printf句的
这个-v标识可以让我们看到测试用例打印的正常情下像这个一样成功的测试用例阻止打印果的里是测试用例里fmt.Printf句的
```
sqrt(A / pi)
@@ -175,9 +175,9 @@ pow(x, 3) + pow(y, 3)
map[F:212] => 100
```
的是目前止所有的入都是合的格式但是我們的運氣不可能一直都有甚至在解釋型語言中爲了靜態錯誤檢査語法是非常常靜態錯誤就是不用行程序就可以檢測出來的錯誤通過將靜態檢査和動態的部分分可以快速的檢査錯誤併且對於多次檢査隻執行一次而不是每次表達式計算的候都進行檢査
的是目前止所有的入都是合的格式但是我们的运气不可能一直都有甚至在解释型语言中为了静态错误检查语法是非常常静态错误就是不用行程序就可以检测出来的错误通过将静态检查和动态的部分分可以快速的检查错误并且对于多次检查只执行一次而不是每次表达式计算的候都进行检查
讓我們往Expr接口中增加另一方法Check方法在一個表達式語義樹檢査出靜態錯誤們馬上會説明它的vars參數
让我们往Expr接口中增加另一方法Check方法在一个表达式语义树检查出静态错误们马上会说明它的vars参数
```go
type Expr interface {
@@ -187,7 +187,7 @@ type Expr interface {
}
```
的Check方法展示在下面literal和Var型的算不可能失所以這些類型的Check方法會返迴一個nil值對於unary和binary的Check方法首先檢査操作符是否有效後遞歸的檢査運算單相似地對於call的這個方法首先檢査調用的函是否已知且有有正確個數的參數然後遞歸的檢査每一個參數
的Check方法展示在下面literal和Var型的算不可能失所以这些类型的Check方法会返回一个nil值对于unary和binary的Check方法首先检查操作符是否有效后递归的检查运算单相似地对于call的这个方法首先检查调用的函是否已知且有有正确个数的参数然后递归的检查每一个参数
```go
func (v Var) Check(vars map[Var]bool) error {
@@ -236,7 +236,7 @@ func (c call) Check(vars map[Var]bool) error {
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
```
們在兩個組中有選擇地列出有問題的輸入和它得出的錯誤Parse函這里沒有出會報出一個語法錯誤和Check函數會報出語義錯誤
们在两个组中有选择地列出有问题的输入和它得出的错误Parse函这里没有出会报出一个语法错误和Check函数会报出语义错误
```
x % 2 unexpected '%'
@@ -248,11 +248,11 @@ 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複這些檢査
在第3.2们绘制了一个在编译器才定的函f(x,y)。在我可以解析检查和计算在字符串中的表可以建一个在运行时从客户端接收表式的web应用并且它会绘制这个函数的表示的可以使用集合vars来检查表达式是否是一个只有两个变,x和y的函——实际上是3为我们为了方便提供半大小r且我们会在计算前使用Check方法拒有格式问题的表这样我们就不在下面函的40000个计算过100x100个栅每一有4复这些检查
這個ParseAndCheck函混合了解析和檢査步驟的過
这个ParseAndCheck函混合了解析和检查步骤的过
<u><i>gopl.io/ch7/surface</i></u>
```go
@@ -279,7 +279,7 @@ func parseAndCheck(s string) (eval.Expr, error) {
}
```
爲了編寫這個web用,所有我需要做的就是下面這個plot函數,這個函數有和http.HandlerFunc相似的名:
为了编写这个web用,所有我需要做的就是下面这个plot函数,这个函数有和http.HandlerFunc相似的名:
```go
func plot(w http.ResponseWriter, r *http.Request) {
@@ -299,12 +299,12 @@ func plot(w http.ResponseWriter, r *http.Request) {
![](../images/ch7-07.png)
這個plot函解析和檢査在HTTP求中指定的表達式併且用它來創建一個兩個變量的匿名函數。這個匿名函數和來自原surface-plotting程序中的固定函f有相同的名,但是它算一個用戶提供的表式。環境變量中定了xy和半r。最plot調用surface函它就是gopl.io/ch3/surface中的主要函數,脩改後它可以接受plot中的函數和輸出io.Writer作爲參數,而不是使用固定的函f和os.Stdout。7.7中示了通程序生的3個麴面。
这个plot函解析和检查在HTTP求中指定的表达式并且用它来创建一个两个变量的匿名函数。这个匿名函数和来自原surface-plotting程序中的固定函f有相同的名,但是它算一个用户提供的表式。环境变量中定了xy和半r。最plot用surface函它就是gopl.io/ch3/surface中的主要函数,修改后它可以接受plot中的函数和输出io.Writer作为参数,而不是使用固定的函f和os.Stdout。7.7中示了通程序生的3个曲面。
**練習 7.13** Expr增加一String方法打印美觀的語法樹。當再一次解析的候,檢査它的果是否生成相同的語法樹
**练习 7.13** Expr增加一String方法打印美观的语法树。当再一次解析的候,检查它的果是否生成相同的语法树
**練習 7.14**義一個新的滿足Expr接口的具體類型併且提供一新的操作例如對它運算單元中的最小值的算。因Parse函數不會創建這個新類型的例,了使用它你可能需要直接造一個語法樹(或者承parser接口
**练习 7.14**义一个新的足Expr接口的具体类型并且提供一新的操作例如对它运算单元中的最小值的算。因Parse函数不会创建这个新类型的例,了使用它你可能需要直接造一个语法树(或者承parser接口
**練習 7.15** 編寫一個從標準輸入中取一個單一表式的程序,用戶及時地提供對於任意量的值,然後在結果環境變量中算表式的值。雅的理所有遇到的錯誤
**练习 7.15** 编写一个从标准输入中取一个单一表式的程序,用户及时地提供对于任意量的值,然后在结果环境变量中算表式的值。雅的理所有遇到的错误
**練習 7.16** 編寫一個基於web的算器程序。
**练习 7.16** 编写一个基于web的算器程序。

View File

@@ -1,8 +1,8 @@
## 7.10. 類型斷
## 7.10. 类型断
類型斷言是一使用在接口值上的操作。法上它看起像x.(T)被稱爲斷言類型,里x表示一接口的型和T表示一個類型。一個類型斷言檢査它操作象的動態類型是否和言的型匹配。
类型断言是一使用在接口值上的操作。法上它看起像x.(T)被称为断言类型,里x表示一接口的型和T表示一个类型。一个类型断言检查它操作象的动态类型是否和言的型匹配。
里有兩種可能。第一,如果言的型T是一個具體類型然後類型斷言檢査x的動態類型是否和T相同。如果這個檢査成功了,類型斷言的果是x的動態值,然它的型是T。換句話説,具體類型的類型斷言從它的操作象中得具的值。如果檢査失敗,接下來這個操作會拋出panic。例如
里有两种可能。第一,如果言的型T是一个具体类型然后类型断言检查x的动态类型是否和T相同。如果这个检查成功了,类型断言的果是x的动态值,然它的型是T。换句话说,具体类型的类型断言从它的操作象中得具的值。如果检查失败,接下来这个操作会抛出panic。例如
```go
var w io.Writer
@@ -11,9 +11,9 @@ f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
```
第二,如果相反言的型T是一接口型,然後類型斷言檢査是否x的動態類型滿足T。如果這個檢査成功了動態值沒有獲取到這個結果仍然是一有相同型和值部分的接口值,但是果有型T。換句話説,對一個接口型的類型斷言改變了類型的表述方式,改了可以取的方法集合(通常更大),但是它保了接口值部的動態類型和值的部分。
第二,如果相反言的型T是一接口型,然后类型断言检查是否x的动态类型满足T。如果这个检查成功了动态值没有获取到这个结果仍然是一有相同型和值部分的接口值,但是果有型T。换句话说,对一个接口型的类型断言改变了类型的表述方式,改了可以取的方法集合(通常更大),但是它保了接口值部的动态类型和值的部分。
在下面的第一個類型斷言後w和rw都持有os.Stdout因此它們每個有一個動態類型*os.File但是量w是一io.Writer類型隻對外公出文件的Write方法然而rw量也隻公開它的Read方法。
在下面的第一个类型断言后w和rw都持有os.Stdout因此它们每个有一个动态类型*os.File但是量w是一io.Writer类型只对外公出文件的Write方法然而rw量也只公开它的Read方法。
```go
var w io.Writer
@@ -23,14 +23,14 @@ w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
```
如果言操作的象是一nil接口值麽不論被斷言的型是什麽這個類型斷言都會失敗。我們幾乎不需要對一個更少限性的接口型(更少的方法集合)做言,因它表的就像值操作一,除了對於nil接口值的情
如果言操作的象是一nil接口值么不论被断言的型是什么这个类型断言都会失败。我们几乎不需要对一个更少限性的接口型(更少的方法集合)做言,因它表的就像值操作一,除了对于nil接口值的情
```go
w = rw // io.ReadWriter is assignable to io.Writer
w = rw.(io.Writer) // fails only if rw == nil
```
常地我們對一個接口值的動態類型是不定的,且我們更願意去檢驗它是否是一些特定的型。如果類型斷言出在一個預期有兩個結果的值操作中,例如如下的定義,這個操作不在失敗的時候發生panic但是代替地返迴一個額外的第二個結果,這個結果是一個標識成功的布值:
常地我们对一个接口值的动态类型是不定的,且我们更愿意去检验它是否是一些特定的型。如果类型断言出在一个预期有两个结果的值操作中,例如如下的定义,这个操作不在失败的时候发生panic但是代替地返回一个额外的第二个结果,这个结果是一个标识成功的布值:
```go
var w io.Writer = os.Stdout
@@ -38,9 +38,9 @@ f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
```
第二個結果常規地賦值給一個命名ok的量。如果這個操作失了,那ok就是false值第一個結果等於被斷言類型的零值,在這個例子中就是一nil的*bytes.Buffer型。
第二个结果常规地赋值给一个命名ok的量。如果这个操作失了,那ok就是false值第一个结果等于被断言类型的零值,在这个例子中就是一nil的*bytes.Buffer型。
這個ok結果經常立卽用於決定程序下面做什。if句的展格式讓這個變的很簡潔
这个ok结果经常立即用于决定程序下面做什。if句的展格式让这个变的很简洁
```go
if f, ok := w.(*os.File); ok {
@@ -48,7 +48,7 @@ if f, ok := w.(*os.File); ok {
}
```
當類型斷言的操作象是一個變量,你有時會看見原來的變量名重用而不是明一新的本地量,這個重用的變量會覆蓋原來的值,如下面這樣
当类型断言的操作象是一个变量,你有时会看见原来的变量名重用而不是明一新的本地量,这个重用的变量会覆盖原来的值,如下面这样
```go
if w, ok := w.(*os.File); ok {

View File

@@ -1,6 +1,6 @@
## 7.11. 基於類型斷言區别錯誤類
## 7.11. 基于类型断言区别错误类
思考在os包中文件操作返迴的錯誤集合。I/O可以因任何量的原因失,但是有三種經常的錯誤必須進行不同的理:文件已存在(對於創建操作),找不到文件(對於讀取操作),和限拒。os包中提供了這三個幫助函數來對給定的錯誤值表示的失敗進行分
思考在os包中文件操作返回的错误集合。I/O可以因任何量的原因失,但是有三种经常的错误必须进行不同的理:文件已存在(对于创建操作),找不到文件(对于读取操作),和限拒。os包中提供了这三个帮助函数来对给定的错误值表示的失败进行分
```go
package os
@@ -10,7 +10,7 @@ func IsNotExist(err error) bool
func IsPermission(err error) bool
```
對這些判的一缺乏經驗的實現可能會去檢査錯誤消息是否包含了特定的子字符串,
对这些判的一缺乏经验的实现可能会去检查错误消息是否包含了特定的子字符串,
```go
func IsNotExist(err error) bool {
@@ -19,9 +19,9 @@ func IsNotExist(err error) bool {
}
```
但是理I/O錯誤的邏輯可能一和另一平台非常的不同,所以這種方案不健壯併且對相同的失可能會報出各不同的錯誤消息。在測試的過程中,通過檢査錯誤消息的子字符串來保證特定的函以期望的方式失是非常有用的,但對於線上的代是不的。
但是理I/O错误的逻辑可能一和另一平台非常的不同,所以这种方案不健壮并且对相同的失可能会报出各不同的错误消息。在测试的过程中,通过检查错误消息的子字符串来保证特定的函以期望的方式失是非常有用的,但对于线上的代是不的。
更可靠的方式是使用一個專門的類型來描述結構化的錯誤。os包中定了一PathError類型來描述在文件路操作中涉及到的失像Open或者Delete操作,且定了一叫LinkError的變體來描述涉及到兩個文件路的操作像Symlink和Rename。下面是os.PathError
更可靠的方式是使用一个专门的类型来描述结构化的错误。os包中定了一PathError类型来描述在文件路操作中涉及到的失像Open或者Delete操作,且定了一叫LinkError的变体来描述涉及到两个文件路的操作像Symlink和Rename。下面是os.PathError
```go
package os
@@ -38,7 +38,7 @@ func (e *PathError) Error() string {
}
```
大多數調用方都不知道PathError且通過調用錯誤本身的Error方法來統一處理所有的錯誤。盡管PathError的Error方法簡單地把些字段接起生成錯誤消息PathError的結構保護了內部的錯誤組件。調用方需要使用類型斷言來檢測錯誤的具體類型以便將一種失敗和另一種區分開;具體的類型比字符串可以提供更多的細節
大多数调用方都不知道PathError且通过调用错误本身的Error方法来统一处理所有的错误。尽管PathError的Error方法简单地把些字段接起生成错误消息PathError的结构保护了内部的错误组件。用方需要使用类型断言来检测错误的具体类型以便将一种失败和另一种区分开;具体的类型比字符串可以提供更多的细节
```go
_, err := os.Open("/no/such/file")
@@ -48,7 +48,7 @@ fmt.Printf("%#v\n", err)
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
```
就是三個幫助函是怎工作的。例如下面展示的IsNotExist會報出是否一個錯誤和syscall.ENOENT(§7.8)或者和有名的錯誤os.ErrNotExist相等(可以在§5.4.2中找到io.EOF或者是一*PathError部的錯誤是syscall.ENOENT和os.ErrNotExist其中之一。
就是三个帮助函是怎工作的。例如下面展示的IsNotExist会报出是否一个错误和syscall.ENOENT(§7.8)或者和有名的错误os.ErrNotExist相等(可以在§5.4.2中找到io.EOF或者是一*PathError部的错误是syscall.ENOENT和os.ErrNotExist其中之一。
```go
import (
@@ -69,11 +69,11 @@ func IsNotExist(err error) bool {
}
```
下面里是它的實際使用:
下面里是它的实际使用:
```go
_, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err)) // "true"
```
如果錯誤消息合成一更大的字符串,然PathError的結構就不再人所知,例如通過一個對fmt.Errorf函數的調用。區别錯誤通常必在失操作後,錯誤傳迴調用者前行。
如果错误消息合成一更大的字符串,然PathError的结构就不再人所知,例如通过一个对fmt.Errorf函数的调用。区别错误通常必在失操作后,错误传回调用者前行。

View File

@@ -1,6 +1,6 @@
## 7.12. 通過類型斷言詢問行爲
## 7.12. 通过类型断言询问行为
下面這段邏輯和net/http包中web服務器負責寫入HTTP字段(例如:"Content-type:text/html的部分相似。io.Writer接口型的量w代表HTTP響應;寫入它的字節最終被發送到某人的web瀏覽器上。
下面这段逻辑和net/http包中web服务器负责写入HTTP字段(例如:"Content-type:text/html的部分相似。io.Writer接口型的量w代表HTTP响应;写入它的字节最终被发送到某人的web浏览器上。
```go
func writeHeader(w io.Writer, contentType string) error {
@@ -14,11 +14,11 @@ func writeHeader(w io.Writer, contentType string) error {
}
```
Write方法需要入一byte切片而我希望入的值是一字符串,所以我需要使用[]byte(...)進行轉換。這個轉換分配內存併且做一個拷貝,但是這個拷貝在轉換後幾乎立就被丟棄掉。讓我們假裝這是一web服器的核心部分且我的性能分析表示這個內存分配使服器的速度慢。里我可以避免掉存分配
Write方法需要入一byte切片而我希望入的值是一字符串,所以我需要使用[]byte(...)进行转换。这个转换分配内存并且做一个拷贝,但是这个拷贝在转换后几乎立就被丢弃掉。让我们假装这是一web服器的核心部分且我的性能分析表示这个内存分配使服器的速度慢。里我可以避免掉存分配
這個io.Writer接口告訴我們關於w持有的具體類型的唯一西:就是可以向它入字切片。如果我們迴顧net/http包中的幕,我知道在這個程序中的w量持有的動態類型也有一個允許字符串高效入的WriteString方法這個方法避免去分配一個零時的拷。(可能像在黑夜中射擊一樣,但是許多滿足io.Writer接口的重要型同也有WriteString方法包括\*bytes.Buffer\*os.File和\*bufio.Writer。
这个io.Writer接口告诉我们关于w持有的具体类型的唯一西:就是可以向它入字切片。如果我们回顾net/http包中的幕,我知道在这个程序中的w量持有的动态类型也有一个允许字符串高效入的WriteString方法这个方法避免去分配一个零时的拷。(可能像在黑夜中射击一样,但是许多满足io.Writer接口的重要型同也有WriteString方法包括\*bytes.Buffer\*os.File和\*bufio.Writer。
不能任意io.Writer型的量w它也有WriteString方法。但是我可以定義一個隻有這個方法的新接口且使用類型斷言來檢測是否w的動態類型滿足這個新接口。
不能任意io.Writer型的量w它也有WriteString方法。但是我可以定义一个只有这个方法的新接口且使用类型断言来检测是否w的动态类型满足这个新接口。
```go
// writeString writes s to w.
@@ -44,9 +44,9 @@ func writeHeader(w io.Writer, contentType string) error {
}
```
了避免重複定義,我們將這個檢査移入到一個實用工具函writeString中但是它太有用了以致標準庫將它作io.WriteString函提供。是向一io.Writer接口入字符串的推方法。
了避免重复定义,我们将这个检查移入到一个实用工具函writeString中但是它太有用了以致标准库将它作io.WriteString函提供。是向一io.Writer接口入字符串的推方法。
這個例子的神奇之處在於沒有定了WriteString方法的標準接口和有指定它是一需要行爲的標準接口。而且一個具體類型隻會通過它的方法定它是否滿足stringWriter接口而不是任何它和這個接口型表明的關繫。它的意思就是上面的技術依賴於一個假設;這個假設就是,如果一個類型滿足下面的這個接口,然WriteString(s)就方法必和Write([]byte(s))有相同的效果。
这个例子的神奇之处在于没有定了WriteString方法的标准接口和有指定它是一需要行为的标准接口。而且一个具体类型只会通过它的方法定它是否足stringWriter接口而不是任何它和这个接口型表明的关系。它的意思就是上面的技术依赖于一个假设;这个假设就是,如果一个类型满足下面的这个接口,然WriteString(s)就方法必和Write([]byte(s))有相同的效果。
```go
interface {
@@ -55,11 +55,11 @@ interface {
}
```
管io.WriteString記録了它的假,但是調用它的函數極少有可能會去記録它們也做了同的假。定義一個特定型的方法式地取了特定行爲的協約。對於Go言的新手,特别是那些自有強類型語言使用背景的新手,可能會發現它缺乏式的意令人感到混,但是在實戰的過程中這幾乎不是一個問題。除了空接口interface{},接口型很少意外巧合地被實現
管io.WriteString记录了它的假,但是用它的函数极少有可能会去记录它们也做了同的假。定义一个特定型的方法式地取了特定行为的协约。对于Go言的新手,特别是那些自有强类型语言使用背景的新手,可能会发现它缺乏式的意令人感到混,但是在实战的过程中这几乎不是一个问题。除了空接口interface{},接口型很少意外巧合地被实现
上面的writeString函使用一個類型斷言來知道一普遍接口型的值是否滿足一更加具的接口型;且如果滿足,它使用這個更具接口的行爲。這個技術可以被很好的使用不論這個被詢問的接口是一個標準的如io.ReadWriter或者用戶定義的如stringWriter。
上面的writeString函使用一个类型断言来知道一普遍接口型的值是否足一更加具的接口型;且如果足,它使用这个更具接口的行为。这个技术可以被很好的使用不论这个被询问的接口是一个标准的如io.ReadWriter或者用户定义的如stringWriter。
也是fmt.Fprintf函數怎麽從其它所有值中區分滿足error或者fmt.Stringer接口的值。在fmt.Fprintf部,有一個將單個操作對象轉換成一字符串的步,像下面這樣
也是fmt.Fprintf函数怎么从其它所有值中区分满足error或者fmt.Stringer接口的值。在fmt.Fprintf部,有一个将单个操作对象转换成一字符串的步,像下面这样
```go
package fmt
@@ -75,6 +75,6 @@ func formatOneValue(x interface{}) string {
}
```
如果x滿足這個兩個接口型中的一,具體滿足的接口決定對值的格式化方式。如果都不滿足,默的case或多或少會統一地使用反射來處理所有的其它型;我可以在第12章知道具是怎麽實現的。
如果x满足这个两个接口型中的一,具体满足的接口决定对值的格式化方式。如果都不足,默的case或多或少会统一地使用反射来处理所有的其它型;我可以在第12章知道具是怎么实现的。
再一次的,它假任何有String方法的類型滿足fmt.Stringer中定的行爲,這個行爲會返迴一個適合打印的字符串。
再一次的,它假任何有String方法的类型满足fmt.Stringer中定的行为,这个行为会返回一个适合打印的字符串。

View File

@@ -1,12 +1,12 @@
## 7.13. 類型開關
## 7.13. 类型开关
接口被以兩種不同的方式使用。在第一方式中以io.Readerio.Writerfmt.Stringersort.Interfacehttp.Handler和error典型,一接口的方法表達了實現這個接口的具體類型間的相思性,但是藏了代表的細節和這些具體類型本身的操作。重點在於方法上,而不是具體的類型上。
接口被以两种不同的方式使用。在第一方式中以io.Readerio.Writerfmt.Stringersort.Interfacehttp.Handler和error典型,一接口的方法表达了实现这个接口的具体类型间的相思性,但是藏了代表的细节和这些具体类型本身的操作。重点在于方法上,而不是具体的类型上。
第二方式利用一接口值可以持有各種具體類型值的能力併且將這個接口認爲是這些類型的union合)。類型斷言用來動態地區别這些類型併且對每一種情況都不一。在這個方式中,重點在於具體的類型滿足這個接口,而不是在接口的方法(如果它確實有一些的併且沒有任何的信息藏。我們將以這種方式使用的接口描述discriminated unions可辨識聯合)。
第二方式利用一接口值可以持有各种具体类型值的能力并且将这个接口认为是这些类型的union合)。类型断言用来动态地区别这些类型并且对每一种情况都不一。在这个方式中,重点在于具体的类型满足这个接口,而不是在接口的方法(如果它确实有一些的并且没有任何的信息藏。我们将以这种方式使用的接口描述discriminated unions可辨识联合)。
如果你熟悉面向對象編程,你可能會將這兩種方式作是subtype polymorphism型多)和 ad hoc polymorphism參數多態),但是你不需要去記住這些術語。對於本章剩下的部分,我們將會呈現一些第二方式的例子。
如果你熟悉面向对象编程,你可能会将这两种方式作是subtype polymorphism型多)和 ad hoc polymorphism参数多态),但是你不需要去记住这些术语。对于本章剩下的部分,我们将会呈现一些第二方式的例子。
和其它那些言一Go語言査詢一個SQL數據庫的API會榦淨地將査詢中固定的部分和化的部分分。一個調用的例子可能看起來像這樣
和其它那些言一Go语言查询一个SQL数据库的API会干净地将查询中固定的部分和化的部分分。一个调用的例子可能看起来像这样
```go
import "database/sql"
@@ -19,7 +19,7 @@ func listTracks(db sql.DB, artist string, minYear, maxYear int) {
}
```
Exec方法使用SQL字面量替換在査詢字符串中的每'?'SQL字面量表示相應參數的值,它有可能是一個布爾值,一個數字,一字符串或者nil空值。用這種方式構造査詢可以助避免SQL入攻擊;這種攻擊就是手可以通利用輸入內容中不正的引文來控製査詢語句。在Exec函數內部,我可能找到像下面這樣的一個函數,它會將每一個參數值轉換成它的SQL字面量符
Exec方法使用SQL字面量替换在查询字符串中的每'?'SQL字面量表示相应参数的值,它有可能是一个布尔值,一个数字,一字符串或者nil空值。用这种方式构造查询可以助避免SQL入攻击;这种攻击就是手可以通利用输入内容中不正的引文来控制查询语句。在Exec函数内部,我可能找到像下面这样的一个函数,它会将每一个参数值转换成它的SQL字面量符
```go
func sqlQuote(x interface{}) string {
@@ -42,9 +42,9 @@ func sqlQuote(x interface{}) string {
}
```
switch句可以化if-else,如果這個if-else鏈對一連串值做相等測試。一相似的type switch類型開關)可以簡化類型斷言的if-else
switch句可以化if-else,如果这个if-else链对一连串值做相等测试。一相似的type switch类型开关)可以简化类型断言的if-else
在它最簡單的形式中,一個類型開關像普通的switch句一,它的運算對象是x.(type)-它使用了關鍵詞字面量type且每case有一到多個類型。一個類型開關基於這個接口值的動態類型使一多路分支有效。這個nil的case和if x == nil匹配併且這個default的case和如果其它case都不匹配的情匹配。一個對sqlQuote的類型開關可能會有這些case
在它最简单的形式中,一个类型开关像普通的switch句一,它的运算对象是x.(type)-它使用了关键词字面量type且每case有一到多个类型。一个类型开关基于这个接口值的动态类型使一多路分支有效。这个nil的case和if x == nil匹配并且这个default的case和如果其它case都不匹配的情匹配。一个对sqlQuote的类型开关可能会有这些case
```go
switch x.(type) {
@@ -56,17 +56,17 @@ switch x.(type) {
}
```
和(§1.8)中的普通switch句一,每一case會被順序的行考併且當一個匹配找到時這個case中的內容會被執行。當一個或多case型是接口case的序就會變得很重要,因可能會有兩個case同匹配的情。default case相其它case的位置是無所謂的。它不會允許落空生。
和(§1.8)中的普通switch句一,每一case会被顺序的行考并且当一个匹配找到时这个case中的内容会被执行。当一个或多case型是接口case的序就会变得很重要,因可能会有两个case同匹配的情。default case相其它case的位置是无所谓的。它不会允许落空生。
意到在原的函中,對於bool和string情況的邏輯需要通過類型斷言訪問提取的值。因爲這個做法很典型,類型開關語句有一個擴展的形式,它可以提取的值定到一在每case范圍內的新量。
意到在原的函中,对于bool和string情况的逻辑需要通过类型断言访问提取的值。因为这个做法很典型,类型开关语句有一个扩展的形式,它可以提取的值定到一在每case范围内的新量。
```go
switch x := x.(type) { /* ... */ }
```
里我們已經將新的量也命名x類型斷言一,重用量名是很常的。和一switch句相似地,一個類型開關隱式的建了一個語言塊,因此新量x的定義不會和外面中的x變量衝突。每一case也會隱式的建一個單獨的語言塊
里我们已经将新的量也命名x类型断言一,重用量名是很常的。和一switch句相似地,一个类型开关隐式的建了一个语言块,因此新量x的定义不会和外面中的x变量冲突。每一case也会隐式的建一个单独的语言块
使用類型開關的擴展形式來重寫sqlQuote函數會讓這個函數更加的清晰:
使用类型开关的扩展形式来重写sqlQuote函数会让这个函数更加的清晰:
```go
func sqlQuote(x interface{}) string {
@@ -88,6 +88,6 @@ func sqlQuote(x interface{}) string {
}
```
這個版本的函中,在每個單一類型的case部,量x和這個case的型相同。例如,量x在bool的case中是bool型和string的case中是string型。在所有其它的情中,量x是switch運算對象的型(接口);在這個例子中運算對象是一interface{}。當多個case需要相同的操作比如int和uint的情況,類型開關可以很容易的合併這些情
这个版本的函中,在每个单一类型的case部,量x和这个case的型相同。例如,量x在bool的case中是bool型和string的case中是string型。在所有其它的情中,量x是switch运算对象的型(接口);在这个例子中运算对象是一interface{}。当多个case需要相同的操作比如int和uint的情况,类型开关可以很容易的合并这些情
管sqlQuote接受一任意型的參數,但是這個函數隻會在它的參數匹配類型開關中的一case時運行到束;其它情的它panic出“unexpected type”消息。然x的型是interface{},但是我把它認爲是一intuintboolstring和nil值的discriminated union識别聯合)
管sqlQuote接受一任意型的参数,但是这个函数只会在它的参数匹配类型开关中的一case时运行到束;其它情的它panic出“unexpected type”消息。然x的型是interface{},但是我把它认为是一intuintboolstring和nil值的discriminated union识别联合)

View File

@@ -1,8 +1,8 @@
## 7.14. 示例: 基於標記的XML解
## 7.14. 示例: 基于标记的XML解
第4.5章展示了如何使用encoding/json包中的Marshal和Unmarshal函數來將JSON文檔轉換成Go言的數據結構。encoding/xml包提供了一相似的API。當我們想構造一個文檔樹的表示使用encoding/xml包很方便,但是對於很多程序不是必的。encoding/xml包也提供了一更低的基於標記的API用XML解。在基於標記的樣式中,解析器消費輸入和生一個標記流;四主要的標記類StartElementEndElementCharData和Comment每一都是encoding/xml包中的具體類型。每一個對(\*xml.Decoder).Token的調用都返迴一個標記
第4.5章展示了如何使用encoding/json包中的Marshal和Unmarshal函数来将JSON文档转换成Go言的数据结构。encoding/xml包提供了一相似的API。当我们想构造一个文档树的表示使用encoding/xml包很方便,但是对于很多程序不是必的。encoding/xml包也提供了一更低的基于标记的API用XML解。在基于标记的样式中,解析器消费输入和生一个标记流;四主要的标记类StartElementEndElementCharData和Comment每一都是encoding/xml包中的具体类型。每一个对(\*xml.Decoder).Token的用都返回一个标记
這里顯示的是和這個API相的部分:
这里显示的是和这个API相的部分:
<u><i>encoding/xml</i></u>
```go
@@ -33,9 +33,9 @@ func NewDecoder(io.Reader) *Decoder
func (*Decoder) Token() (Token, error) // returns next Token in sequence
```
這個沒有方法的Token接口也是一個可識别聯合的例子。傳統的接口如io.Reader的目的是隱藏滿足它的具體類型的細節,這樣就可以造出新的實現;在這個實現中每個具體類型都被一地待。相反,滿足可識别聯合的具體類型的集合被設計確定和暴露,而不是藏。可别的聯合類型幾乎沒有方法;操作它的函使用一個類型開關的case集合來進行表述;這個case集合中每一case中有不同的邏輯
这个没有方法的Token接口也是一个可识别联合的例子。传统的接口如io.Reader的目的是隐藏满足它的具体类型的细节,这样就可以造出新的实现;在这个实现中每个具体类型都被一地待。相反,足可识别联合的具体类型的集合被设计确定和暴露,而不是藏。可别的联合类型几乎没有方法;操作它的函使用一个类型开关的case集合来进行表述;这个case集合中每一case中有不同的逻辑
下面的xmlselect程序取和打印在一XML文檔樹中確定的元素下找到的文本。使用上面的API它可以在入上一次完成它的工作而從來不要具體化這個文檔樹
下面的xmlselect程序取和打印在一XML文档树中确定的元素下找到的文本。使用上面的API它可以在入上一次完成它的工作而从来不要具体化这个文档树
<u><i>gopl.io/ch7/xmlselect</i></u>
```go
@@ -89,9 +89,9 @@ func containsAll(x, y []string) bool {
}
```
每次main函中的循遇到一StartElement,它把這個元素的名稱壓到一個棧里;且每次遇到EndElement,它將名稱從這個棧中推出。這個API保了StartElement和EndElement的序列可以被完全的匹配甚至在一糟糕的文格式中。註釋會被忽略。xmlselect遇到一CharData時,隻有當棧中有序地包含所有通命令行參數傳入的元素名稱時它才會輸出相的文本。
每次main函中的循遇到一StartElement,它把这个元素的名称压到一个栈里;且每次遇到EndElement,它将名称从这个栈中推出。这个API保了StartElement和EndElement的序列可以被完全的匹配甚至在一糟糕的文格式中。注释会被忽略。xmlselect遇到一CharData时,只有当栈中有序地包含所有通命令行参数传入的元素名称时它才会输出相的文本。
下面的命令打印出任意出現在兩層div元素下的h2元素的文本。它的入是XML的明文檔,併且它自己就是XML文格式的。
下面的命令打印出任意出现在两层div元素下的h2元素的文本。它的入是XML的明文档,并且它自己就是XML文格式的。
```
$ go build gopl.io/ch1/fetch
@@ -108,11 +108,11 @@ html body div div h2: B Definitions for Character Normalization
...
```
**練習 7.17** 展xmlselect程序以便元素不僅僅可以通過名稱選擇,也可以通過它們CSS式上屬性進行選擇;例如一個像這樣<div id="page" class="wide">的元素可以通匹配id或者class同時還有它的名稱來進行選擇
**练习 7.17** 展xmlselect程序以便元素不仅仅可以通过名称选择,也可以通过它们CSS式上属性进行选择;例如一个像这样<div id="page" class="wide">的元素可以通匹配id或者class同时还有它的名称来进行选择
**練習 7.18** 使用基於標記的解API編寫一個可以取任意XML文檔和構造這個文檔所代表的普通節點樹的程序。節點有兩種類CharData節點表示文本字符串,和 Element節點表示被命名的元素和它們的屬性。每一元素節點有一個字節點的切片。
**练习 7.18** 使用基于标记的解API编写一个可以取任意XML文档和构造这个文档所代表的普通节点树的程序。节点有两种类CharData节点表示文本字符串,和 Element节点表示被命名的元素和它们的属性。每一元素节点有一个字节点的切片。
你可能發現下面的定義會對你有助。
你可能发现下面的定义会对你有助。
```go
import "encoding/xml"

View File

@@ -1,9 +1,9 @@
## 7.15. 一些建
## 7.15. 一些建
當設計一個新的包新的Go程序員總是通過創建一接口的集合始和面定義滿足它的具體類型。這種方式的果就是有很多的接口,它中的每一個僅隻有一個實現。不要再這麽做了。這種接口是不必要的抽象;它也有一個運行時損耗。你可以使用導出機製(§6.6)來限製一個類型的方法或一個結構體的字段是否在包外可。接口隻有當有兩個或兩個以上的具體類型必以相同的方式進行處理時才需要。
当设计一个新的包新的Go程序员总是通过创建一接口的集合始和面定义满足它的具体类型。这种方式的果就是有很多的接口,它中的每一个仅只有一个实现。不要再这么做了。这种接口是不必要的抽象;它也有一个运行时损耗。你可以使用导出机制(§6.6)来限制一个类型的方法或一个结构体的字段是否在包外可。接口只有当有两个或两个以上的具体类型必以相同的方式进行处理时才需要。
當一個接口被一個單一的具體類型實現時有一例外,就是由它的依賴,這個具體類型不能和這個接口存在在一相同的包中。這種情況下,一接口是解耦這兩個包的一好好方式。
当一个接口被一个单一的具体类型实现时有一例外,就是由它的依赖,这个具体类型不能和这个接口存在在一相同的包中。这种情况下,一接口是解耦这两个包的一好好方式。
在Go言中隻有當兩個或更多的類型實現一個接口才使用接口,它必定會從任意特定的實現細節中抽象出來。結果就是有更少和更簡單方法(常和io.Writer或 fmt.Stringer一樣隻有一)的更小的接口。新的型出現時,小的接口更容易滿足。對於接口設計的一好的標準就是 ask only for what you need隻考慮你需要的西)
在Go言中只有当两个或更多的类型实现一个接口才使用接口,它必定会从任意特定的实现细节中抽象出来。结果就是有更少和更简单方法(常和io.Writer或 fmt.Stringer一样只有一)的更小的接口。新的型出现时,小的接口更容易足。对于接口设计的一好的标准就是 ask only for what you need只考虑你需要的西)
完成了methods和接口的學習過程。Go言良好的支持面向對象風格的程,但不是説你僅僅隻能使用它。不是任何事物都需要被做成一個對象;立的函有它自己的用,未封裝的數據類型也是這樣。同時觀察到這兩個,在本的前五章的例子中沒有調用超過兩打方法像input.Scan之相反的是普遍的函數調用如fmt.Printf。
完成了methods和接口的学习过程。Go言良好的支持面向对象风格的程,但不是说你仅仅只能使用它。不是任何事物都需要被做成一个对象;立的函有它自己的用,未封装的数据类型也是这样。同时观察到这两个,在本的前五章的例子中没有调用超过两打方法像input.Scan之相反的是普遍的函数调用如fmt.Printf。

View File

@@ -1,7 +1,7 @@
# 第七章 接口
接口型是其它型行的抽象和概括;因接口型不和特定的實現細節綁定在一起,通過這種抽象的方式我可以讓我們的函更加活和更具有適應能力。
接口型是其它型行的抽象和概括;因接口型不和特定的实现细节绑定在一起,通过这种抽象的方式我可以让我们的函更加活和更具有适应能力。
很多面向象的言都有相似的接口概念但Go言中接口型的特之處在於它是滿足隱式實現的。也就是,我們沒有必要對於給定的具體類型定所有滿足的接口型;簡單地擁有一些必需的方法就足了。這種設計可以讓你創建一新的接口類型滿足已存在的具體類型卻不會去改變這些類型的定義;當我們使用的類型來自於不受我們控製的包時這種設計尤其有用。
很多面向象的言都有相似的接口概念但Go言中接口型的特之处在于它是满足隐式实现的。也就是,我们没有必要对于给定的具体类型定所有足的接口型;简单地拥有一些必需的方法就足了。这种设计可以让你创建一新的接口类型满足已存在的具体类型却不会去改变这些类型的定义;当我们使用的类型来自于不受我们控制的包时这种设计尤其有用。
在本章,我們會開始看到接口型和值的一些基本技巧。順着這種方式我們將學習幾個來自標準庫的重要接口。很多Go程序中都可能多的去使用標準庫中的接口。最,我們會在(§7.10)看到類型斷言的知,在(§7.13)看到類型開關的使用併且學到他是怎樣讓不同的型的概括成可能。
在本章,我们会开始看到接口型和值的一些基本技巧。顺着这种方式我们将学习几个来自标准库的重要接口。很多Go程序中都可能多的去使用标准库中的接口。最,我们会在(§7.10)看到类型断言的知,在(§7.13)看到类型开关的使用并且学到他是怎样让不同的型的概括成可能。