diff --git a/eBook/11.0.md b/eBook/11.0.md index 9270a46..243d67e 100644 --- a/eBook/11.0.md +++ b/eBook/11.0.md @@ -1,9 +1,9 @@ -# 11 ӿڣInterfaces뷴䣨reflection +# 11 接口(Interfaces)与反射(reflection) -½ Go нӿںͷݡ +本章介绍 Go 语言中接口和反射的相关内容。 -## +## 链接 -- [Ŀ¼](directory.md) -- һڣ[պSetFinalizer](10.8.md) -- һڣ[ӿʲô](11.1.md) +- [目录](directory.md) +- 上一节:[垃圾回收和SetFinalizer](10.8.md) +- 下一节:[接口是什么](11.1.md) diff --git a/eBook/11.1.md b/eBook/11.1.md index a28eb3e..7f86fc5 100644 --- a/eBook/11.1.md +++ b/eBook/11.1.md @@ -1,12 +1,12 @@ -# 11.1 ӿʲô +# 11.1 接口是什么 -Go Բһ *ͳ* ԣûͼ̳еĸ +Go 语言不是一种 *“传统”* 的面向对象编程语言:它里面没有类和继承的概念。 - Go зdz **ӿ** ͨʵֺܶԡӿṩһַʽ **˵** Ϊ˭ܸ㶨£Ϳ +但是 Go 语言里有非常灵活的 **接口** 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 **说明** 对象的行为:如果谁能搞定这件事,它就可以用在这儿。 -ӿڶһ鷽Щʵ֣룺ûбʵ֣dzģӿҲܰ +接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。 -ͨ¸ʽӿڣ +通过如下格式定义接口: ```go type Namer interface { @@ -16,35 +16,35 @@ type Namer interface { } ``` - `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 еĽӿڶܼ̣ͨǻ03 +Go 语言中的接口都很简短,通常它们会包含0个、最多3个方法。 -ԣ Go нӿڿֵһӿ͵ıһ **ӿֵ** `var ai Namer``ai`һ֣multiwordݽṹֵ `nil`һָ룬Ȼȫһ¡ָӿֵָǷǷģDzһҲûУᵼ´ +不像大多数面向对象编程语言,在 Go 语言中接口可以有值,一个接口类型的变量或一个 **接口值** :`var ai Namer`,`ai`是一个多字(multiword)数据结构,它的值是 `nil`。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。 ![](images/11.1_fig11.1.jpg?raw=true) -˴ķָͨʱġ +此处的方法指针表是通过运行时反射能力构建的。 -ͣṹ壩ʵֽӿڷеķÿһʵ˵˴˷ڸ͵ģ**ʵֽӿ**ͬʱҲ˸͵Ľӿڡʵ `Namer` ӿ͵ıԸֵ `ai` ֵʱеָָʵֵĽӿڷȻһͣҲʵ˸ýӿڣıֵ`ai`ߣעָͷʵ֣Ҳ֮ı䡣 +类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:**即实现接口**,同时方法集也构成了该类型的接口。实现了 `Namer` 接口类型的变量可以赋值给 `ai` (接收者值),此时方法表中的指针会指向被实现的接口方法。当然如果另一个类型(也实现了该接口)的变量被赋值给`ai`,这二者(译者注:指针和方法实现)也会随之改变。 -**ͲҪʽʵijӿڣӿڱʽʵ֡Ϳʵͬһӿ** +**类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口**。 -**ʵijӿڵͣʵֽӿڷ⣩ķ** +**实现某个接口的类型(除了实现接口方法外)可以有其他的方法**。 -**һͿʵֶӿ** +**一个类型可以实现多个接口**。 -**ӿͿ԰һʵã ʵʵ˴˽ӿڣӿǶ̬ͣ** +**接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)**。 -ʹӿ֮Ŷ壬ߴڲͬİУ룺ֻҪʵ˽ӿеķʵ˴˽ӿڡ +即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。 -Щʹýӿھкܴԡ +所有这些特性使得接口具有很大的灵活性。 -һӣ +第一个例子: -ʾ 11.1 interfaces.go +示例 11.1 interfaces.go: ```go package main @@ -77,30 +77,30 @@ func main() { } ``` - +输出: The square has area: 25.000000 -ijһṹ `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: *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 +示例 11.2 interfaces_poly.go: ```go package main @@ -142,7 +142,7 @@ func main() { } ``` - +输出: Looping through shapes for area ... Shape details: {5 3} @@ -150,13 +150,13 @@ func main() { Shape details: &{5} 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 +示例 11.3 valuable.go: ```go package main @@ -202,14 +202,14 @@ func main() { } ``` - +输出: Value of the asset is 2308.800049 Value of the asset is 66500.000000 -**һ׼** +**一个标准库的例子** -`io`һӿ `Reader`: +`io`包里有一个接口类型 `Reader`: ```go type Reader interface { @@ -217,9 +217,9 @@ type Reader interface { } ``` - `r`` var r io.Reader` +定义变量 `r`:` var r io.Reader` -ôͿдµĴ룺 +那么就可以写如下的代码: ```go var r io.Reader @@ -230,27 +230,27 @@ type Reader interface { r = bufio.NewReader(f) ``` - `r` ұߵͶʵ `Read()` ͬķǩ`r` ľ̬ `io.Reader` +上面 `r` 右边的类型都实现了 `Read()` 方法,并且有相同的方法签名,`r` 的静态类型是 `io.Reader`。 -**ע** +**备注** -еʱҲһ΢ͬķʽʹýӿʣij͵ĽǶĽӿָǣеֻûʽΪЩⶨһӿڶѡ +有的时候,也会以一种稍微不同的方式来使用接口这个词:从某个类型的角度来看,它的接口指的是:它的所有导出方法,只不过没有显式地为这些导出方法额外定一个接口而已。 -**ϰ 11.1** simple_interface.go +**练习 11.1** simple_interface.go: -һӿ `Simpler`һ `Get()` һ `Set()``Get()`һֵ`Set()` һͲһṹ `Simple` ʵӿڡ +定义一个接口 `Simpler`,它有一个 `Get()` 方法和一个 `Set()`,`Get()`返回一个整型值,`Set()` 有一个整型参数。创建一个结构体类型 `Simple` 实现这个接口。 -Ŷһһ `Simpler` ͵IJò `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) -- һڣ[ӿڣInterfaces뷴䣨reflection](11.0.md) -- һڣ[ӿǶ׽ӿ](11.2.md) +- [目录](directory.md) +- 上一节:[接口(Interfaces)与反射(reflection)](11.0.md) +- 下一节:[接口嵌套接口](11.2.md) diff --git a/eBook/11.2.md b/eBook/11.2.md index d7c1a53..85784c8 100644 --- a/eBook/11.2.md +++ b/eBook/11.2.md @@ -1,8 +1,8 @@ -# 11.2 ӿǶ׽ӿ +# 11.2 接口嵌套接口 -һӿڿ԰һĽӿڣ൱ֱӽЩǶӿڵķоӿһ +一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。 -ӿ `File` `ReadWrite` `Lock` зһ `Close()` +比如接口 `File` 包含了 `ReadWrite` 和 `Lock` 的所有方法,它还额外有一个 `Close()` 方法。 ```go type ReadWrite interface { @@ -22,8 +22,8 @@ type File interface { } ``` -## +## 链接 -- [Ŀ¼](directory.md) -- һڣ[ӿʲô](11.1.md) -- һڣ[μתӿڱͣͶ](11.3.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[接口是什么](11.1.md) +- 下一节:[如何检测和转换接口变量的类型:类型断言](11.3.md) \ No newline at end of file diff --git a/eBook/11.3.md b/eBook/11.3.md index b162d65..5a6602b 100644 --- a/eBook/11.3.md +++ b/eBook/11.3.md @@ -1,14 +1,14 @@ -# 11.3 Ͷԣμתӿڱ +# 11.3 类型断言:如何检测和转换接口变量的类型 -һӿ͵ı `varI` п԰κ͵ֵһַʽ **̬** ͣʱڱд洢ֵʵִ͡йж̬ͿܻͬǿԷӿڱͨ͡ǿʹ **Ͷ** ijʱ `varI` Ƿ `T` ֵ +一个接口类型的变量 `varI` 中可以包含任何类型的值,必须有一种方式来检测它的 **动态** 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 **类型断言** 来测试在某个时刻 `varI` 是否包含类型 `T` 的值: ```go 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 if v, ok := varI.(T); ok { // checked type assertion @@ -18,11 +18,11 @@ if v, ok := varI.(T); ok { // checked type assertion // 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 if _, ok := varI.(T); ok { @@ -30,9 +30,9 @@ if _, ok := varI.(T); ok { } ``` -TODO ??In this form shadowing the variable varI by giving varI and v the same name is sometimes done. +TODO ??:In this form shadowing the variable varI by giving varI and v the same name is sometimes done. -ʾ 11.4 type_interfaces.go +示例 11.4 type_interfaces.go ```go package main @@ -80,19 +80,19 @@ func (ci *Circle) Area() float32 { } ``` - +输出: The type of areaIntf is: *main.Square areaIntf does not contain a variable of type Circle -жһ `Circle`Ҳʵ `Shaper` ӿڡ `t, ok := areaIntf.(*Square); ok ` `areaIntf` Ƿһ 'Square' ͵ıȷģȻDzǷһ 'Circle' ͵ıǷ񶨵ġ +程序行中定义了一个新类型 `Circle`,它也实现了 `Shaper` 接口。 `t, ok := areaIntf.(*Square); ok ` 测试 `areaIntf` 里是否一个包含 'Square' 类型的变量,结果是确定的;然后我们测试它是否包含一个 'Circle' 类型的变量,结果是否定的。 -**ע** +**备注** - `areaIntf.(*Square)` е `*` ţᵼ±`impossible type assertion: Square does not implement Shaper (Area method has pointer receiver)` +如果忽略 `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) \ No newline at end of file +- [目录](directory.md) +- 上一节:[接口嵌套接口](11.2.md) +- 下一节:[类型判断:type-switch](11.4.md) \ No newline at end of file diff --git a/eBook/11.4.md b/eBook/11.4.md index 6bbe115..618372b 100644 --- a/eBook/11.4.md +++ b/eBook/11.4.md @@ -1,6 +1,6 @@ -# 11.4 жϣtype-switch +# 11.4 类型判断:type-switch -ӿڱҲʹһʽ `swtich` ⣺**type-swtich** ʾ 11.4 ĵڶ֣ +接口变量的类型也可以使用一种特殊形式的 `swtich` 来检测:**type-swtich** (下面是 示例 11.4 的第二部分): ```go switch t := areaIntf.(type) { @@ -15,15 +15,15 @@ } ``` - +输出: 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` 。 -DzԱֵͣôͿԲҪֵ䣬磺 +如果仅仅是测试变量的类型,不用它的值,那么就可以不需要赋值语句,比如: ```go switch areaIntf.(type) { @@ -37,7 +37,7 @@ } ``` -ĴƬչʾһͷຯһɱ䳤Ȳ͵飬ԪصʵִвͬĶ +下面的代码片段展示了一个类型分类函数,它有一个可变长度参数,可以是任意类型的数组,它会根据数组元素的实际类型执行不同的动作: ```go @@ -61,18 +61,18 @@ func classifier(items ...interface{}) { } ``` -ô˷`classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false)` +可以这样调用此方法:`classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false)` 。 -ڴⲿġδ֪ʱ JSON XML ݣͲԺתdzá +在处理来自于外部的、类型未知的数据时,比如解析诸如 JSON 或 XML 编码的数据,类型测试和转换会非常有用。 - ʾ12.17(xml.go) н XML ĵǣǾͻõ `type-switch` +在 示例12.17(xml.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) -- һڣ[Ͷԣμתӿڱ](11.3.md) -- һڣ[һֵǷʵijӿ](11.5.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[类型断言:如何检测和转换接口变量的类型](11.3.md) +- 下一节:[测试一个值是否实现了某个接口](11.5.md) \ No newline at end of file diff --git a/eBook/11.5.md b/eBook/11.5.md index d0a5db0..6bb0b78 100644 --- a/eBook/11.5.md +++ b/eBook/11.5.md @@ -1,6 +1,6 @@ -# 11.5 һֵǷʵijӿ +# 11.5 测试一个值是否实现了某个接口 - 11.3 Ͷеһٶ `v` һֵȻǷʵ `Stringer` ӿڣ +这是 11.3 类型断言中的一个特例:假定 `v` 是一个值,然后我们想测试它是否实现了 `Stringer` 接口,可以这样做: ```go type Stringer interface { @@ -12,20 +12,20 @@ if sv, ok := v.(Stringer); ok { } ``` -`Print` ˼ǷԴӡġ +`Print` 函数就是如此检测类型是否可以打印自身的。 -ӿһԼʵͱ͵Ϊ涨Ϳʲôӿڳ׽ʲôԼ뿪ʹͬӿڵıڲͬʱֳ̱ͬΪǶ̬ıʡ +接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。 -дǽӿڱĺʹǸһԡ +编写参数是接口变量的函数,这使得它们更具有一般性。 -**ʹýӿʹԡ** +**使用接口使代码更具有普适性。** -׼ﵽʹԭԽӿڸûõİգDzιġ +标准库里到处都使用了这个原则,如果对接口概念没有良好的把握,是不可能理解它是如何构建的。 -ڽ½УǻҪӣȥǣͿԸõӦԭ +在接下来的章节中,我们会讨论两个重要的例子,试着去深入理解它们,这样你就可以更好的应用上面的原则。 -## +## 链接 -- [Ŀ¼](directory.md) -- һڣ[жϣtype-switch](11.4.md) -- һڣ[ʹ÷ӿ](11.6.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[类型判断:type-switch](11.4.md) +- 下一节:[使用方法集与接口](11.6.md) \ No newline at end of file diff --git a/eBook/11.6.md b/eBook/11.6.md index 3952f7f..029e7ed 100644 --- a/eBook/11.6.md +++ b/eBook/11.6.md @@ -1,8 +1,8 @@ -# 11.6 ʹ÷ӿ +# 11.6 使用方法集与接口 - 10.6.3 methodset1.go ǿڱϵķʵDzֱָ뻹ֵġӿֵʱе㸴ӣԭǽӿڱд洢ľֵDzѰַģ˵ǣʹò󡣿ij +在 10.6.3 及例子 methodset1.go 中我们看到,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序: -ʾ 11.5 methodset2.go: +示例 11.5 methodset2.go: ```go package main @@ -60,34 +60,34 @@ func main() { } ``` -**** +**讨论** - `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) -- һڣ[һֵǷʵijӿ](11.5.md) -- һڣ[һӣʹSorterӿ](11.7.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[测试一个值是否实现了某个接口](11.5.md) +- 下一节:[第一个例子:使用Sorter接口排序](11.7.md) \ No newline at end of file diff --git a/eBook/11.7.md b/eBook/11.7.md index 873aec9..9f7403e 100644 --- a/eBook/11.7.md +++ b/eBook/11.7.md @@ -1,9 +1,9 @@ -# 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 func Sort(data Sorter) { @@ -17,7 +17,7 @@ func Sort(data Sorter) { } ``` -`Sort` һӿͲ `Sorter` Щ +`Sort` 函数接收一个接口类型参数: `Sorter` ,它声明了这些方法: ```go type Sorter interface { @@ -27,9 +27,9 @@ type Sorter interface { } ``` -е `int` ˵ҪĶһҪһ `int``i` `j` ʾԪصҲ͵ġ +参数中的 `int` 不是说要排序的对象一定要是一组 `int`,`i` 和 `j` 表示元素的整型索引,长度也是整型的。 -һ `int` бǣΪ鶨һͲʵ `Sorter` ӿڵķ +现在如果我们想对一个 `int` 数组进行排序,所有必须做的事情就是:为数组定一个类型并在它上面实现 `Sorter` 接口的方法: ```go type IntArray []int @@ -38,7 +38,7 @@ 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} @@ -46,11 +46,11 @@ a := sort.IntArray(data) //conversion to type IntArray from package sort sort.Sort(a) ``` -ġеĴ `sort.go` `sortmain.go` ҵ +完整的、可运行的代码可以在 `sort.go` 和 `sortmain.go` 里找到。 -ͬԭһ飬һַ飬һʾÿܸĽṹ `dayArray`. +同样的原理,排序函数可以用于一个浮点型数组,一个字符串数组,或者一个表示每周各天的结构体 `dayArray`. -ʾ 11.6 sort.go +示例 11.6 sort.go: ```go package sort @@ -102,7 +102,7 @@ func IntsAreSorted(a []int) bool { return IsSorted(IntArray(a)) } func StringsAreSorted(a []string) bool { return IsSorted(StringArray(a)) } ``` -ʾ 11.7 sortmain.go +示例 11.7 sortmain.go: ```go package main @@ -173,17 +173,17 @@ func main() { } ``` - +输出: 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")` ֹͣڷµijϸο 13£ȻҲȴӡһϢȻ `os.Exit(1)` ֹͣ +`panic("fail")` 用于停止处于在非正常情况下的程序(详细请参考 第13章),当然也可以先打印一条信息,然后调用 `os.Exit(1)` 来停止程序。 -Ӱǽһ˽˽ӿڵʹ÷ʽڻ͵򣬱׼ѾṩصԲҪظˡһԵ`sort` һӿڣ +上面的例子帮助我们进一步了解了接口的意义和使用方式。对于基本类型的排序,标准库已经提供了相关的排序函数,所以不需要我们再重复造轮子了。对于一般性的排序,`sort` 包定义了一个接口: ```go type Interface interface { @@ -193,36 +193,36 @@ type Interface interface { } ``` -ӿܽҪij󷽷 `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 -- `List()`ʽַ `String()` еͲʽص `List()` ӡ飨ο10.7 -- `Fill()`һ10飨ο4.5.2.6 +- `NewFloat64Array()`:创建一个包含25个元素的数组变量(参考10.2) +- `List()`:返回数组格式化后的字符串,并在 `String()` 方法中调用它,这样就不用显式地调用 `List()` 来打印数组(参考10.7) +- `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) -- һڣ[ʹ÷ӿ](11.6.md) -- һڣ[ڶӣд](11.8.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[使用方法集与接口](11.6.md) +- 下一节:[第二个例子:读和写](11.8.md) \ No newline at end of file diff --git a/eBook/11.8.md b/eBook/11.8.md index 9bfd50f..26fa5b6 100644 --- a/eBook/11.8.md +++ b/eBook/11.8.md @@ -1,8 +1,8 @@ -# 11.8 ڶӣд +# 11.8 第二个例子:读和写 -дкձΪǻ뵽дļ棨ֽڻַƬ׼׼Լӡܵȵȣ߶дǵԶ͡ΪǴ뾡ͨãGo ȡһµķʽдݡ +读和写是软件中很普遍的行为,提起它们会立即想到读写文件、缓存(比如字节或字符串切片)、标准输入输出、标准错误以及网络连接、管道等等,或者读写我们的自定义类型。为了是代码尽可能通用,Go 采取了一致的方式来读写数据。 -`io` ṩڶдĽӿڣ`io.Reader` `io.Writer` +`io` 包提供了用于读和写的接口:`io.Reader` 和 `io.Writer` ```go type Reader interface { @@ -14,16 +14,16 @@ type Writer interface { } ``` -ֻҪʵ˶дӿڣṩ `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` Dzģ`bufio` ṩ˶ӦĴIJڶд `UTF-8` ıļʱá 12 ǻῴʵսʹǵĺܶӡ +`io` 包里的 `Readers` 和 `Writers` 都是不带缓冲的,`bufio` 包里提供了对应的带缓冲的操作,在读写 `UTF-8` 编码的文本文件时它们尤其有用。在 第12章 我们会看在实战使用它们的很多例子。 -ʵʱоܵʹЩӿڣʹøͨãκʵЩӿڵʹöд +在实际编程中尽可能的使用这些接口,会使程序变得更通用,可以在任何实现了这些接口的类型上使用读写方法。 -һ `JPEG` ͼνͨһ `Reader` ԽԴ̡ӻ `gzip` ѹ `HTTP` е `JPEG`ͼݣκʵ`Reader` ӿڵĶ +例如一个 `JPEG` 图形解码器,通过一个 `Reader` 参数,它可以解码来自磁盘、网络连接或以 `gzip` 压缩的 `HTTP` 流中的 `JPEG`图形数据,或者其他任何实现了`Reader` 接口的对象。 -## +## 链接 -- [Ŀ¼](directory.md) -- һڣ[һӣʹSorterӿ](11.7.md) -- һڣ[սӿ](11.9.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[第一个例子:使用Sorter接口排序](11.7.md) +- 下一节:[空接口](11.9.md) \ No newline at end of file diff --git a/eBook/11.9.md b/eBook/11.9.md index 07e0f50..bc74cfd 100644 --- a/eBook/11.9.md +++ b/eBook/11.9.md @@ -1,20 +1,20 @@ -# 11.9 սӿ +# 11.9 空接口 -## 11.9.1 +## 11.9.1 概念 -**սӿڻСӿ**κηʵֲκҪ +**空接口或者最小接口**不包含任何方法,它对实现不做任何要求: ```go type Any interface {} ``` -κͶʵ˿սӿڣ `Java/C#` `Object` ͣ`any` `Any` ǿսӿһܺõıд +任何其他类型都实现了空接口(它不仅仅像 `Java/C#` 中 `Object` 引用类型),`any` 或 `Any` 是空接口一个很好的别名或缩写。 -սӿ `Java/C#` Ļࣺ `Object` ࣬ߵĿҲ +空接口类似 `Java/C#` 中所有类的基类: `Object` 类,二者的目标也很相近。 -Ըһսӿ͵ı `var val interface {}` κ͵ֵ +可以给一个空接口类型的变量 `var val interface {}` 赋任何类型的值。 -ʾ 11.8 empty_interface.go +示例 11.8 empty_interface.go: ```go package main @@ -56,16 +56,16 @@ func main() { } ``` - +输出: val has the value: 5 val has the value: ABC val has the value: &{Rob Pike 55} Type pointer to Person *main.Person -Уӿڱ `val` θһ `int``string` `Person` ʵֵȻʹ `type-swtich` ʵ͡ÿ `interface {}` ڴռֳһ洢ͣһ洢ݻָݵָ롣 +在上面的例子中,接口变量 `val` 被依次赋予一个 `int`,`string` 和 `Person` 实例的值,然后使用 `type-swtich` 来测试它的实际类型。每个 `interface {}` 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。 - emptyint_switch.go ˵˿սӿ `type-swtich` `lambda` ÷ +例子 emptyint_switch.go 说明了空接口在 `type-swtich` 中联合 `lambda` 函数的用法: ```go package main @@ -101,21 +101,21 @@ func main() { } ``` - +输出: any hello is a special String! -**ϰ 11.9** simple_interface3.go +**练习 11.9** simple_interface3.go: - ϰ11.2һ `gI` ٽ `Simpler` ͵IJǽһսӿڲȻͨͶжϲǷ `Simpler` ͡ `main` ʹ `gI` ȡ `fI` ȷĴ㹻ȫ +继续 练习11.2,在它中添加一个 `gI` 函数,它不再接受 `Simpler` 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 `Simpler` 类型。最后在 `main` 使用 `gI` 取代 `fI` 函数并调用它。确保你的代码足够安全。 -## 11.9.2 ͨͻͬͱ +## 11.9.2 构建通用类型或包含不同类型变量的数组 - 7.6.6 ǿܱ `int` 顢`float` Լ `string` 飬ô͵أDzDZԼʵǣ +在 7.6.6 中我们看到了能被搜索和排序的 `int` 数组、`float` 数组以及 `string` 数组,那么对于其他类型的数组呢,是不是我们必须得自己编程实现它们? -֪ôˣͨʹÿսӿڡǸսӿڶһ `Element``type Element interface{}` +现在我们知道该怎么做了,就是通过使用空接口。让我们给空接口定一个别名类型 `Element`:`type Element interface{}` -Ȼһ͵Ľṹ `Vector`һ `Element` ԪصƬ +然后定义一个容器类型的结构体 `Vector`,它包含一个 `Element` 类型元素的切片: ```go type Vector struct { @@ -123,7 +123,7 @@ type Vector struct { } ``` -`Vector` ܷκ͵ıΪκͶʵ˿սӿڣʵ `Vector` ŵÿԪؿDzͬ͵ıΪһ `At()` ڷص `i` Ԫأ +`Vector` 里能放任何类型的变量,因为任何类型都实现了空接口,实际上 `Vector` 里放的每个元素可以是不同类型的变量。我们为它定义一个 `At()` 方法用于返回第 `i` 个元素: ```go func (p *Vector) At(i int) Element { @@ -131,7 +131,7 @@ func (p *Vector) At(i int) Element { } ``` -ٶһ `Set()` õ `i` Ԫصֵ +再定一个 `Set()` 方法用于设置第 `i` 个元素的值: ```go func (p *Vector) Set(i int, e Element) { @@ -139,26 +139,26 @@ func (p *Vector) Set(i int, e Element) { } ``` -`Vector` д洢Ԫض `Element` ͣҪõǵԭʼͣunboxing䣩ҪõͶԡTODOThe compiler rejects assertions guaranteed to failͶʱִУʱ +`Vector` 中存储的所有元素都是 `Element` 类型,要得到它们的原始类型(unboxing:拆箱)需要用到类型断言。TODO:The compiler rejects assertions guaranteed to fail,类型断言总是在运行时才执行,因此它会产生运行时错误。 -**ϰ 11.10** min_interface.go / minmain.go +**练习 11.10** min_interface.go / minmain.go: -11.7п `Sorter` ӿڣһ `Miner` ӿڲʵһЩҪIJ `Min` һ `Miner` ͱļϣȻ㲢ؼСԪء +仿照11.7中开发的 `Sorter` 接口,创建一个 `Miner` 接口并实现一些必要的操作。函数 `Min` 接受一个 `Miner` 类型变量的集合,然后计算并返回集合中最小的元素。 -## 11.9.3 ƬսӿƬ +## 11.9.3 复制数据切片至空接口切片 -һ `myType` ͵Ƭ뽫ƬеݸƵһսӿƬУƣ +假设你有一个 `myType` 类型的数据切片,你想将切片中的数据复制到一个空接口切片中,类似: ```go var dataSlice []myType = FuncReturnSlice() var interfaceSlice []interface{} = dataSlice ``` -ϧôʱ`cannot use dataSlice (type []myType) as type []interface { } in assignment` +可惜不能这么做,编译时会出错:`cannot use dataSlice (type []myType) as type []interface { } in assignment` -ԭڴеIJDzһģο[http://golang.org/doc/go_spec.html](http://golang.org/doc/go_spec.html) +原因是它们俩在内存中的布局是不一样的(参考[http://golang.org/doc/go_spec.html](http://golang.org/doc/go_spec.html))。 -ʹ `for-range` һһʽظƣ +必须使用 `for-range` 语句来一个一个显式地复制: ```go var dataSlice []myType = FuncReturnSlice() @@ -168,11 +168,11 @@ for ix, d := range dataSlice { } ``` -## 11.9.4 ͨ͵Ľڵݽṹ +## 11.9.4 通用类型的节点数据结构 -10.1бݽṹǵĶʹһֽнڵĵݹṹͣڵһij͵ֶΡڿʹÿսӿΪֶεͣǾдͨõĴ롣ʵһIJִ룺ͨö塢ڴսڵ `NewNode` ݵ `SetData` . +在10.1中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 `NewNode` 方法,及设置数据的 `SetData` 方法. -ʾ 11.10 node_structures.go: +示例 11.10 node_structures.go: ```go package main @@ -207,11 +207,11 @@ func main() { } ``` -## 11.9.5 ӿڵӿ +## 11.9.5 接口到接口 -һӿڵֵԸֵһӿڱֻҪײʵ˱Ҫķתʱмģתʧܻᵼһʱ 'Go' Զ̬һ棬 `Ruby` `Python` Щ̬Ƚϡ +一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 'Go' 语言动态的一面,可以那它和 `Ruby` 和 `Python` 这些动态语言相比较。 -ٶ +假定: ```go var ai AbsInterface // declares method Abs() @@ -223,18 +223,18 @@ pp := new(Point) // say *Point implements Abs, Sqr var empty interface{} ``` -ôͶǺϷģ +那么下面的语句和类型断言是合法的: ```go empty = pp // everything satisfies empty ai = empty.(AbsInterface) // underlying value pp implements Abs() // (runtime failure otherwise) -si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesnt +si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesn’t empty = si // *Point implements empty set // Note: statically checkable so type assertion not necessary. ``` -Ǻõһӣ +下面是函数调用的一个例子: ```go type myPrintInterface interface { @@ -246,10 +246,10 @@ func f3(x myInterface) { } ``` -`x` תΪ `myPrintInterface` ȫ̬ģֻҪ `x` ĵײ̬ͣͣ `print` þͿС +`x` 转换为 `myPrintInterface` 类型是完全动态的:只要 `x` 的底层类型(动态类型)定义了 `print` 方法这个调用就可以正常运行。 -## +## 链接 -- [Ŀ¼](directory.md) -- һڣ[ڶӣд](11.8.md) -- һڣ[](11.10.md) \ No newline at end of file +- [目录](directory.md) +- 上一节:[第二个例子:读和写](11.8.md) +- 下一节:[反射包](11.10.md) \ No newline at end of file