mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-11 23:08:34 +08:00
改正错别字及微调格式 (#782)
This commit is contained in:
@@ -20,4 +20,4 @@
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[在 Mac OS X 上安装 Go](02.4.md)
|
||||
- 下一节:[安装目录清单](02.6.md)
|
||||
- 下一节:[安装目录清单](02.6.md)
|
||||
|
@@ -16,4 +16,4 @@ README.md, AUTHORS, CONTRIBUTORS, LICENSE
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[在 Windows 上安装 Go](02.5.md)
|
||||
- 下一节:[Go 运行时(runtime)](02.7.md)
|
||||
- 下一节:[Go 运行时(runtime)](02.7.md)
|
||||
|
@@ -8,4 +8,4 @@
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一章:[Go 解释器](02.8.md)
|
||||
- 下一节:[Go 开发环境的基本要求](03.1.md)
|
||||
- 下一节:[Go 开发环境的基本要求](03.1.md)
|
||||
|
@@ -18,4 +18,4 @@
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[编辑器和集成开发环境](03.2.md)
|
||||
- 下一节:[构建并运行 Go 程序](03.4.md)
|
||||
- 下一节:[构建并运行 Go 程序](03.4.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)
|
||||
- 下一节:[介绍](06.1.md)
|
||||
|
@@ -18,4 +18,4 @@ fmt.Printf("longCalculation took this amount of time: %s\n", delta)
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[使用闭包调试](06.10.md)
|
||||
- 下一节:[通过内存缓存来提升性能](06.12.md)
|
||||
- 下一节:[通过内存缓存来提升性能](06.12.md)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 10 结构(struct)与方法(method)
|
||||
# 10.0 结构(struct)与方法(method)
|
||||
|
||||
Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型(composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。然后可以访问这些数据,就好像它是一个独立实体的一部分。结构体也是值类型,因此可以通过 **new** 函数来创建。
|
||||
|
||||
|
@@ -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)
|
||||
|
512
eBook/11.1.md
512
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`。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。
|
||||
|
||||

|
||||
|
||||
此处的方法指针表是通过运行时反射能力构建的。
|
||||
|
||||
类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:**即实现接口**,同时方法集也构成了该类型的接口。实现了 `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`。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。
|
||||
|
||||

|
||||
|
||||
此处的方法指针表是通过运行时反射能力构建的。
|
||||
|
||||
类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:**即实现接口**,同时方法集也构成了该类型的接口。实现了 `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)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 总结:Go 中的面向对象
|
||||
# 11.13 总结:Go 中的面向对象
|
||||
|
||||
我们总结一下前面看到的:Go 没有类,而是松耦合的类型、方法对接口的实现。
|
||||
|
||||
|
@@ -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)
|
||||
# 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)
|
||||
|
192
eBook/11.3.md
192
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)
|
||||
|
154
eBook/11.4.md
154
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)
|
||||
|
@@ -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)
|
||||
# 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)
|
||||
|
184
eBook/11.6.md
184
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)
|
||||
|
454
eBook/11.7.md
454
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)
|
||||
|
@@ -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)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 12 读写数据
|
||||
# 12.0 读写数据
|
||||
|
||||
除了 fmt 和 os 包,我们还需要用到 bufio 包来处理缓冲的输入和输出。
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 13 错误处理与测试
|
||||
# 13.0 错误处理与测试
|
||||
|
||||
Go 没有像 Java 和 .NET 那样的 `try/catch` 异常机制:不能执行抛异常操作。但是有一套 `defer-panic-and-recover` 机制(参见 13.2-13.3 节)。
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 14 协程(goroutine)与通道(channel)
|
||||
# 14.0 协程(goroutine)与通道(channel)
|
||||
|
||||
作为一门 21 世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见第 15 章)和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言,编译器,和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 15.0 网络,模板和网页应用
|
||||
# 15.0 网络、模板与网页应用
|
||||
|
||||
Go 在编写 web 应用方面非常得力。因为目前它还没有GUI(Graphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的 html 页面是目前 Go 编写界面应用程序的唯一方式。(**译者注:实际上在翻译的时候,已经有了一些不太成熟的GUI库例如:go ui。**)
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 15.3 访问并读取页面
|
||||
# 15.3 访问并读取页面数据
|
||||
|
||||
在下边这个程序中,数组中的 url 都将被访问:会发送一个简单的 `http.Head()` 请求查看返回值;它的声明如下:`func Head(url string) (r *Response, err error)`
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 16 常见的陷阱与错误
|
||||
# 16.0 常见的陷阱与错误
|
||||
|
||||
在之前的内容中,有时候使用`!!...!!`标记警告go语言中的一些错误使用方式。当你在编程时候遇到的一个困难,可以确定本书特定的章节能找到类似的主题。为了方便起见,这里列出了一些常见陷阱,以便于你能发现更多的解释和例子:
|
||||
|
||||
|
@@ -38,4 +38,4 @@ func shadow() (err error) {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[常见的陷阱与错误](16.0.md)
|
||||
- 下一节:[误用字符串](16.2.md)
|
||||
- 下一节:[误用字符串](16.2.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)
|
||||
- 下一节:[何时使用new()和make()](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)
|
||||
- 下一节:[不需要将一个指向切片的指针传递给函数](16.5.md)
|
||||
|
@@ -6,4 +6,4 @@
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[使用指针指向接口类型](16.6.md)
|
||||
- 下一节:[误用协程和通道](16.8.md)
|
||||
- 下一节:[误用协程和通道](16.8.md)
|
||||
|
@@ -8,4 +8,4 @@
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[使用值类型时误用指针](16.7.md)
|
||||
- 下一节:[闭包和协程的使用](16.9.md)
|
||||
- 下一节:[闭包和协程的使用](16.9.md)
|
||||
|
@@ -72,4 +72,4 @@ func main() {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[误用协程和通道](16.8.md)
|
||||
- 下一节:[糟糕的错误处理](16.10.md)
|
||||
- 下一节:[糟糕的错误处理](16.10.md)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 17 Go 语言模式
|
||||
# 17.0 模式
|
||||
|
||||
## 链接
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# 18 出于性能考虑的实用代码片段
|
||||
# 18.0 出于性能考虑的实用代码片段
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一章:[运算符模式和接口](17.4.md)
|
||||
- 下一节:[字符串](18.1.md)
|
||||
- 下一节:[字符串](18.1.md)
|
||||
|
@@ -21,4 +21,4 @@ if err != nil {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[网络和网页应用](18.9.md)
|
||||
- 下一节:[出于性能考虑的最佳实践和建议](18.11.md)
|
||||
- 下一节:[出于性能考虑的最佳实践和建议](18.11.md)
|
||||
|
@@ -47,4 +47,4 @@ Found: for row := range arr2Dim {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[字符串](18.1.md)
|
||||
- 下一节:[映射](18.3.md)
|
||||
- 下一节:[映射](18.3.md)
|
||||
|
@@ -26,4 +26,4 @@ for key, value := range map1 {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[数组和切片](18.2.md)
|
||||
- 下一节:[结构体](18.4.md)
|
||||
- 下一节:[结构体](18.4.md)
|
||||
|
@@ -31,4 +31,4 @@ func Newstruct1(n int, f float32, name string) *struct1 {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[映射](18.3.md)
|
||||
- 下一节:[接口](18.5.md)
|
||||
- 下一节:[接口](18.5.md)
|
||||
|
@@ -35,4 +35,4 @@ func classifier(items ...interface{}) {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[结构体](18.4.md)
|
||||
- 下一节:[函数](18.6.md)
|
||||
- 下一节:[函数](18.6.md)
|
||||
|
@@ -20,4 +20,4 @@ func protect(g func()) {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[接口](18.5.md)
|
||||
- 下一节:[文件](18.7.md)
|
||||
- 下一节:[文件](18.7.md)
|
||||
|
@@ -49,4 +49,4 @@ func cat(f *file.File) {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[函数](18.6.md)
|
||||
- 下一节:[协程(goroutine)与通道(channel)](18.8.md)
|
||||
- 下一节:[协程(goroutine)与通道(channel)](18.8.md)
|
||||
|
@@ -113,4 +113,4 @@ func Worker(in, out chan *Task) {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[文件](18.7.md)
|
||||
- 下一节:[网络和网页应用](18.9.md)
|
||||
- 下一节:[网络和网页应用](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)
|
||||
- 下一节:[其他](18.10.md)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 19 构建一个完整的应用程序
|
||||
# 19.0 构建一个完整的应用程序
|
||||
|
||||
## 链接
|
||||
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user