From 806880974dd83efb70e49bae17cd7f14e7858f3a Mon Sep 17 00:00:00 2001 From: SiHuan <49349784+Si-Huan@users.noreply.github.com> Date: Tue, 24 Nov 2020 01:46:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E6=AD=A3=E9=94=99=E5=88=AB=E5=AD=97?= =?UTF-8?q?=E5=8F=8A=E5=BE=AE=E8=B0=83=E6=A0=BC=E5=BC=8F=20(#782)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/02.5.md | 2 +- eBook/02.6.md | 2 +- eBook/03.0.md | 2 +- eBook/03.3.md | 2 +- eBook/06.0.md | 4 +- eBook/06.11.md | 2 +- eBook/10.0.md | 2 +- eBook/11.0.md | 18 +- eBook/11.1.md | 512 ++++++++++++++++++++++----------------------- eBook/11.13.md | 2 +- eBook/11.2.md | 58 ++--- eBook/11.3.md | 192 ++++++++--------- eBook/11.4.md | 154 +++++++------- eBook/11.5.md | 62 +++--- eBook/11.6.md | 184 ++++++++-------- eBook/11.7.md | 454 ++++++++++++++++++++-------------------- eBook/11.8.md | 58 ++--- eBook/12.0.md | 2 +- eBook/13.0.md | 2 +- eBook/14.0.md | 2 +- eBook/15.0.md | 2 +- eBook/15.2.md | 2 +- eBook/15.3.md | 2 +- eBook/16.0.md | 2 +- eBook/16.1.md | 2 +- eBook/16.3.md | 4 +- eBook/16.4.md | 4 +- eBook/16.7.md | 2 +- eBook/16.8.md | 2 +- eBook/16.9.md | 2 +- eBook/17.0.md | 2 +- eBook/18.0.md | 4 +- eBook/18.10.md | 2 +- eBook/18.2.md | 2 +- eBook/18.3.md | 2 +- eBook/18.4.md | 2 +- eBook/18.5.md | 2 +- eBook/18.6.md | 2 +- eBook/18.7.md | 2 +- eBook/18.8.md | 2 +- eBook/18.9.md | 2 +- eBook/19.0.md | 2 +- eBook/directory.md | 22 +- 43 files changed, 894 insertions(+), 894 deletions(-) diff --git a/eBook/02.5.md b/eBook/02.5.md index 2b475d5..3ad8814 100644 --- a/eBook/02.5.md +++ b/eBook/02.5.md @@ -20,4 +20,4 @@ - [目录](directory.md) - 上一节:[在 Mac OS X 上安装 Go](02.4.md) -- 下一节:[安装目录清单](02.6.md) \ No newline at end of file +- 下一节:[安装目录清单](02.6.md) diff --git a/eBook/02.6.md b/eBook/02.6.md index 1daa0f1..14f7a85 100644 --- a/eBook/02.6.md +++ b/eBook/02.6.md @@ -16,4 +16,4 @@ README.md, AUTHORS, CONTRIBUTORS, LICENSE - [目录](directory.md) - 上一节:[在 Windows 上安装 Go](02.5.md) -- 下一节:[Go 运行时(runtime)](02.7.md) \ No newline at end of file +- 下一节:[Go 运行时(runtime)](02.7.md) diff --git a/eBook/03.0.md b/eBook/03.0.md index ac078b6..50786f1 100644 --- a/eBook/03.0.md +++ b/eBook/03.0.md @@ -8,4 +8,4 @@ - [目录](directory.md) - 上一章:[Go 解释器](02.8.md) -- 下一节:[Go 开发环境的基本要求](03.1.md) \ No newline at end of file +- 下一节:[Go 开发环境的基本要求](03.1.md) diff --git a/eBook/03.3.md b/eBook/03.3.md index 3e1895e..034ee4f 100644 --- a/eBook/03.3.md +++ b/eBook/03.3.md @@ -18,4 +18,4 @@ - [目录](directory.md) - 上一节:[编辑器和集成开发环境](03.2.md) -- 下一节:[构建并运行 Go 程序](03.4.md) \ No newline at end of file +- 下一节:[构建并运行 Go 程序](03.4.md) diff --git a/eBook/06.0.md b/eBook/06.0.md index 295f735..57457ef 100644 --- a/eBook/06.0.md +++ b/eBook/06.0.md @@ -1,4 +1,4 @@ -# 6.0 函数 +# 6.0 函数(function) 函数是 Go 里面的基本代码块:Go 函数的功能非常强大,以至于被认为拥有函数式编程语言的多种特性。在这一章,我们将对 [第 4.2.2 节](04.2.md) 所简要描述的函数进行详细的讲解。 @@ -6,4 +6,4 @@ - [目录](directory.md) - 上一章:[标签与 goto](05.6.md) -- 下一节:[介绍](06.1.md) \ No newline at end of file +- 下一节:[介绍](06.1.md) diff --git a/eBook/06.11.md b/eBook/06.11.md index 403a45e..d5ef306 100644 --- a/eBook/06.11.md +++ b/eBook/06.11.md @@ -18,4 +18,4 @@ fmt.Printf("longCalculation took this amount of time: %s\n", delta) - [目录](directory.md) - 上一节:[使用闭包调试](06.10.md) -- 下一节:[通过内存缓存来提升性能](06.12.md) \ No newline at end of file +- 下一节:[通过内存缓存来提升性能](06.12.md) diff --git a/eBook/10.0.md b/eBook/10.0.md index c676d37..74eeb2a 100644 --- a/eBook/10.0.md +++ b/eBook/10.0.md @@ -1,4 +1,4 @@ -# 10 结构(struct)与方法(method) +# 10.0 结构(struct)与方法(method) Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型(composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。然后可以访问这些数据,就好像它是一个独立实体的一部分。结构体也是值类型,因此可以通过 **new** 函数来创建。 diff --git a/eBook/11.0.md b/eBook/11.0.md index e374fb1..09e2b63 100644 --- a/eBook/11.0.md +++ b/eBook/11.0.md @@ -1,9 +1,9 @@ -# 11 接口(Interfaces)与反射(reflection) - -本章介绍 Go 语言中接口和反射的相关内容。 - -## 链接 - -- [目录](directory.md) -- 上一章:[垃圾回收和 SetFinalizer](10.8.md) -- 下一节:[接口是什么](11.1.md) +# 11.0 接口(interface)与反射(reflection) + +本章介绍 Go 语言中接口和反射的相关内容。 + +## 链接 + +- [目录](directory.md) +- 上一章:[垃圾回收和 SetFinalizer](10.8.md) +- 下一节:[接口是什么](11.1.md) diff --git a/eBook/11.1.md b/eBook/11.1.md index d0074bc..ba6f705 100644 --- a/eBook/11.1.md +++ b/eBook/11.1.md @@ -1,256 +1,256 @@ -# 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](examples/chapter_11/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](examples/chapter_11/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](examples/chapter_11/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) - +# 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](examples/chapter_11/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](examples/chapter_11/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](examples/chapter_11/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) + diff --git a/eBook/11.13.md b/eBook/11.13.md index c9774bd..de08ed9 100644 --- a/eBook/11.13.md +++ b/eBook/11.13.md @@ -1,4 +1,4 @@ -# 总结:Go 中的面向对象 +# 11.13 总结:Go 中的面向对象 我们总结一下前面看到的:Go 没有类,而是松耦合的类型、方法对接口的实现。 diff --git a/eBook/11.2.md b/eBook/11.2.md index 85784c8..342d472 100644 --- a/eBook/11.2.md +++ b/eBook/11.2.md @@ -1,29 +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) \ No newline at end of file +# 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) diff --git a/eBook/11.3.md b/eBook/11.3.md index 137412d..3b9e567 100644 --- a/eBook/11.3.md +++ b/eBook/11.3.md @@ -1,96 +1,96 @@ -# 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 { - // ... -} -``` - -示例 11.4 [type_interfaces.go](examples/chapter_11/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` 接口。 `if 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) +# 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 { + // ... +} +``` + +示例 11.4 [type_interfaces.go](examples/chapter_11/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` 接口。 `if 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) diff --git a/eBook/11.4.md b/eBook/11.4.md index 8b446bd..3b5faf1 100644 --- a/eBook/11.4.md +++ b/eBook/11.4.md @@ -1,77 +1,77 @@ -# 11.4 类型判断:type-switch - -接口变量的类型也可以使用一种特殊形式的 `switch` 来检测:**type-switch** (下面是示例 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) +# 11.4 类型判断:type-switch + +接口变量的类型也可以使用一种特殊形式的 `switch` 来检测:**type-switch** (下面是示例 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) diff --git a/eBook/11.5.md b/eBook/11.5.md index 6bb0b78..ab68c3b 100644 --- a/eBook/11.5.md +++ b/eBook/11.5.md @@ -1,31 +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) \ No newline at end of file +# 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) diff --git a/eBook/11.6.md b/eBook/11.6.md index 9338f25..48d669a 100644 --- a/eBook/11.6.md +++ b/eBook/11.6.md @@ -1,92 +1,92 @@ -# 11.6 使用方法集与接口 - -在第 10.6.3 节及例子 methodset1.go 中我们看到,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序: - -示例 11.5 [methodset2.go](examples/chapter_11/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) +# 11.6 使用方法集与接口 + +在第 10.6.3 节及例子 methodset1.go 中我们看到,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序: + +示例 11.5 [methodset2.go](examples/chapter_11/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) diff --git a/eBook/11.7.md b/eBook/11.7.md index 4dd84d5..ecba9f0 100644 --- a/eBook/11.7.md +++ b/eBook/11.7.md @@ -1,227 +1,227 @@ -# 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](examples/chapter_11/sort/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](examples/chapter_11/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) +# 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](examples/chapter_11/sort/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](examples/chapter_11/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) diff --git a/eBook/11.8.md b/eBook/11.8.md index 92ff0b5..a6d71b1 100644 --- a/eBook/11.8.md +++ b/eBook/11.8.md @@ -1,29 +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) +# 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) diff --git a/eBook/12.0.md b/eBook/12.0.md index b5defd2..7e569fd 100644 --- a/eBook/12.0.md +++ b/eBook/12.0.md @@ -1,4 +1,4 @@ -# 12 读写数据 +# 12.0 读写数据 除了 fmt 和 os 包,我们还需要用到 bufio 包来处理缓冲的输入和输出。 diff --git a/eBook/13.0.md b/eBook/13.0.md index ad84690..1538d25 100644 --- a/eBook/13.0.md +++ b/eBook/13.0.md @@ -1,4 +1,4 @@ -# 13 错误处理与测试 +# 13.0 错误处理与测试 Go 没有像 Java 和 .NET 那样的 `try/catch` 异常机制:不能执行抛异常操作。但是有一套 `defer-panic-and-recover` 机制(参见 13.2-13.3 节)。 diff --git a/eBook/14.0.md b/eBook/14.0.md index ba0d41f..cb4ff95 100644 --- a/eBook/14.0.md +++ b/eBook/14.0.md @@ -1,4 +1,4 @@ -# 14 协程(goroutine)与通道(channel) +# 14.0 协程(goroutine)与通道(channel) 作为一门 21 世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见第 15 章)和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言,编译器,和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。 diff --git a/eBook/15.0.md b/eBook/15.0.md index ab3eff9..b2abba8 100644 --- a/eBook/15.0.md +++ b/eBook/15.0.md @@ -1,4 +1,4 @@ -# 15.0 网络,模板和网页应用 +# 15.0 网络、模板与网页应用 Go 在编写 web 应用方面非常得力。因为目前它还没有GUI(Graphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的 html 页面是目前 Go 编写界面应用程序的唯一方式。(**译者注:实际上在翻译的时候,已经有了一些不太成熟的GUI库例如:go ui。**) diff --git a/eBook/15.2.md b/eBook/15.2.md index 5a18b90..075a76d 100644 --- a/eBook/15.2.md +++ b/eBook/15.2.md @@ -1,4 +1,4 @@ -# 15.2 一个简单的网页服务器 +# 15.2 一个简单的 web 服务器 http 是比 tcp 更高层的协议,它描述了网页服务器如何与客户端浏览器进行通信。Go 提供了 `net/http` 包,我们马上就来看下。先从一些简单的示例开始,首先编写一个“Hello world!”网页服务器:[查看示例15.6](examples/chapter_15/hello_world_webserver.go) diff --git a/eBook/15.3.md b/eBook/15.3.md index d5142d5..9dfeb4c 100644 --- a/eBook/15.3.md +++ b/eBook/15.3.md @@ -1,4 +1,4 @@ -# 15.3 访问并读取页面 +# 15.3 访问并读取页面数据 在下边这个程序中,数组中的 url 都将被访问:会发送一个简单的 `http.Head()` 请求查看返回值;它的声明如下:`func Head(url string) (r *Response, err error)` diff --git a/eBook/16.0.md b/eBook/16.0.md index 4813b79..b2960a0 100644 --- a/eBook/16.0.md +++ b/eBook/16.0.md @@ -1,4 +1,4 @@ -# 16 常见的陷阱与错误 +# 16.0 常见的陷阱与错误 在之前的内容中,有时候使用`!!...!!`标记警告go语言中的一些错误使用方式。当你在编程时候遇到的一个困难,可以确定本书特定的章节能找到类似的主题。为了方便起见,这里列出了一些常见陷阱,以便于你能发现更多的解释和例子: diff --git a/eBook/16.1.md b/eBook/16.1.md index d9c3bde..f2e0ed5 100644 --- a/eBook/16.1.md +++ b/eBook/16.1.md @@ -38,4 +38,4 @@ func shadow() (err error) { - [目录](directory.md) - 上一节:[常见的陷阱与错误](16.0.md) -- 下一节:[误用字符串](16.2.md) \ No newline at end of file +- 下一节:[误用字符串](16.2.md) diff --git a/eBook/16.3.md b/eBook/16.3.md index 073f961..ce8e0b4 100644 --- a/eBook/16.3.md +++ b/eBook/16.3.md @@ -1,4 +1,4 @@ -# 16.3 发生错误时使用defer关闭一个文件 +# 16.3 发生错误时使用 defer 关闭一个文件 如果你在一个for循环内部处理一系列文件,你需要使用defer确保文件在处理完毕后被关闭,例如: @@ -34,4 +34,4 @@ for _, file := range files { - [目录](directory.md) - 上一节:[误用字符串](16.2.md) -- 下一节:[何时使用new()和make()](16.4.md) \ No newline at end of file +- 下一节:[何时使用new()和make()](16.4.md) diff --git a/eBook/16.4.md b/eBook/16.4.md index 223f333..656cb89 100644 --- a/eBook/16.4.md +++ b/eBook/16.4.md @@ -1,4 +1,4 @@ -# 16.4 何时使用new()和make() +# 16.4 何时使用 new() 和 make() 在第[7.2.1小节](07.2.md)和第[10.2.2](10.2.md)小节,我们已经讨论过此问题,并使用代码进行详细说明,观点如下: @@ -9,4 +9,4 @@ - [目录](directory.md) - 上一节:[发生错误时使用defer关闭一个文件](16.3.md) -- 下一节:[不需要将一个指向切片的指针传递给函数](16.5.md) \ No newline at end of file +- 下一节:[不需要将一个指向切片的指针传递给函数](16.5.md) diff --git a/eBook/16.7.md b/eBook/16.7.md index 1f72f16..2ee5467 100644 --- a/eBook/16.7.md +++ b/eBook/16.7.md @@ -6,4 +6,4 @@ - [目录](directory.md) - 上一节:[使用指针指向接口类型](16.6.md) -- 下一节:[误用协程和通道](16.8.md) \ No newline at end of file +- 下一节:[误用协程和通道](16.8.md) diff --git a/eBook/16.8.md b/eBook/16.8.md index 2b17942..cd1dfa8 100644 --- a/eBook/16.8.md +++ b/eBook/16.8.md @@ -8,4 +8,4 @@ - [目录](directory.md) - 上一节:[使用值类型时误用指针](16.7.md) -- 下一节:[闭包和协程的使用](16.9.md) \ No newline at end of file +- 下一节:[闭包和协程的使用](16.9.md) diff --git a/eBook/16.9.md b/eBook/16.9.md index 8800bf7..810931a 100644 --- a/eBook/16.9.md +++ b/eBook/16.9.md @@ -72,4 +72,4 @@ func main() { - [目录](directory.md) - 上一节:[误用协程和通道](16.8.md) -- 下一节:[糟糕的错误处理](16.10.md) \ No newline at end of file +- 下一节:[糟糕的错误处理](16.10.md) diff --git a/eBook/17.0.md b/eBook/17.0.md index 9f4d241..b4afe25 100644 --- a/eBook/17.0.md +++ b/eBook/17.0.md @@ -1,4 +1,4 @@ -# 17 Go 语言模式 +# 17.0 模式 ## 链接 diff --git a/eBook/18.0.md b/eBook/18.0.md index 0319b54..9e9b964 100644 --- a/eBook/18.0.md +++ b/eBook/18.0.md @@ -1,7 +1,7 @@ -# 18 出于性能考虑的实用代码片段 +# 18.0 出于性能考虑的实用代码片段 ## 链接 - [目录](directory.md) - 上一章:[运算符模式和接口](17.4.md) -- 下一节:[字符串](18.1.md) \ No newline at end of file +- 下一节:[字符串](18.1.md) diff --git a/eBook/18.10.md b/eBook/18.10.md index 9d7acc3..72c1d53 100644 --- a/eBook/18.10.md +++ b/eBook/18.10.md @@ -21,4 +21,4 @@ if err != nil { - [目录](directory.md) - 上一节:[网络和网页应用](18.9.md) -- 下一节:[出于性能考虑的最佳实践和建议](18.11.md) \ No newline at end of file +- 下一节:[出于性能考虑的最佳实践和建议](18.11.md) diff --git a/eBook/18.2.md b/eBook/18.2.md index d5ab9f2..18ec6c5 100644 --- a/eBook/18.2.md +++ b/eBook/18.2.md @@ -47,4 +47,4 @@ Found: for row := range arr2Dim { - [目录](directory.md) - 上一节:[字符串](18.1.md) -- 下一节:[映射](18.3.md) \ No newline at end of file +- 下一节:[映射](18.3.md) diff --git a/eBook/18.3.md b/eBook/18.3.md index de41251..4422890 100644 --- a/eBook/18.3.md +++ b/eBook/18.3.md @@ -26,4 +26,4 @@ for key, value := range map1 { - [目录](directory.md) - 上一节:[数组和切片](18.2.md) -- 下一节:[结构体](18.4.md) \ No newline at end of file +- 下一节:[结构体](18.4.md) diff --git a/eBook/18.4.md b/eBook/18.4.md index 2ed1b29..6965f91 100644 --- a/eBook/18.4.md +++ b/eBook/18.4.md @@ -31,4 +31,4 @@ func Newstruct1(n int, f float32, name string) *struct1 { - [目录](directory.md) - 上一节:[映射](18.3.md) -- 下一节:[接口](18.5.md) \ No newline at end of file +- 下一节:[接口](18.5.md) diff --git a/eBook/18.5.md b/eBook/18.5.md index 009178a..e5249ea 100644 --- a/eBook/18.5.md +++ b/eBook/18.5.md @@ -35,4 +35,4 @@ func classifier(items ...interface{}) { - [目录](directory.md) - 上一节:[结构体](18.4.md) -- 下一节:[函数](18.6.md) \ No newline at end of file +- 下一节:[函数](18.6.md) diff --git a/eBook/18.6.md b/eBook/18.6.md index ee2093b..056f808 100644 --- a/eBook/18.6.md +++ b/eBook/18.6.md @@ -20,4 +20,4 @@ func protect(g func()) { - [目录](directory.md) - 上一节:[接口](18.5.md) -- 下一节:[文件](18.7.md) \ No newline at end of file +- 下一节:[文件](18.7.md) diff --git a/eBook/18.7.md b/eBook/18.7.md index 0112e5d..9e5b3e1 100644 --- a/eBook/18.7.md +++ b/eBook/18.7.md @@ -49,4 +49,4 @@ func cat(f *file.File) { - [目录](directory.md) - 上一节:[函数](18.6.md) -- 下一节:[协程(goroutine)与通道(channel)](18.8.md) \ No newline at end of file +- 下一节:[协程(goroutine)与通道(channel)](18.8.md) diff --git a/eBook/18.8.md b/eBook/18.8.md index c2e9de7..c615b43 100644 --- a/eBook/18.8.md +++ b/eBook/18.8.md @@ -113,4 +113,4 @@ func Worker(in, out chan *Task) { - [目录](directory.md) - 上一节:[文件](18.7.md) -- 下一节:[网络和网页应用](18.9.md) \ No newline at end of file +- 下一节:[网络和网页应用](18.9.md) diff --git a/eBook/18.9.md b/eBook/18.9.md index 491d7e0..170edc1 100644 --- a/eBook/18.9.md +++ b/eBook/18.9.md @@ -18,4 +18,4 @@ var strTempl = template.Must(template.New("TName").Parse(strTemplateHTML)) - [目录](directory.md) - 上一节:[协程(goroutine)与通道(channel)](18.8.md) -- 下一节:[其他](18.10.md) \ No newline at end of file +- 下一节:[其他](18.10.md) diff --git a/eBook/19.0.md b/eBook/19.0.md index 37a8cbf..6c5e409 100644 --- a/eBook/19.0.md +++ b/eBook/19.0.md @@ -1,4 +1,4 @@ -# 19 构建一个完整的应用程序 +# 19.0 构建一个完整的应用程序 ## 链接 diff --git a/eBook/directory.md b/eBook/directory.md index b0d3a5d..269f116 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -120,7 +120,7 @@ - 12.6 [用切片读写文件](12.6.md) - 12.7 [用 defer 关闭文件](12.7.md) - 12.8 [使用接口的实际例子:fmt.Fprintf](12.8.md) - - 12.9 [格式化 JSON 数据](12.9.md) + - 12.9 [JSON 数据格式](12.9.md) - 12.10 [XML 数据格式](12.10.md) - 12.11 [用 Gob 传输数据](12.11.md) - 12.12 [Go 中的密码学](12.12.md) @@ -137,10 +137,10 @@ - 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 第14章:[协程(goroutine)与通道(channel)](14.0.md) - 14.1 [并发、并行和协程](14.1.md) - - 14.2 [使用通道进行协程间通信](14.2.md) - - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) + - 14.2 [协程间的信道](14.2.md) + - 14.3 [协程的同步:关闭通道-测试阻塞的通道](14.3.md) - 14.4 [使用 select 切换协程](14.4.md) - - 14.5 [通道,超时和计时器(Ticker)](14.5.md) + - 14.5 [通道、超时和计时器(Ticker)](14.5.md) - 14.6 [协程和恢复(recover)](14.6.md) - 14.7 [新旧模型对比:任务和worker](14.7.md) - 14.8 [惰性生成器的实现](14.8.md) @@ -153,9 +153,9 @@ - 14.15 [漏桶算法](14.15.md) - 14.16 [对Go协程进行基准测试](14.16.md) - 14.17 [使用通道并发访问对象](14.17.md) -- 第15章:[网络、模版与网页应用](15.0.md) - - 15.1 [tcp服务器](15.1.md) - - 15.2 [一个简单的web服务器](15.2.md) +- 第15章:[网络、模板与网页应用](15.0.md) + - 15.1 [tcp 服务器](15.1.md) + - 15.2 [一个简单的 web 服务器](15.2.md) - 15.3 [访问并读取页面数据](15.3.md) - 15.4 [写一个简单的网页应用](15.4.md) - 15.5 [确保网页应用健壮](15.5.md) @@ -172,8 +172,8 @@ - 第16章:[常见的陷阱与错误](16.0.md) - 16.1 [误用短声明导致变量覆盖](16.1.md) - 16.2 [误用字符串](16.2.md) - - 16.3 [发生错误时使用defer关闭一个文件](16.3.md) - - 16.4 [何时使用new()和make()](16.4.md) + - 16.3 [发生错误时使用 defer 关闭一个文件](16.3.md) + - 16.4 [何时使用 new() 和 make()](16.4.md) - 16.5 [不需要将一个指向切片的指针传递给函数](16.5.md) - 16.6 [使用指针指向接口类型](16.6.md) - 16.7 [使用值类型时误用指针](16.7.md) @@ -181,7 +181,7 @@ - 16.9 [闭包和协程的使用](16.9.md) - 16.10 [糟糕的错误处理](16.10.md) - 第17章:[模式](17.0.md) - - 17.1 [逗号ok模式](17.1.md) + - 17.1 [逗号 ok 模式](17.1.md) - 17.2 [defer 模式](17.2.md) - 17.3 [可见性模式](17.3.md) - 17.4 [运算符模式和接口](17.4.md) @@ -197,7 +197,7 @@ - 18.9 [网络和网页应用](18.9.md) - 18.10 [其他](18.10.md) - 18.11 [出于性能考虑的最佳实践和建议](18.11.md) -- 第19章:构建一个完整的应用程序 +- 第19章:[构建一个完整的应用程序](19.0.md) - 19.1 [简介](19.1.md) - 19.2 [短网址项目简介](19.2.md) - 19.3 [数据结构](19.3.md)