Kaynağa Gözat

Merge pull request #120 from leisore/master

第11章 1-9节翻译完毕
无闻 10 yıl önce
ebeveyn
işleme
532b330d03
13 değiştirilmiş dosya ile 1117 ekleme ve 4 silme
  1. 1 1
      eBook/10.8.md
  2. 9 0
      eBook/11.0.md
  3. 256 1
      eBook/11.1.md
  4. 29 0
      eBook/11.2.md
  5. 98 0
      eBook/11.3.md
  6. 78 0
      eBook/11.4.md
  7. 31 0
      eBook/11.5.md
  8. 93 0
      eBook/11.6.md
  9. 228 0
      eBook/11.7.md
  10. 29 0
      eBook/11.8.md
  11. 255 0
      eBook/11.9.md
  12. 10 2
      eBook/directory.md
  13. BIN
      eBook/images/11.1_fig11.1.jpg

+ 1 - 1
eBook/10.8.md

@@ -30,4 +30,4 @@ runtime.SetFinalizer(obj, func(obj *typeObj))
 
 - [目录](directory.md)
 - 上一节:[类型的 String() 方法和格式化描述符](10.7.md)
-- 下一章:[什么是接口?](11.1.md)
+- 下一章:[11 接口(Interfaces)与反射(reflection)](11.0.md)

+ 9 - 0
eBook/11.0.md

@@ -0,0 +1,9 @@
+# 11 接口(Interfaces)与反射(reflection)
+
+本章介绍 Go 语言中接口和反射的相关内容。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[垃圾回收和SetFinalizer](10.8.md)
+- 下一节:[接口是什么](11.1.md)

+ 256 - 1
eBook/11.1.md

@@ -1 +1,256 @@
-# 11.1 浠€涔堟槸鎺ュ彛锛�
+# 11.1 接口是什么
+
+Go 语言不是一种 *“传统”* 的面向对象编程语言:它里面没有类和继承的概念。
+
+但是 Go 语言里有非常灵活的 **接口** 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 **说明** 对象的行为:如果谁能搞定这件事,它就可以用在这儿。
+
+接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
+
+通过如下格式定义接口:
+
+```go
+type Namer interface {
+    Method1(param_list) return_type
+    Method2(param_list) return_type
+    ...
+}
+```
+
+上面的 `Namer` 是一个 **接口类型**。
+
+(按照约定,只包含一个方法的)接口的名字由方法名加 `[e]r` 后缀组成,例如 `Printer`、`Reader`、`Writer`、`Logger`、`Converter`等等。还有一些不常用的方式(当后缀`er`不合适时),比如`Recoverable`,此时接口名以`able`结尾,或者以 `I` 开头(像 `.NET` 或 `Java` 中那样)。
+
+Go 语言中的接口都很简短,通常它们会包含0个、最多3个方法。
+
+不像大多数面向对象编程语言,在 Go 语言中接口可以有值,一个接口类型的变量或一个 **接口值** :`var ai Namer`,`ai`是一个多字(multiword)数据结构,它的值是 `nil`。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。
+
+![](images/11.1_fig11.1.jpg?raw=true)
+
+此处的方法指针表是通过运行时反射能力构建的。
+
+类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:**即实现接口**,同时方法集也构成了该类型的接口。实现了 `Namer` 接口类型的变量可以赋值给 `ai` (接收者值),此时方法表中的指针会指向被实现的接口方法。当然如果另一个类型(也实现了该接口)的变量被赋值给`ai`,这二者(译者注:指针和方法实现)也会随之改变。
+
+**类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口**。
+
+**实现某个接口的类型(除了实现接口方法外)可以有其他的方法**。
+
+**一个类型可以实现多个接口**。
+
+**接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)**。
+
+即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。
+
+所有这些特性使得接口具有很大的灵活性。
+
+第一个例子:
+
+示例 11.1 interfaces.go:
+
+```go
+package main
+
+import "fmt"
+
+type Shaper interface {
+	Area() float32
+}
+
+type Square struct {
+	side float32
+}
+
+func (sq *Square) Area() float32 {
+	return sq.side * sq.side
+}
+
+func main() {
+	sq1 := new(Square)
+	sq1.side = 5
+
+	// var areaIntf Shaper
+	// areaIntf = sq1
+	// shorter,without separate declaration:
+	// areaIntf := Shaper(sq1)
+	// or even:
+	areaIntf := sq1
+	fmt.Printf("The square has area: %f\n", areaIntf.Area())
+}
+```
+
+输出:
+
+    The square has area: 25.000000
+
+上面的程序定义了一个结构体 `Square` 和一个接口 `Shaper`,接口有一个方法 `Area()`。
+
+在 `main()` 方法中创建了一个 `Square` 的实例。在主程序外边定义了一个接收者类型是 `Square`方法的 `Area()`,用来计算正方形的面积:结构体 `Square` 实现了接口 `Shaper` 。
+
+所以可以将一个 `Square` 类型的变量赋值给一个接口类型的变量:`areaIntf = sq1` 。
+
+现在接口变量包含一个指向 `Square` 变量的引用,通过它可以调用 `Square` 上的方法 `Area()`。当然也可以直接在 `Square` 的实例上调用此方法,但是在接口实例上调用此方法更令人兴奋,它使此方法更具有一般性。接口变量里包含了接收者实例的值和指向对应方法表的指针。
+
+这是 **多态** 的 Go 版本,多态是面向对象编程中一个广为人知的概念:根据当前的类型选择正确的方法,或者说:同一种类型在不同的实例上似乎表现出不同的行为。
+
+如果 `Square` 没有实现 `Area()` 方法,编译器将会给出清晰的错误信息:
+
+    cannot use sq1 (type *Square) as type Shaper in assignment:
+    *Square does not implement Shaper (missing Area method)
+
+如果 `Shaper` 有另外一个方法 `Perimeter()`,但是`Square` 没有实现它,即使没有人在 `Square` 实例上调用这个方法,编译器也会给出上面同样的错误。
+
+扩展一下上面的例子,类型 `Rectangle` 也实现了 `Shaper` 接口。接着创建一个 `Shaper` 类型的数组,迭代它的每一个元素并在上面调用 `Area()` 方法,以此来展示多态行为:
+
+示例 11.2 interfaces_poly.go:
+
+```go
+package main
+
+import "fmt"
+
+type Shaper interface {
+	Area() float32
+}
+
+type Square struct {
+	side float32
+}
+
+func (sq *Square) Area() float32 {
+	return sq.side * sq.side
+}
+
+type Rectangle struct {
+	length, width float32
+}
+
+func (r Rectangle) Area() float32 {
+	return r.length * r.width
+}
+
+func main() {
+
+	r := Rectangle{5, 3} // Area() of Rectangle needs a value
+	q := &Square{5}      // Area() of Square needs a pointer
+	// shapes := []Shaper{Shaper(r), Shaper(q)}
+	// or shorter
+	shapes := []Shaper{r, q}
+	fmt.Println("Looping through shapes for area ...")
+	for n, _ := range shapes {
+		fmt.Println("Shape details: ", shapes[n])
+		fmt.Println("Area of this shape is: ", shapes[n].Area())
+	}
+}
+```
+
+输出:
+
+    Looping through shapes for area ...
+    Shape details:  {5 3}
+    Area of this shape is:  15
+    Shape details:  &{5}
+    Area of this shape is:  25
+
+在调用 `shapes[n].Area()) ` 这个时,只知道 `shapes[n]` 是一个 `Shaper` 对象,最后它摇身一变成为了一个 `Square` 或 `Rectangle` 对象,并且表现出了相对应的行为。
+
+也许从现在开始你将看到通过接口如何产生 **更干净**、**更简单** 及 **更具有扩展性** 的代码。在 11.12.3 中将看到在开发中为类型添加新的接口是多么的容易。
+
+下面是一个更具体的例子:有两个类型 `stockPosition` 和 `car`,它们都有一个 `getValue()` 方法,我们可以定义一个具有此方法的接口 `valuable`。接着定义一个使用 `valuable`类型作为参数的函数 `showValue()`,所有实现了 `valuable` 接口的类型都可以用这个函数。
+
+示例 11.3 valuable.go:
+
+```go
+package main
+
+import "fmt"
+
+type stockPosition struct {
+	ticker     string
+	sharePrice float32
+	count      float32
+}
+
+/* method to determine the value of a stock position */
+func (s stockPosition) getValue() float32 {
+	return s.sharePrice * s.count
+}
+
+type car struct {
+	make  string
+	model string
+	price float32
+}
+
+/* method to determine the value of a car */
+func (c car) getValue() float32 {
+	return c.price
+}
+
+/* contract that defines different things that have value */
+type valuable interface {
+	getValue() float32
+}
+
+func showValue(asset valuable) {
+	fmt.Printf("Value of the asset is %f\n", asset.getValue())
+}
+
+func main() {
+	var o valuable = stockPosition{"GOOG", 577.20, 4}
+	showValue(o)
+	o = car{"BMW", "M3", 66500}
+	showValue(o)
+}
+```
+
+输出:
+
+    Value of the asset is 2308.800049
+    Value of the asset is 66500.000000
+
+**一个标准库的例子**
+
+`io`包里有一个接口类型 `Reader`:
+
+```go
+type Reader interface {
+    Read(p []byte) (n int, err error)
+}
+```
+
+定义变量 `r`:` var r io.Reader`
+
+那么就可以写如下的代码:
+
+```go
+	var r io.Reader
+	r = os.Stdin    // see 12.1
+	r = bufio.NewReader(r)
+	r = new(bytes.Buffer)
+	f,_ := os.Open("test.txt")
+	r = bufio.NewReader(f)
+```
+
+上面 `r` 右边的类型都实现了 `Read()` 方法,并且有相同的方法签名,`r` 的静态类型是 `io.Reader`。
+
+**备注**
+
+有的时候,也会以一种稍微不同的方式来使用接口这个词:从某个类型的角度来看,它的接口指的是:它的所有导出方法,只不过没有显式地为这些导出方法额外定一个接口而已。
+
+**练习 11.1** simple_interface.go:
+
+定义一个接口 `Simpler`,它有一个 `Get()` 方法和一个 `Set()`,`Get()`返回一个整型值,`Set()` 有一个整型参数。创建一个结构体类型 `Simple` 实现这个接口。
+
+接着定一个函数,它有一个 `Simpler` 类型的参数,调用参数的 `Get()` 和 `Set()` 方法。在 `main` 函数里调用这个函数,看看它是否可以正确运行。
+
+**练习 11.2** interfaces_poly2.go:
+
+a) 扩展 interfaces_poly.go 中的例子,添加一个 `Circle` 类型
+
+b) 使用一个抽象类型 `Shape`(没有字段) 实现同样的功能,它实现接口 `Shaper`,然后在其他类型里内嵌此类型。扩展 10.6.5 中的例子来说明覆写。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[接口(Interfaces)与反射(reflection)](11.0.md)
+- 下一节:[接口嵌套接口](11.2.md)
+

+ 29 - 0
eBook/11.2.md

@@ -0,0 +1,29 @@
+# 11.2 接口嵌套接口
+
+一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
+
+比如接口 `File` 包含了 `ReadWrite` 和 `Lock` 的所有方法,它还额外有一个 `Close()` 方法。
+
+```go
+type ReadWrite interface {
+    Read(b Buffer) bool
+    Write(b Buffer) bool
+}
+
+type Lock interface {
+    Lock()
+    Unlock()
+}
+
+type File interface {
+    ReadWrite
+    Lock
+    Close()
+}
+```
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[接口是什么](11.1.md)
+- 下一节:[如何检测和转换接口变量的类型:类型断言](11.3.md)

+ 98 - 0
eBook/11.3.md

@@ -0,0 +1,98 @@
+# 11.3 类型断言:如何检测和转换接口变量的类型
+
+一个接口类型的变量 `varI` 中可以包含任何类型的值,必须有一种方式来检测它的 **动态** 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 **类型断言** 来测试在某个时刻 `varI` 是否包含类型 `T` 的值:
+
+```go
+    v := varI.(T)       // unchecked type assertion
+```
+
+**varI必须是一个接口变量**,否则编译器会报错:`invalid type assertion: varI.(T) (non-interface type (type of varI) on left)` 。
+
+类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:
+
+```go
+if v, ok := varI.(T); ok {  // checked type assertion
+    Process(v)
+    return
+}
+// varI is not of type T
+```
+
+如果转换合法,`v` 是 `varI` 转换到类型 `T`的值,`ok` 会是 `true`;否则 `v` 是类型 `T` 的零值,`ok` 是 `false`,也没有运行时错误发生。
+
+**应该总是使用上面的方式来进行类型断言**。
+
+多数情况下,我们可能只是想在 `if` 中测试一下 `ok` 的值,此时使用以下的方法会是最方便的:
+
+```go
+if _, ok := varI.(T); ok {
+    // ...
+}
+```
+
+TODO ??:In this form shadowing the variable varI by giving varI and v the same name is sometimes done.
+
+示例 11.4 type_interfaces.go
+
+```go
+package main
+
+import (
+	"fmt"
+	"math"
+)
+
+type Square struct {
+	side float32
+}
+
+type Circle struct {
+	radius float32
+}
+
+type Shaper interface {
+	Area() float32
+}
+
+func main() {
+	var areaIntf Shaper
+	sq1 := new(Square)
+	sq1.side = 5
+
+	areaIntf = sq1
+	// Is Square the type of areaIntf?
+	if t, ok := areaIntf.(*Square); ok {
+		fmt.Printf("The type of areaIntf is: %T\n", t)
+	}
+	if u, ok := areaIntf.(*Circle); ok {
+		fmt.Printf("The type of areaIntf is: %T\n", u)
+	} else {
+		fmt.Println("areaIntf does not contain a variable of type Circle")
+	}
+}
+
+func (sq *Square) Area() float32 {
+	return sq.side * sq.side
+}
+
+func (ci *Circle) Area() float32 {
+	return ci.radius * ci.radius * math.Pi
+}
+```
+
+输出:
+
+    The type of areaIntf is: *main.Square
+    areaIntf does not contain a variable of type Circle
+
+程序行中定义了一个新类型 `Circle`,它也实现了 `Shaper` 接口。 `t, ok := areaIntf.(*Square); ok ` 测试 `areaIntf` 里是否一个包含 'Square' 类型的变量,结果是确定的;然后我们测试它是否包含一个 'Circle' 类型的变量,结果是否定的。
+
+**备注**
+
+如果忽略 `areaIntf.(*Square)` 中的 `*` 号,会导致编译错误:`impossible type assertion: Square does not implement Shaper (Area method has pointer receiver)`。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[接口嵌套接口](11.2.md)
+- 下一节:[类型判断:type-switch](11.4.md)

+ 78 - 0
eBook/11.4.md

@@ -0,0 +1,78 @@
+# 11.4 类型判断:type-switch
+
+接口变量的类型也可以使用一种特殊形式的 `swtich` 来检测:**type-swtich** (下面是 示例 11.4 的第二部分):
+
+```go
+	switch t := areaIntf.(type) {
+	case *Square:
+		fmt.Printf("Type Square %T with value %v\n", t, t)
+	case *Circle:
+		fmt.Printf("Type Circle %T with value %v\n", t, t)
+	case nil:
+		fmt.Printf("nil value: nothing to check?\n")
+	default:
+		fmt.Printf("Unexpected type %T\n", t)
+	}
+```
+
+输出:
+
+    Type Square *main.Square with value &{5}
+
+变量 `t` 得到了 `areaIntf` 的值和类型, 所有 `case` 语句中列举的类型(`nil` 除外)都必须实现对应的接口(在上例中即 `Shaper`),如果被检测类型没有在 `case` 语句列举的类型中,就会执行`default` 语句。
+
+可以用 `type-switch` 进行运行时类型分析,但是在 `type-switch` 不允许有 `fallthrough` 。
+
+如果仅仅是测试变量的类型,不用它的值,那么就可以不需要赋值语句,比如:
+
+```go
+	switch areaIntf.(type) {
+	case *Square:
+		// TODO
+	case *Circle:
+		// TODO
+	...
+	default:
+		// TODO
+	}
+```
+
+下面的代码片段展示了一个类型分类函数,它有一个可变长度参数,可以是任意类型的数组,它会根据数组元素的实际类型执行不同的动作:
+
+```go
+
+func classifier(items ...interface{}) {
+	for i, x := range items {
+		switch x.(type) {
+		case bool:
+			fmt.Printf("Param #%d is a bool\n", i)
+		case float64:
+			fmt.Printf("Param #%d is a float64\n", i)
+		case int, int64:
+			fmt.Printf("Param #%d is a int\n", i)
+		case nil:
+			fmt.Printf("Param #%d is a nil\n", i)
+		case string:
+			fmt.Printf("Param #%d is a string\n", i)
+		default:
+			fmt.Printf("Param #%d is unknown\n", i)
+		}
+	}
+}
+```
+
+可以这样调用此方法:`classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false)` 。
+
+在处理来自于外部的、类型未知的数据时,比如解析诸如 JSON 或 XML 编码的数据,类型测试和转换会非常有用。
+
+在 示例12.17(xml.go) 中解析 XML 文档是,我们就会用到 `type-switch` 。
+
+**练习 11.4** simple_interface2.go:
+
+接着 练习11.1 中的内容,创建第二个类型 `RSimple`,它也实现了接口 `Simpler`,写一个函数 `fi`,它可以区分 `Simple` 和 `RSimple` 类型的变量。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[类型断言:如何检测和转换接口变量的类型](11.3.md)
+- 下一节:[测试一个值是否实现了某个接口](11.5.md)

+ 31 - 0
eBook/11.5.md

@@ -0,0 +1,31 @@
+# 11.5 测试一个值是否实现了某个接口
+
+这是 11.3 类型断言中的一个特例:假定 `v` 是一个值,然后我们想测试它是否实现了 `Stringer` 接口,可以这样做:
+
+```go
+type Stringer interface {
+    String() string
+}
+
+if sv, ok := v.(Stringer); ok {
+    fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
+}
+```
+
+`Print` 函数就是如此检测类型是否可以打印自身的。
+
+接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。
+
+编写参数是接口变量的函数,这使得它们更具有一般性。
+
+**使用接口使代码更具有普适性。**
+
+标准库里到处都使用了这个原则,如果对接口概念没有良好的把握,是不可能理解它是如何构建的。
+
+在接下来的章节中,我们会讨论两个重要的例子,试着去深入理解它们,这样你就可以更好的应用上面的原则。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[类型判断:type-switch](11.4.md)
+- 下一节:[使用方法集与接口](11.6.md)

+ 93 - 0
eBook/11.6.md

@@ -0,0 +1,93 @@
+# 11.6 使用方法集与接口
+
+在 10.6.3 及例子 methodset1.go 中我们看到,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序:
+
+示例 11.5 methodset2.go:
+
+```go
+package main
+
+import (
+	"fmt"
+)
+
+type List []int
+
+func (l List) Len() int {
+	return len(l)
+}
+
+func (l *List) Append(val int) {
+	*l = append(*l, val)
+}
+
+type Appender interface {
+	Append(int)
+}
+
+func CountInto(a Appender, start, end int) {
+	for i := start; i <= end; i++ {
+		a.Append(i)
+	}
+}
+
+type Lener interface {
+	Len() int
+}
+
+func LongEnough(l Lener) bool {
+	return l.Len()*10 > 42
+}
+
+func main() {
+	// A bare value
+	var lst List
+	// compiler error:
+	// cannot use lst (type List) as type Appender in argument to CountInto:
+	//       List does not implement Appender (Append method has pointer receiver)
+	// CountInto(lst, 1, 10)
+	if LongEnough(lst) { // VALID:Identical receiver type
+		fmt.Printf("- lst is long enough\n")
+	}
+
+	// A pointer value
+	plst := new(List)
+	CountInto(plst, 1, 10) //VALID:Identical receiver type
+	if LongEnough(plst) {
+		// VALID: a *List can be dereferenced for the receiver
+		fmt.Printf("- plst is long enough\n")
+	}
+}
+```
+
+**讨论**
+
+在 `lst` 上调用 `CountInto` 时会导致一个编译器错误,因为 `CountInto` 需要一个 `Appender`,而它的方法 `Append` 只定义在指针上。 在 `lst` 上调用 `LongEnough` 是可以的因为 'Len' 定义在值上。
+
+在 `plst` 上调用 `CountInto` 是可以的,因为 `CountInto` 需要一个 `Appender`,并且它的方法 `Append` 定义在指针上。 在 `plst` 上调用 `LongEnough` 也是可以的,因为指针会被自动解引用。
+
+**总结**
+
+在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以从具体类型 `P` 直接可以辨识的:
+
+- 指针方法可以通过指针调用
+- 值方法可以通过值调用
+- 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
+- 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
+
+将一个值赋值给一个接口赋值时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
+
+
+**译注**
+
+Go语言规范定义了接口方法集的调用规则:
+
+- 类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
+- 类型 T 的可调用方法集包含接受者为 T 的所有方法
+- 类型 T 的可调用方法集不包含接受者为 *T 的方法
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[测试一个值是否实现了某个接口](11.5.md)
+- 下一节:[第一个例子:使用Sorter接口排序](11.7.md)

+ 228 - 0
eBook/11.7.md

@@ -0,0 +1,228 @@
+# 11.7 第一个例子:使用Sorter接口排序
+
+一个很好的例子是来自标准库的 `sort` 包,要对一组数字或字符串排序,只需要实现三个方法:反映元素个数的 `Len()`方法、比较第 `i` 和 `j` 个元素的 `Less(i, j)` 方法以及交换第 `i` 和 `j` 个元素的 `Swap(i, j)` 方法。
+
+
+排序函数的算法只会使用到这三个方法(可以使用任何排序算法来实现,此处我们使用冒泡排序):
+
+```go
+func Sort(data Sorter) {
+    for pass := 1; pass < data.Len(); pass++ {
+        for i := 0;i < data.Len() - pass; i++ {
+            if data.Less(i+1, i) {
+                data.Swap(i, i + 1)
+            }
+        }
+    }
+}
+```
+
+`Sort` 函数接收一个接口类型参数: `Sorter` ,它声明了这些方法:
+
+```go
+type Sorter interface {
+    Len() int
+    Less(i, j int) bool
+    Swap(i, j int)
+}
+```
+
+参数中的 `int` 不是说要排序的对象一定要是一组 `int`,`i` 和 `j` 表示元素的整型索引,长度也是整型的。
+
+现在如果我们想对一个 `int` 数组进行排序,所有必须做的事情就是:为数组定一个类型并在它上面实现 `Sorter` 接口的方法:
+
+```go
+type IntArray []int
+func (p IntArray) Len() int           { return len(p) }
+func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
+func (p IntArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+```
+
+下面是调用排序函数的一个具体例子:
+
+```go
+data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
+a := sort.IntArray(data) //conversion to type IntArray from package sort
+sort.Sort(a)
+```
+
+完整的、可运行的代码可以在 `sort.go` 和 `sortmain.go` 里找到。
+
+同样的原理,排序函数可以用于一个浮点型数组,一个字符串数组,或者一个表示每周各天的结构体 `dayArray`.
+
+示例 11.6 sort.go:
+
+```go
+package sort
+
+type Sorter interface {
+	Len() int
+	Less(i, j int) bool
+	Swap(i, j int)
+}
+
+func Sort(data Sorter) {
+	for pass := 1; pass < data.Len(); pass++ {
+		for i := 0; i < data.Len()-pass; i++ {
+			if data.Less(i+1, i) {
+				data.Swap(i, i+1)
+			}
+		}
+	}
+}
+
+func IsSorted(data Sorter) bool {
+	n := data.Len()
+	for i := n - 1; i > 0; i-- {
+		if data.Less(i, i-1) {
+			return false
+		}
+	}
+	return true
+}
+
+// Convenience types for common cases
+type IntArray []int
+
+func (p IntArray) Len() int           { return len(p) }
+func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
+func (p IntArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+type StringArray []string
+
+func (p StringArray) Len() int           { return len(p) }
+func (p StringArray) Less(i, j int) bool { return p[i] < p[j] }
+func (p StringArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+// Convenience wrappers for common cases
+func SortInts(a []int)       { Sort(IntArray(a)) }
+func SortStrings(a []string) { Sort(StringArray(a)) }
+
+func IntsAreSorted(a []int) bool       { return IsSorted(IntArray(a)) }
+func StringsAreSorted(a []string) bool { return IsSorted(StringArray(a)) }
+```
+
+示例 11.7 sortmain.go:
+
+```go
+package main
+
+import (
+	"./sort"
+	"fmt"
+)
+
+func ints() {
+	data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
+	a := sort.IntArray(data) //conversion to type IntArray
+	sort.Sort(a)
+	if !sort.IsSorted(a) {
+		panic("fails")
+	}
+	fmt.Printf("The sorted array is: %v\n", a)
+}
+
+func strings() {
+	data := []string{"monday", "friday", "tuesday", "wednesday", "sunday", "thursday", "", "saturday"}
+	a := sort.StringArray(data)
+	sort.Sort(a)
+	if !sort.IsSorted(a) {
+		panic("fail")
+	}
+	fmt.Printf("The sorted array is: %v\n", a)
+}
+
+type day struct {
+	num       int
+	shortName string
+	longName  string
+}
+
+type dayArray struct {
+	data []*day
+}
+
+func (p *dayArray) Len() int           { return len(p.data) }
+func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num }
+func (p *dayArray) Swap(i, j int)      { p.data[i], p.data[j] = p.data[j], p.data[i] }
+
+func days() {
+	Sunday    := day{0, "SUN", "Sunday"}
+	Monday    := day{1, "MON", "Monday"}
+	Tuesday   := day{2, "TUE", "Tuesday"}
+	Wednesday := day{3, "WED", "Wednesday"}
+	Thursday  := day{4, "THU", "Thursday"}
+	Friday    := day{5, "FRI", "Friday"}
+	Saturday  := day{6, "SAT", "Saturday"}
+	data := []*day{&Tuesday, &Thursday, &Wednesday, &Sunday, &Monday, &Friday, &Saturday}
+	a := dayArray{data}
+	sort.Sort(&a)
+	if !sort.IsSorted(&a) {
+		panic("fail")
+	}
+	for _, d := range data {
+		fmt.Printf("%s ", d.longName)
+	}
+	fmt.Printf("\n")
+}
+
+func main() {
+	ints()
+	strings()
+	days()
+}
+```
+
+输出:
+
+    The sorted array is: [-5467984 -784 0 0 42 59 74 238 905 959 7586 7586 9845]
+    The sorted array is: [ friday monday saturday sunday thursday tuesday wednesday]
+    Sunday Monday Tuesday Wednesday Thursday Friday Saturday 
+
+**备注**:
+
+`panic("fail")` 用于停止处于在非正常情况下的程序(详细请参考 第13章),当然也可以先打印一条信息,然后调用 `os.Exit(1)` 来停止程序。
+
+上面的例子帮助我们进一步了解了接口的意义和使用方式。对于基本类型的排序,标准库已经提供了相关的排序函数,所以不需要我们再重复造轮子了。对于一般性的排序,`sort` 包定义了一个接口:
+
+```go
+type Interface interface {
+	Len() int
+	Less(i, j int) bool
+	Swap(i, j int)
+}
+```
+
+这个接口总结了需要用于排序的抽象方法,函数 `Sort(data Interface)` 用来对此类对象进行排序,可以用它们来实现对其他数据(非基本类型)进行排序。在上面的例子中,我们也是这么做的,不仅可以对 `int` 和 `string` 序列进行排序,也可以对用户自定义类型 `dayArray` 进行排序。
+
+**练习 11.5** interfaces_ext.go:
+
+a). 继续扩展程序,定义类型 `Triangle`,让它实现 `AreaInterface` 接口。通过计算一个特定三角形的面积来进行测试(三角形面积=0.5 * (底 * 高))
+
+b). 定义一个新接口 `PeriInterface`,它有一个 `Perimeter` 接口。让 `Square` 实现这个接口,并通过一个 `Square` 示例来测试它。
+
+**练习 11.6** point_interfaces.go:
+
+继续 10.3 中的练习 point_methods.go,定义接口 `Magnitude`,它有一个方法 `Abs()`。让 `Point`、`Point3` 及`Polar` 实现此接口。通过接口类型变量使用方法做point.go中同样的事情。
+
+**练习 11.7** float_sort.go / float_sortmain.go:
+
+类似11.7和示例11.3/4,定义一个包 `float64`,并在包里定义类型 `Float64Array`,然后让它实现 `Sorter` 接口用来对 `float64` 数组进行排序。
+
+另外提供如下方法:
+
+- `NewFloat64Array()`:创建一个包含25个元素的数组变量(参考10.2)
+- `List()`:返回数组格式化后的字符串,并在 `String()` 方法中调用它,这样就不用显式地调用 `List()` 来打印数组(参考10.7)
+- `Fill()`:创建一个包含10个随机浮点数的数组(参考4.5.2.6)
+
+在主程序中新建一个此类型的变量,然后对它排序并进行测试。
+
+**练习 11.8** sort.go/sort_persons.go:
+
+定义一个结构体 `Person`,它有两个字段:`firstName` 和 `lastName`,为 `[]Person` 定义类型 `Persons` 。让 `Persons` 实现 `Sorter` 接口并进行测试。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[使用方法集与接口](11.6.md)
+- 下一节:[第二个例子:读和写](11.8.md)

+ 29 - 0
eBook/11.8.md

@@ -0,0 +1,29 @@
+# 11.8 第二个例子:读和写
+
+读和写是软件中很普遍的行为,提起它们会立即想到读写文件、缓存(比如字节或字符串切片)、标准输入输出、标准错误以及网络连接、管道等等,或者读写我们的自定义类型。为了是代码尽可能通用,Go 采取了一致的方式来读写数据。
+
+`io` 包提供了用于读和写的接口:`io.Reader` 和 `io.Writer`
+
+```go
+type Reader interface {
+    Read(p []byte) (n int, err error)
+}
+
+type Writer interface {
+    Write(p []byte) (n int, err error)
+}
+```
+
+只要类型实现了读写接口,提供 `Read()` 和 `Write` 方法,就可以从它读取数据,或向它写入数据。一个对象要是可读的,它必须实现 `io.Reader` 接口,这个接口只有一个签名是 `Read(p []byte) (n int, err error)` 的方法,它从调用它的对象上读取数据,并把读到的数据放入参数中的字节切片中,然后返回读取的字节数和一个 `error` 对象,如果没有错误发生返回 'nil',如果已经到达输入的尾端,会返回 `io.EOF("EOF")`,如果读取的过程中发生了错误,就会返回具体的错误信息。类似地,一个对象要是可写的,它必须实现 `io.Writer` 接口,这个接口也只有一个签名是 `Write(p []byte) (n int, err error)` 的方法,它将指定字节切片中的数据写入调用它的对象里,然后返回实际写入的字节数一个 `error` 对象(如果没有错误发生就是 `nil`)。
+
+`io` 包里的 `Readers` 和 `Writers` 都是不带缓冲的,`bufio` 包里提供了对应的带缓冲的操作,在读写 `UTF-8` 编码的文本文件时它们尤其有用。在 第12章 我们会看在实战使用它们的很多例子。
+
+在实际编程中尽可能的使用这些接口,会使程序变得更通用,可以在任何实现了这些接口的类型上使用读写方法。
+
+例如一个 `JPEG` 图形解码器,通过一个 `Reader` 参数,它可以解码来自磁盘、网络连接或以 `gzip` 压缩的 `HTTP` 流中的 `JPEG`图形数据,或者其他任何实现了`Reader` 接口的对象。 
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[第一个例子:使用Sorter接口排序](11.7.md)
+- 下一节:[空接口](11.9.md)

+ 255 - 0
eBook/11.9.md

@@ -0,0 +1,255 @@
+# 11.9 空接口
+
+## 11.9.1 概念
+
+**空接口或者最小接口**不包含任何方法,它对实现不做任何要求:
+
+```go
+type Any interface {}
+```
+
+任何其他类型都实现了空接口(它不仅仅像 `Java/C#` 中 `Object` 引用类型),`any` 或 `Any` 是空接口一个很好的别名或缩写。
+
+空接口类似 `Java/C#` 中所有类的基类: `Object` 类,二者的目标也很相近。
+
+可以给一个空接口类型的变量 `var val interface {}` 赋任何类型的值。
+
+示例 11.8 empty_interface.go:
+
+```go
+package main
+import "fmt"
+
+var i = 5
+var str = "ABC"
+
+type Person struct {
+	name string
+	age  int
+}
+
+type Any interface{}
+
+func main() {
+	var val Any
+	val = 5
+	fmt.Printf("val has the value: %v\n", val)
+	val = str
+	fmt.Printf("val has the value: %v\n", val)
+	pers1 := new(Person)
+	pers1.name = "Rob Pike"
+	pers1.age = 55
+	val = pers1
+	fmt.Printf("val has the value: %v\n", val)
+	switch t := val.(type) {
+	case int:
+		fmt.Printf("Type int %T\n", t)
+	case string:
+		fmt.Printf("Type string %T\n", t)
+	case bool:
+		fmt.Printf("Type boolean %T\n", t)
+	case *Person:
+		fmt.Printf("Type pointer to Person %T\n", t)
+	default:
+		fmt.Printf("Unexpected type %T", t)
+	}
+}
+```
+
+输出:
+
+    val has the value: 5
+    val has the value: ABC
+    val has the value: &{Rob Pike 55}
+    Type pointer to Person *main.Person
+
+在上面的例子中,接口变量 `val` 被依次赋予一个 `int`,`string` 和 `Person` 实例的值,然后使用 `type-swtich` 来测试它的实际类型。每个 `interface {}` 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。
+
+例子 emptyint_switch.go 说明了空接口在 `type-swtich` 中联合 `lambda` 函数的用法:
+
+```go
+package main
+
+import "fmt"
+
+type specialString string
+
+var whatIsThis specialString = "hello"
+
+func TypeSwitch() {
+	testFunc := func(any interface{}) {
+		switch v := any.(type) {
+		case bool:
+			fmt.Printf("any %v is a bool type", v)
+		case int:
+			fmt.Printf("any %v is an int type", v)
+		case float32:
+			fmt.Printf("any %v is a float32 type", v)
+		case string:
+			fmt.Printf("any %v is a string type", v)
+		case specialString:
+			fmt.Printf("any %v is a special String!", v)
+		default:
+			fmt.Println("unknown type!")
+		}
+	}
+	testFunc(whatIsThis)
+}
+
+func main() {
+	TypeSwitch()
+}
+```
+
+输出:
+
+    any hello is a special String!
+
+**练习 11.9** simple_interface3.go:
+
+继续 练习11.2,在它中添加一个 `gI` 函数,它不再接受 `Simpler` 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 `Simpler` 类型。最后在 `main` 使用 `gI` 取代 `fI` 函数并调用它。确保你的代码足够安全。
+
+## 11.9.2 构建通用类型或包含不同类型变量的数组
+
+在 7.6.6 中我们看到了能被搜索和排序的 `int` 数组、`float` 数组以及 `string` 数组,那么对于其他类型的数组呢,是不是我们必须得自己编程实现它们?
+
+现在我们知道该怎么做了,就是通过使用空接口。让我们给空接口定一个别名类型 `Element`:`type Element interface{}`
+
+然后定义一个容器类型的结构体 `Vector`,它包含一个 `Element` 类型元素的切片:
+
+```go
+type Vector struct {
+	a []Element
+}
+```
+
+`Vector` 里能放任何类型的变量,因为任何类型都实现了空接口,实际上 `Vector` 里放的每个元素可以是不同类型的变量。我们为它定义一个 `At()` 方法用于返回第 `i` 个元素:
+
+```go
+func (p *Vector) At(i int) Element {
+	return p.a[i]
+}
+```
+
+再定一个 `Set()` 方法用于设置第 `i` 个元素的值:
+
+```go
+func (p *Vector) Set(i int, e Element) {
+	p.a[i] = e
+}
+```
+
+`Vector` 中存储的所有元素都是 `Element` 类型,要得到它们的原始类型(unboxing:拆箱)需要用到类型断言。TODO:The compiler rejects assertions guaranteed to fail,类型断言总是在运行时才执行,因此它会产生运行时错误。
+
+**练习 11.10** min_interface.go / minmain.go:
+
+仿照11.7中开发的 `Sorter` 接口,创建一个 `Miner` 接口并实现一些必要的操作。函数 `Min` 接受一个 `Miner` 类型变量的集合,然后计算并返回集合中最小的元素。
+
+## 11.9.3 复制数据切片至空接口切片
+
+假设你有一个 `myType` 类型的数据切片,你想将切片中的数据复制到一个空接口切片中,类似:
+
+```go
+var dataSlice []myType = FuncReturnSlice()
+var interfaceSlice []interface{} = dataSlice
+```
+
+可惜不能这么做,编译时会出错:`cannot use dataSlice (type []myType) as type []interface { } in assignment`
+
+原因是它们俩在内存中的布局是不一样的(参考[http://golang.org/doc/go_spec.html](http://golang.org/doc/go_spec.html))。
+
+必须使用 `for-range` 语句来一个一个显式地复制:
+
+```go
+var dataSlice []myType = FuncReturnSlice()
+var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
+for ix, d := range dataSlice {
+    interfaceSlice[i] = d
+}
+```
+
+## 11.9.4 通用类型的节点数据结构
+
+在10.1中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 `NewNode` 方法,及设置数据的 `SetData` 方法.
+
+示例 11.10 node_structures.go:
+
+```go
+package main
+
+import "fmt"
+
+type Node struct {
+	le   *Node
+	data interface{}
+	ri   *Node
+}
+
+func NewNode(left, right *Node) *Node {
+	return &Node{left, nil, right}
+}
+
+func (n *Node) SetData(data interface{}) {
+	n.data = data
+}
+
+func main() {
+	root := NewNode(nil, nil)
+	root.SetData("root node")
+	// make child (leaf) nodes:
+	a := NewNode(nil, nil)
+	a.SetData("left node")
+	b := NewNode(nil, nil)
+	b.SetData("right node")
+	root.le = a
+	root.ri = b
+	fmt.Printf("%v\n", root) // Output: &{0x125275f0 root node 0x125275e0}
+}
+```
+
+## 11.9.5 接口到接口
+
+一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 'Go' 语言动态的一面,可以那它和 `Ruby` 和 `Python` 这些动态语言相比较。
+
+假定:
+
+```go
+var ai AbsInterface // declares method Abs()
+type SqrInterface interface {
+    Sqr() float
+}
+var si SqrInterface
+pp := new(Point) // say *Point implements Abs, Sqr
+var empty interface{}
+```
+
+那么下面的语句和类型断言是合法的:
+
+```go
+empty = pp                // everything satisfies empty
+ai = empty.(AbsInterface) // underlying value pp implements Abs()
+// (runtime failure otherwise)
+si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesn’t
+empty = si             // *Point implements empty set
+// Note: statically checkable so type assertion not necessary.
+```
+
+下面是函数调用的一个例子:
+
+```go
+type myPrintInterface interface {
+	print()
+}
+
+func f3(x myInterface) {
+	x.(myPrintInterface).print() // type assertion to myPrintInterface
+}
+```
+
+`x` 转换为 `myPrintInterface` 类型是完全动态的:只要 `x` 的底层类型(动态类型)定义了 `print` 方法这个调用就可以正常运行。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[第二个例子:读和写](11.8.md)
+- 下一节:[反射包](11.10.md)

+ 10 - 2
eBook/directory.md

@@ -93,8 +93,16 @@
     - 10.6 [方法](10.6.md)
     - 10.7 [类型的 String() 方法和格式化描述符](10.7.md)
     - 10.8 [垃圾回收和 SetFinalizer](10.8.md)
-- 第11章:接口(interface)与反射(reflection)
-	- 11.1 [什么是接口?](11.1.md)
+- 第11章:[接口(interface)与反射(reflection)](11.0.md)
+    - 11.1 [接口是什么](11.1.md)
+    - 11.2 [接口嵌套接口](11.2.md)
+    - 11.3 [类型断言:如何检测和转换接口变量的类型](11.3.md)
+    - 11.4 [类型判断:type-switch](11.4.md)
+    - 11.5 [测试一个值是否实现了某个接口](11.5.md)
+    - 11.6 [使用方法集与接口](11.6.md)
+    - 11.7 [第一个例子:使用Sorter接口排序](11.7.md)
+    - 11.8 [第二个例子:读和写](11.8.md)
+    - 11.9 [空接口](11.9.md)
 
 ## 第三部分:Go 高级编程
 

BIN
eBook/images/11.1_fig11.1.jpg