改正错别字及微调格式 (#782)

This commit is contained in:
SiHuan
2020-11-24 01:46:48 +08:00
committed by GitHub
parent d89b99b642
commit 806880974d
43 changed files with 894 additions and 894 deletions

View File

@@ -20,4 +20,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一节:[在 Mac OS X 上安装 Go](02.4.md) - 上一节:[在 Mac OS X 上安装 Go](02.4.md)
- 下一节:[安装目录清单](02.6.md) - 下一节:[安装目录清单](02.6.md)

View File

@@ -16,4 +16,4 @@ README.md, AUTHORS, CONTRIBUTORS, LICENSE
- [目录](directory.md) - [目录](directory.md)
- 上一节:[在 Windows 上安装 Go](02.5.md) - 上一节:[在 Windows 上安装 Go](02.5.md)
- 下一节:[Go 运行时runtime](02.7.md) - 下一节:[Go 运行时runtime](02.7.md)

View File

@@ -8,4 +8,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一章:[Go 解释器](02.8.md) - 上一章:[Go 解释器](02.8.md)
- 下一节:[Go 开发环境的基本要求](03.1.md) - 下一节:[Go 开发环境的基本要求](03.1.md)

View File

@@ -18,4 +18,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一节:[编辑器和集成开发环境](03.2.md) - 上一节:[编辑器和集成开发环境](03.2.md)
- 下一节:[构建并运行 Go 程序](03.4.md) - 下一节:[构建并运行 Go 程序](03.4.md)

View File

@@ -1,4 +1,4 @@
# 6.0 函数 # 6.0 函数function
函数是 Go 里面的基本代码块Go 函数的功能非常强大,以至于被认为拥有函数式编程语言的多种特性。在这一章,我们将对 [第 4.2.2 节](04.2.md) 所简要描述的函数进行详细的讲解。 函数是 Go 里面的基本代码块Go 函数的功能非常强大,以至于被认为拥有函数式编程语言的多种特性。在这一章,我们将对 [第 4.2.2 节](04.2.md) 所简要描述的函数进行详细的讲解。
@@ -6,4 +6,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一章:[标签与 goto](05.6.md) - 上一章:[标签与 goto](05.6.md)
- 下一节:[介绍](06.1.md) - 下一节:[介绍](06.1.md)

View File

@@ -18,4 +18,4 @@ fmt.Printf("longCalculation took this amount of time: %s\n", delta)
- [目录](directory.md) - [目录](directory.md)
- 上一节:[使用闭包调试](06.10.md) - 上一节:[使用闭包调试](06.10.md)
- 下一节:[通过内存缓存来提升性能](06.12.md) - 下一节:[通过内存缓存来提升性能](06.12.md)

View File

@@ -1,4 +1,4 @@
# 10 结构struct与方法method # 10.0 结构struct与方法method
Go 通过类型别名alias types和结构体的形式支持用户自定义类型或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型composite types当需要定义一个类型它由一系列属性组成每个属性都有自己的类型和值的时候就应该使用结构体它把数据聚集在一起。然后可以访问这些数据就好像它是一个独立实体的一部分。结构体也是值类型因此可以通过 **new** 函数来创建。 Go 通过类型别名alias types和结构体的形式支持用户自定义类型或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型composite types当需要定义一个类型它由一系列属性组成每个属性都有自己的类型和值的时候就应该使用结构体它把数据聚集在一起。然后可以访问这些数据就好像它是一个独立实体的一部分。结构体也是值类型因此可以通过 **new** 函数来创建。

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# 总结Go 中的面向对象 # 11.13 总结Go 中的面向对象
我们总结一下前面看到的Go 没有类,而是松耦合的类型、方法对接口的实现。 我们总结一下前面看到的Go 没有类,而是松耦合的类型、方法对接口的实现。

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# 12 读写数据 # 12.0 读写数据
除了 fmt 和 os 包,我们还需要用到 bufio 包来处理缓冲的输入和输出。 除了 fmt 和 os 包,我们还需要用到 bufio 包来处理缓冲的输入和输出。

View File

@@ -1,4 +1,4 @@
# 13 错误处理与测试 # 13.0 错误处理与测试
Go 没有像 Java 和 .NET 那样的 `try/catch` 异常机制:不能执行抛异常操作。但是有一套 `defer-panic-and-recover` 机制(参见 13.2-13.3 节)。 Go 没有像 Java 和 .NET 那样的 `try/catch` 异常机制:不能执行抛异常操作。但是有一套 `defer-panic-and-recover` 机制(参见 13.2-13.3 节)。

View File

@@ -1,4 +1,4 @@
# 14 协程goroutine与通道channel # 14.0 协程goroutine与通道channel
作为一门 21 世纪的语言Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见第 15 章和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言编译器和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。 作为一门 21 世纪的语言Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见第 15 章和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言编译器和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。

View File

@@ -1,4 +1,4 @@
# 15.0 网络模板网页应用 # 15.0 网络模板网页应用
Go 在编写 web 应用方面非常得力。因为目前它还没有GUIGraphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的 html 页面是目前 Go 编写界面应用程序的唯一方式。(**译者注实际上在翻译的时候已经有了一些不太成熟的GUI库例如go ui。** Go 在编写 web 应用方面非常得力。因为目前它还没有GUIGraphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的 html 页面是目前 Go 编写界面应用程序的唯一方式。(**译者注实际上在翻译的时候已经有了一些不太成熟的GUI库例如go ui。**

View File

@@ -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) http 是比 tcp 更高层的协议它描述了网页服务器如何与客户端浏览器进行通信。Go 提供了 `net/http`我们马上就来看下。先从一些简单的示例开始首先编写一个“Hello world!”网页服务器:[查看示例15.6](examples/chapter_15/hello_world_webserver.go)

View File

@@ -1,4 +1,4 @@
# 15.3 访问并读取页面 # 15.3 访问并读取页面数据
在下边这个程序中,数组中的 url 都将被访问:会发送一个简单的 `http.Head()` 请求查看返回值;它的声明如下:`func Head(url string) (r *Response, err error)` 在下边这个程序中,数组中的 url 都将被访问:会发送一个简单的 `http.Head()` 请求查看返回值;它的声明如下:`func Head(url string) (r *Response, err error)`

View File

@@ -1,4 +1,4 @@
# 16 常见的陷阱与错误 # 16.0 常见的陷阱与错误
在之前的内容中,有时候使用`!!...!!`标记警告go语言中的一些错误使用方式。当你在编程时候遇到的一个困难可以确定本书特定的章节能找到类似的主题。为了方便起见这里列出了一些常见陷阱以便于你能发现更多的解释和例子 在之前的内容中,有时候使用`!!...!!`标记警告go语言中的一些错误使用方式。当你在编程时候遇到的一个困难可以确定本书特定的章节能找到类似的主题。为了方便起见这里列出了一些常见陷阱以便于你能发现更多的解释和例子

View File

@@ -38,4 +38,4 @@ func shadow() (err error) {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[常见的陷阱与错误](16.0.md) - 上一节:[常见的陷阱与错误](16.0.md)
- 下一节:[误用字符串](16.2.md) - 下一节:[误用字符串](16.2.md)

View File

@@ -1,4 +1,4 @@
# 16.3 发生错误时使用defer关闭一个文件 # 16.3 发生错误时使用 defer 关闭一个文件
如果你在一个for循环内部处理一系列文件你需要使用defer确保文件在处理完毕后被关闭例如 如果你在一个for循环内部处理一系列文件你需要使用defer确保文件在处理完毕后被关闭例如
@@ -34,4 +34,4 @@ for _, file := range files {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[误用字符串](16.2.md) - 上一节:[误用字符串](16.2.md)
- 下一节:[何时使用new()和make()](16.4.md) - 下一节:[何时使用new()和make()](16.4.md)

View File

@@ -1,4 +1,4 @@
# 16.4 何时使用new()make() # 16.4 何时使用 new()make()
在第[7.2.1小节](07.2.md)和第[10.2.2](10.2.md)小节,我们已经讨论过此问题,并使用代码进行详细说明,观点如下: 在第[7.2.1小节](07.2.md)和第[10.2.2](10.2.md)小节,我们已经讨论过此问题,并使用代码进行详细说明,观点如下:
@@ -9,4 +9,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一节:[发生错误时使用defer关闭一个文件](16.3.md) - 上一节:[发生错误时使用defer关闭一个文件](16.3.md)
- 下一节:[不需要将一个指向切片的指针传递给函数](16.5.md) - 下一节:[不需要将一个指向切片的指针传递给函数](16.5.md)

View File

@@ -6,4 +6,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一节:[使用指针指向接口类型](16.6.md) - 上一节:[使用指针指向接口类型](16.6.md)
- 下一节:[误用协程和通道](16.8.md) - 下一节:[误用协程和通道](16.8.md)

View File

@@ -8,4 +8,4 @@
- [目录](directory.md) - [目录](directory.md)
- 上一节:[使用值类型时误用指针](16.7.md) - 上一节:[使用值类型时误用指针](16.7.md)
- 下一节:[闭包和协程的使用](16.9.md) - 下一节:[闭包和协程的使用](16.9.md)

View File

@@ -72,4 +72,4 @@ func main() {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[误用协程和通道](16.8.md) - 上一节:[误用协程和通道](16.8.md)
- 下一节:[糟糕的错误处理](16.10.md) - 下一节:[糟糕的错误处理](16.10.md)

View File

@@ -1,4 +1,4 @@
# 17 Go 语言模式 # 17.0 模式
## 链接 ## 链接

View File

@@ -1,7 +1,7 @@
# 18 出于性能考虑的实用代码片段 # 18.0 出于性能考虑的实用代码片段
## 链接 ## 链接
- [目录](directory.md) - [目录](directory.md)
- 上一章:[运算符模式和接口](17.4.md) - 上一章:[运算符模式和接口](17.4.md)
- 下一节:[字符串](18.1.md) - 下一节:[字符串](18.1.md)

View File

@@ -21,4 +21,4 @@ if err != nil {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[网络和网页应用](18.9.md) - 上一节:[网络和网页应用](18.9.md)
- 下一节:[出于性能考虑的最佳实践和建议](18.11.md) - 下一节:[出于性能考虑的最佳实践和建议](18.11.md)

View File

@@ -47,4 +47,4 @@ Found: for row := range arr2Dim {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[字符串](18.1.md) - 上一节:[字符串](18.1.md)
- 下一节:[映射](18.3.md) - 下一节:[映射](18.3.md)

View File

@@ -26,4 +26,4 @@ for key, value := range map1 {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[数组和切片](18.2.md) - 上一节:[数组和切片](18.2.md)
- 下一节:[结构体](18.4.md) - 下一节:[结构体](18.4.md)

View File

@@ -31,4 +31,4 @@ func Newstruct1(n int, f float32, name string) *struct1 {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[映射](18.3.md) - 上一节:[映射](18.3.md)
- 下一节:[接口](18.5.md) - 下一节:[接口](18.5.md)

View File

@@ -35,4 +35,4 @@ func classifier(items ...interface{}) {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[结构体](18.4.md) - 上一节:[结构体](18.4.md)
- 下一节:[函数](18.6.md) - 下一节:[函数](18.6.md)

View File

@@ -20,4 +20,4 @@ func protect(g func()) {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[接口](18.5.md) - 上一节:[接口](18.5.md)
- 下一节:[文件](18.7.md) - 下一节:[文件](18.7.md)

View File

@@ -49,4 +49,4 @@ func cat(f *file.File) {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[函数](18.6.md) - 上一节:[函数](18.6.md)
- 下一节:[协程goroutine与通道channel](18.8.md) - 下一节:[协程goroutine与通道channel](18.8.md)

View File

@@ -113,4 +113,4 @@ func Worker(in, out chan *Task) {
- [目录](directory.md) - [目录](directory.md)
- 上一节:[文件](18.7.md) - 上一节:[文件](18.7.md)
- 下一节:[网络和网页应用](18.9.md) - 下一节:[网络和网页应用](18.9.md)

View File

@@ -18,4 +18,4 @@ var strTempl = template.Must(template.New("TName").Parse(strTemplateHTML))
- [目录](directory.md) - [目录](directory.md)
- 上一节:[协程goroutine与通道channel](18.8.md) - 上一节:[协程goroutine与通道channel](18.8.md)
- 下一节:[其他](18.10.md) - 下一节:[其他](18.10.md)

View File

@@ -1,4 +1,4 @@
# 19 构建一个完整的应用程序 # 19.0 构建一个完整的应用程序
## 链接 ## 链接

View File

@@ -120,7 +120,7 @@
- 12.6 [用切片读写文件](12.6.md) - 12.6 [用切片读写文件](12.6.md)
- 12.7 [用 defer 关闭文件](12.7.md) - 12.7 [用 defer 关闭文件](12.7.md)
- 12.8 [使用接口的实际例子fmt.Fprintf](12.8.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.10 [XML 数据格式](12.10.md)
- 12.11 [用 Gob 传输数据](12.11.md) - 12.11 [用 Gob 传输数据](12.11.md)
- 12.12 [Go 中的密码学](12.12.md) - 12.12 [Go 中的密码学](12.12.md)
@@ -137,10 +137,10 @@
- 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 13.10 [性能调试:分析并优化 Go 程序](13.10.md)
- 第14章[协程goroutine与通道channel](14.0.md) - 第14章[协程goroutine与通道channel](14.0.md)
- 14.1 [并发、并行和协程](14.1.md) - 14.1 [并发、并行和协程](14.1.md)
- 14.2 [使用通道进行协程间通信](14.2.md) - 14.2 [协程间的信道](14.2.md)
- 14.3 [协程同步:关闭通道-阻塞的通道进行测试](14.3.md) - 14.3 [协程同步:关闭通道-测试阻塞的通道](14.3.md)
- 14.4 [使用 select 切换协程](14.4.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.6 [协程和恢复recover](14.6.md)
- 14.7 [新旧模型对比任务和worker](14.7.md) - 14.7 [新旧模型对比任务和worker](14.7.md)
- 14.8 [惰性生成器的实现](14.8.md) - 14.8 [惰性生成器的实现](14.8.md)
@@ -153,9 +153,9 @@
- 14.15 [漏桶算法](14.15.md) - 14.15 [漏桶算法](14.15.md)
- 14.16 [对Go协程进行基准测试](14.16.md) - 14.16 [对Go协程进行基准测试](14.16.md)
- 14.17 [使用通道并发访问对象](14.17.md) - 14.17 [使用通道并发访问对象](14.17.md)
- 第15章[网络、模与网页应用](15.0.md) - 第15章[网络、模与网页应用](15.0.md)
- 15.1 [tcp服务器](15.1.md) - 15.1 [tcp 服务器](15.1.md)
- 15.2 [一个简单的web服务器](15.2.md) - 15.2 [一个简单的 web 服务器](15.2.md)
- 15.3 [访问并读取页面数据](15.3.md) - 15.3 [访问并读取页面数据](15.3.md)
- 15.4 [写一个简单的网页应用](15.4.md) - 15.4 [写一个简单的网页应用](15.4.md)
- 15.5 [确保网页应用健壮](15.5.md) - 15.5 [确保网页应用健壮](15.5.md)
@@ -172,8 +172,8 @@
- 第16章[常见的陷阱与错误](16.0.md) - 第16章[常见的陷阱与错误](16.0.md)
- 16.1 [误用短声明导致变量覆盖](16.1.md) - 16.1 [误用短声明导致变量覆盖](16.1.md)
- 16.2 [误用字符串](16.2.md) - 16.2 [误用字符串](16.2.md)
- 16.3 [发生错误时使用defer关闭一个文件](16.3.md) - 16.3 [发生错误时使用 defer 关闭一个文件](16.3.md)
- 16.4 [何时使用new()make()](16.4.md) - 16.4 [何时使用 new()make()](16.4.md)
- 16.5 [不需要将一个指向切片的指针传递给函数](16.5.md) - 16.5 [不需要将一个指向切片的指针传递给函数](16.5.md)
- 16.6 [使用指针指向接口类型](16.6.md) - 16.6 [使用指针指向接口类型](16.6.md)
- 16.7 [使用值类型时误用指针](16.7.md) - 16.7 [使用值类型时误用指针](16.7.md)
@@ -181,7 +181,7 @@
- 16.9 [闭包和协程的使用](16.9.md) - 16.9 [闭包和协程的使用](16.9.md)
- 16.10 [糟糕的错误处理](16.10.md) - 16.10 [糟糕的错误处理](16.10.md)
- 第17章[模式](17.0.md) - 第17章[模式](17.0.md)
- 17.1 [逗号ok模式](17.1.md) - 17.1 [逗号 ok 模式](17.1.md)
- 17.2 [defer 模式](17.2.md) - 17.2 [defer 模式](17.2.md)
- 17.3 [可见性模式](17.3.md) - 17.3 [可见性模式](17.3.md)
- 17.4 [运算符模式和接口](17.4.md) - 17.4 [运算符模式和接口](17.4.md)
@@ -197,7 +197,7 @@
- 18.9 [网络和网页应用](18.9.md) - 18.9 [网络和网页应用](18.9.md)
- 18.10 [其他](18.10.md) - 18.10 [其他](18.10.md)
- 18.11 [出于性能考虑的最佳实践和建议](18.11.md) - 18.11 [出于性能考虑的最佳实践和建议](18.11.md)
- 第19章构建一个完整的应用程序 - 第19章[构建一个完整的应用程序](19.0.md)
- 19.1 [简介](19.1.md) - 19.1 [简介](19.1.md)
- 19.2 [短网址项目简介](19.2.md) - 19.2 [短网址项目简介](19.2.md)
- 19.3 [数据结构](19.3.md) - 19.3 [数据结构](19.3.md)