From 39dbdb7094e197aa8baea11ebce7260c10b8d668 Mon Sep 17 00:00:00 2001 From: Jck Date: Mon, 25 Oct 2021 23:34:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?=E6=A0=87=E7=82=B9=E7=AC=A6=E5=8F=B7=EF=BC=8C=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=B6=85=E9=93=BE=E6=8E=A5=20(#804)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/04.2.md | 4 ++-- eBook/04.5.md | 10 +++++----- eBook/06.11.md | 2 +- eBook/06.12.md | 2 +- eBook/06.2.md | 2 +- eBook/06.7.md | 2 +- eBook/06.8.md | 4 ++-- eBook/06.9.md | 6 +++--- eBook/07.0.md | 2 +- eBook/07.1.md | 12 ++++++------ eBook/07.2.md | 36 ++++++++++++++++++------------------ eBook/07.3.md | 4 ++-- eBook/07.5.md | 4 ++-- eBook/07.6.md | 6 +++--- eBook/08.1.md | 6 +++--- eBook/08.3.md | 2 +- eBook/08.4.md | 2 +- eBook/08.5.md | 2 +- eBook/09.1.md | 8 ++++---- eBook/09.11.md | 12 ++++++------ eBook/09.2.md | 2 +- eBook/09.3.md | 8 ++++---- eBook/09.5.md | 16 ++++++++-------- eBook/09.6.md | 2 +- eBook/09.7.md | 4 ++-- eBook/09.8.md | 20 ++++++++++---------- eBook/09.9.md | 4 ++-- eBook/10.1.md | 10 +++++----- eBook/10.2.md | 6 +++--- eBook/10.4.md | 2 +- eBook/10.6.md | 42 +++++++++++++++++++++--------------------- eBook/10.7.md | 4 ++-- eBook/11.10.md | 4 ++-- eBook/11.11.md | 2 +- eBook/11.12.md | 9 +++++---- eBook/11.4.md | 4 ++-- eBook/11.6.md | 6 +++--- eBook/11.7.md | 12 ++++++------ eBook/11.8.md | 2 +- eBook/11.9.md | 6 +++--- eBook/12.1.md | 2 +- eBook/12.10.md | 2 +- eBook/12.2.md | 10 +++++----- eBook/12.3.md | 2 +- eBook/12.4.md | 10 +++++----- eBook/12.6.md | 2 +- eBook/12.8.md | 11 ++++++----- eBook/12.9.md | 10 +++++----- eBook/13.1.md | 6 +++--- eBook/13.10.md | 4 ++-- eBook/13.3.md | 8 ++++---- eBook/13.5.md | 8 +++----- eBook/13.7.md | 6 +++--- eBook/13.8.md | 2 +- eBook/13.9.md | 2 +- eBook/14.0.md | 2 +- eBook/14.1.md | 2 +- eBook/14.10.md | 43 ++++++++++++++++++++++++------------------- eBook/14.11.md | 4 ++-- eBook/14.12.md | 18 +++++++++--------- eBook/14.13.md | 10 +++++----- eBook/14.14.md | 7 +++++-- eBook/14.15.md | 46 +++++++++++++++++++++++++--------------------- eBook/14.16.md | 12 ++++++------ eBook/14.17.md | 8 +++++--- eBook/14.2.md | 30 +++++++++++++++--------------- eBook/14.3.md | 2 +- eBook/14.4.md | 6 +++--- eBook/14.5.md | 6 +++--- eBook/14.7.md | 22 +++++++++++----------- eBook/14.8.md | 14 +++++++------- eBook/14.9.md | 12 ++++++------ eBook/15.0.md | 4 ++-- eBook/15.1.md | 16 ++++++++-------- eBook/15.3.md | 20 ++++++++++---------- eBook/15.6.md | 2 +- eBook/15.8.md | 4 +++- eBook/15.9.md | 2 +- eBook/16.0.md | 22 +++++++++++----------- eBook/16.1.md | 12 ++++++------ eBook/16.10.md | 10 +++++----- eBook/16.2.md | 6 +++--- eBook/16.3.md | 8 ++++---- eBook/16.4.md | 8 ++++---- eBook/16.5.md | 8 ++++++-- eBook/16.6.md | 8 ++++---- eBook/16.7.md | 2 +- eBook/16.8.md | 2 +- eBook/16.9.md | 19 +++++++++---------- eBook/17.1.md | 16 ++++++++-------- eBook/18.1.md | 6 +++--- eBook/18.11.md | 6 +++--- eBook/18.2.md | 6 +++--- eBook/18.3.md | 8 ++++---- eBook/18.5.md | 4 ++-- eBook/18.6.md | 4 ++-- eBook/18.8.md | 12 ++++++------ eBook/18.9.md | 4 ++-- eBook/19.2.md | 4 ++-- eBook/19.6.md | 2 +- 100 files changed, 433 insertions(+), 414 deletions(-) diff --git a/eBook/04.2.md b/eBook/04.2.md index 68d97af..48dc136 100644 --- a/eBook/04.2.md +++ b/eBook/04.2.md @@ -97,7 +97,7 @@ import ("fmt"; "os") **可见性规则** -当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。 +当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是它们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。 (大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)。 @@ -105,7 +105,7 @@ import ("fmt"; "os") 假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:`pack1.Thing`(pack1 在这里是不可以省略的)。 -因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,例如 `pack1.Thing` 和 `pack2.Thing`。 +因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于它们的包名,例如 `pack1.Thing` 和 `pack2.Thing`。 你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:`import fm "fmt"`。下面的代码展示了如何使用包的别名: diff --git a/eBook/04.5.md b/eBook/04.5.md index 80a925b..82ffb45 100644 --- a/eBook/04.5.md +++ b/eBook/04.5.md @@ -40,9 +40,9 @@ aVar != 10 -> false Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface,第 11 章),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。 -布尔型的常量和变量也可以通过和逻辑运算符(非 `!`、和 `&&`、或 `||`)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。 +布尔型的常量和变量也可以通过和逻辑运算符(非 `!`、与 `&&`、或 `||`)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。 -逻辑值可以被用于条件结构中的条件语句(第 5 章),以便测试某个条件是否满足。另外,和 `&&`、或 `||` 与相等 `==` 或不等 `!=` 属于二元运算符,而非 `!` 属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。 +逻辑值可以被用于条件结构中的条件语句(第 5 章),以便测试某个条件是否满足。另外,与 `&&`、或 `||` 与相等 `==` 或不等 `!=` 属于二元运算符,而非 `!` 属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。 Go 语言中包含以下逻辑运算符: @@ -185,7 +185,7 @@ func main() { **格式化说明符** -在格式化字符串里,`%d` 用于格式化整数(`%x` 和 `%X` 用于格式化 16 进制表示的数字),`%g` 用于格式化浮点型(`%f` 输出浮点数,`%e` 输出科学计数表示法),`%0nd` 用于规定输出长度为n的整数,其中开头的数字 0 是必须的。 +在格式化字符串里,`%d` 用于格式化整数(`%x` 和 `%X` 用于格式化 16 进制表示的数字),`%g` 用于格式化浮点型(`%f` 输出浮点数,`%e` 输出科学计数表示法),`%0nd` 用于规定输出长度为 n 的整数,其中开头的数字 0 是必须的。 `%n.mg` 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 `%5.2e` 来输出 3.4 的结果为 `3.40e+00`。 @@ -228,7 +228,7 @@ Go 拥有以下复数类型: complex64 (32 位实数和虚数) complex128 (64 位实数和虚数) -复数使用 `re+imI` 来表示,其中 `re` 代表实数部分,`im` 代表虚数部分,I 代表根号负 1。 +复数使用 `re+imI` 来表示,其中 `re` 代表实数部分,`im` 代表虚数部分,`I` 代表根号负 1。 示例: @@ -369,7 +369,7 @@ b3 := 10 > 5 // b3 is true **练习 4.4** 尝试编译 [divby0.go](exercises/chapter_4/divby0.go)。 -你可以将语句 `b = b + a` 简写为 `b+=a`,同样的写法也可用于 `-=`、`*=`、`/=`、`%=`。 +你可以将语句 `b = b + a` 简写为 `b += a`,同样的写法也可用于 `-=`、`*=`、`/=`、`%=`。 对于整数和浮点数,你可以使用一元运算符 `++`(递增)和 `--`(递减),但只能用于后缀: diff --git a/eBook/06.11.md b/eBook/06.11.md index d5ef306..bebf53f 100644 --- a/eBook/06.11.md +++ b/eBook/06.11.md @@ -1,6 +1,6 @@ # 6.11 计算函数执行时间 -有时候,能够知道一个计算执行消耗的时间是非常有意义的,尤其是在对比和基准测试中。最简单的一个办法就是在计算开始之前设置一个起始时候,再由计算结束时的结束时间,最后取出它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 `time` 包中的 `Now()` 和 `Sub` 函数: +有时候,能够知道一个计算执行消耗的时间是非常有意义的,尤其是在对比和基准测试中。最简单的一个办法就是在计算开始之前设置一个起始时间,再记录计算结束时的结束时间,最后计算它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 `time` 包中的 `Now()` 和 `Sub` 函数: ```go start := time.Now() diff --git a/eBook/06.12.md b/eBook/06.12.md index ff47fba..d85a810 100644 --- a/eBook/06.12.md +++ b/eBook/06.12.md @@ -6,7 +6,7 @@ 而我们要做就是将第 n 个数的值存在数组中索引为 n 的位置(详见第 7 章),然后在数组中查找是否已经计算过,如果没有找到,则再进行计算。 -程序 Listing 6.17 - fibonacci_memoization.go 就是依照这个原则实现的,下面是计算到第 40 位数字的性能对比: +程序 Listing 6.17 - [fibonacci_memoization.go](examples/chapter_6/fibonacci_memoization.go) 就是依照这个原则实现的,下面是计算到第 40 位数字的性能对比: - 普通写法:4.730270 秒 - 内存缓存:0.001000 秒 diff --git a/eBook/06.2.md b/eBook/06.2.md index 369d7ed..5799660 100644 --- a/eBook/06.2.md +++ b/eBook/06.2.md @@ -84,7 +84,7 @@ func MultiPly3Nums(a int, b int, c int) int { `getX2AndX3` 与 `getX2AndX3_2` 两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 `()` 把它们括起来,比如 `(int, int)`。 -命名返回值作为结果形参(result parameters)被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的return语句。需要注意的是,即使只有一个命名返回值,也需要使用 `()` 括起来(参考 [第 6.6 节](06.6.md)的 fibonacci.go 函数)。 +命名返回值作为结果形参(result parameters)被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的 `return` 语句。需要注意的是,即使只有一个命名返回值,也需要使用 `()` 括起来(参考 [第 6.6 节](06.6.md)的 fibonacci.go 函数)。 示例 6.3 [multiple_return.go](examples/chapter_6/multiple_return.go) diff --git a/eBook/06.7.md b/eBook/06.7.md index 1491611..9c95369 100644 --- a/eBook/06.7.md +++ b/eBook/06.7.md @@ -1,6 +1,6 @@ # 6.7 将函数作为参数 -函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。下面是一个将函数作为参数的简单例子(function_parameter.go): +函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。下面是一个将函数作为参数的简单例子([function_parameter.go](examples/chapter_6/function_parameter.go)): ```go package main diff --git a/eBook/06.8.md b/eBook/06.8.md index e8c3c81..1f69a69 100644 --- a/eBook/06.8.md +++ b/eBook/06.8.md @@ -20,7 +20,7 @@ func() { 表示参数列表的第一对括号必须紧挨着关键字 `func`,因为匿名函数没有名称。花括号 `{}` 涵盖着函数体,最后的一对括号表示对该匿名函数的调用。 -下面的例子展示了如何将匿名函数赋值给变量并对其进行调用(function_literal.go): +下面的例子展示了如何将匿名函数赋值给变量并对其进行调用([function_literal.go](examples/chapter_6/function_literal.go)): ```go package main @@ -63,7 +63,7 @@ func (u string) { }(v) ``` -请学习以下示例并思考(return_defer.go):函数 `f` 返回时,变量 `ret` 的值是什么? +请学习以下示例并思考([return_defer.go](examples/chapter_6/return_defer.go)):函数 `f` 返回时,变量 `ret` 的值是什么? ```go package main diff --git a/eBook/06.9.md b/eBook/06.9.md index 1930f97..7c4403e 100644 --- a/eBook/06.9.md +++ b/eBook/06.9.md @@ -1,6 +1,6 @@ # 6.9 应用闭包:将函数作为返回值 -在程序 `function_return.go` 中我们将会看到函数 Add2 和 Adder 均会返回签名为 `func(b int) int` 的函数: +在程序 [function_return.go](examples/chapter_6/function_return.go) 中我们将会看到函数 Add2 和 Adder 均会返回签名为 `func(b int) int` 的函数: ```go func Add2() (func(b int) int) @@ -9,7 +9,7 @@ func Adder(a int) (func(b int) int) 函数 Add2 不接受任何参数,但函数 Adder 接受一个 int 类型的整数作为参数。 -我们也可以将 Adder 返回的函数存到变量中(function_return.go)。 +我们也可以将 Adder 返回的函数存到变量中([function_return.go](examples/chapter_6/function_return.go))。 ```go package main @@ -45,7 +45,7 @@ Call Add2 for 3 gives: 5 The result is: 5 ``` -下例为一个略微不同的实现(function_closure.go): +下例为一个略微不同的实现([function_closure.go](examples/chapter_6/function_closure.go)): ```go package main diff --git a/eBook/07.0.md b/eBook/07.0.md index 73a2de0..003a048 100644 --- a/eBook/07.0.md +++ b/eBook/07.0.md @@ -1,6 +1,6 @@ # 7.0 数组与切片 -这章我们开始剖析 **容器**, 它是可以包含大量条目(item)的数据结构, 例如数组、切片和 map。从这看到 Go 明显受到 Python 的影响。 +这章我们开始剖析 **容器**,它是可以包含大量条目(item)的数据结构,例如数组、切片和 map。从这看到 Go 明显受到 Python 的影响。 以 `[]` 符号标识的数组类型几乎在所有的编程语言中都是一个基本主力。Go 语言中的数组也是类似的,只是有一些特点。Go 没有 C 那么灵活,但是拥有切片(slice)类型。这是一种建立在 Go 语言数组类型之上的抽象,要想理解切片我们必须先理解数组。数组有特定的用处,但是却有一些呆板,所以在 Go 语言的代码里并不是特别常见。相对的,切片确实随处可见的。它们构建在数组之上并且提供更强大的能力和便捷。 diff --git a/eBook/07.1.md b/eBook/07.1.md index ac279e4..b59fae1 100644 --- a/eBook/07.1.md +++ b/eBook/07.1.md @@ -1,11 +1,11 @@ # 7.1 声明和初始化 ## 7.1.1 概念 -数组是具有相同 **唯一类型** 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的(如下)。 +数组是具有相同 **唯一类型** 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以 `[5]int` 和 `[10]int` 是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的(如下)。 **注意事项** 如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考 [第 11 章](11.9.md))。当使用值时我们必须先做一个类型判断(参考 [第 11 章](11.3.md))。 -数组元素可以通过 **索引**(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。(数组以 0 开始在所有类 C 语言中是相似的)。元素的数目,也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。 +数组元素可以通过 **索引**(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。(数组以 0 开始在所有类 C 语言中是相似的)。元素的数目(也称为长度或者数组大小)必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2GB。 声明的格式是: @@ -33,7 +33,7 @@ arr1 的长度是 5,索引范围从 0 到 `len(arr1)-1`。 runtime error: index out of range -由于索引的存在,遍历数组的方法自然就是使用 for 结构: +由于索引的存在,遍历数组的方法自然就是使用 for 结构: - 通过 for 初始化数组项 - 通过 for 打印数组元素 @@ -99,7 +99,7 @@ for i := range a { Go 语言中的数组是一种 **值类型**(不像 C/C++ 中是指向首元素的指针),所以可以通过 `new()` 来创建: `var arr1 = new([5]int)`。 -那么这种方式和 `var arr2 [5]int` 的区别是什么呢?arr1 的类型是 `*[5]int`,而 arr2的类型是 `[5]int`。 +那么这种方式和 `var arr2 [5]int` 的区别是什么呢?arr1 的类型是 `*[5]int`,而 arr2 的类型是 `[5]int`。 这样的结果就是当把一个数组赋值给另一个时,需要再做一次数组内存的拷贝操作。例如: @@ -181,7 +181,7 @@ var arrAge = [5]int{18, 20, 15, 22, 16} var arrLazy = [...]int{5, 6, 7, 8, 22} ``` -`...` 可同样可以忽略,从技术上说它们其实变化成了切片。 +`...` 同样可以忽略,从技术上说它们其实变成了切片。 第三种变化:`key: value 语法` @@ -258,7 +258,7 @@ func main() { ## 7.1.4 将数组传递给函数 -把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象: +把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种情况: - 传递数组的指针 - 使用数组的切片 diff --git a/eBook/07.2.md b/eBook/07.2.md index 0b994a0..738bd90 100644 --- a/eBook/07.2.md +++ b/eBook/07.2.md @@ -8,11 +8,11 @@ 给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个 **长度可变的数组**。 -切片提供了计算容量的函数 `cap()` 可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度。如果 s 是一个切片,`cap(s)` 就是从 `s[0]` 到数组末尾的数组长度。切片的长度永远不会超过它的容量,所以对于 切片 s 来说该不等式永远成立:`0 <= len(s) <= cap(s)`。 +切片提供了计算容量的函数 `cap()` 可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度。如果 s 是一个切片,`cap(s)` 就是从 `s[0]` 到数组末尾的数组长度。切片的长度永远不会超过它的容量,所以对于切片 s 来说该不等式永远成立:`0 <= len(s) <= cap(s)`。 多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。 -**优点** 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常用。 +**优点** 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中切片比数组更常用。 声明切片的格式是: `var identifier []type`(不需要说明长度)。 @@ -20,7 +20,7 @@ 切片的初始化格式是:`var slice1 []type = arr1[start:end]`。 -这表示 slice1 是由数组 arr1 从 start 索引到 `end-1` 索引之间的元素构成的子集(切分数组,start:end 被称为 slice 表达式)。所以 `slice1[0]` 就等于 `arr1[start]`。这可以在 arr1 被填充前就定义好。 +这表示 slice1 是由数组 arr1 从 `start` 索引到 `end-1` 索引之间的元素构成的子集(切分数组,start:end 被称为 slice 表达式)。所以 `slice1[0]` 就等于 `arr1[start]`。这可以在 arr1 被填充前就定义好。 如果某个人写:`var slice1 []type = arr1[:]` 那么 slice1 就等于完整的 arr1 数组(所以这种表示方式是 `arr1[0:len(arr1)]` 的一种缩写)。另外一种表述方式是:`slice1 = &arr1`。 @@ -30,7 +30,7 @@ 如果你想去掉 slice1 的最后一个元素,只要 `slice1 = slice1[:len(slice1)-1]`。 -一个由数字 1、2、3 组成的切片可以这么生成:`s := [3]int{1,2,3}[:]`(注: 应先用`s := [3]int{1, 2, 3}`生成数组, 再使用`s[:]`转成切片) 甚至更简单的 `s := []int{1,2,3}`。 +一个由数字 1、2、3 组成的切片可以这么生成:`s := [3]int{1,2,3}[:]`(注:应先用`s := [3]int{1, 2, 3}`生成数组, 再使用`s[:]`转成切片) 甚至更简单的 `s := []int{1,2,3}`。 `s2 := s[:]` 是用切片组成的切片,拥有相同的元素,但是仍然指向相同的相关数组。 @@ -43,7 +43,7 @@ 切片也可以用类似数组的方式初始化:`var x = []int{2, 3, 5, 7, 11}`。这样就创建了一个长度为 5 的数组并且创建了一个相关切片。 -切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。下图给出了一个长度为 2,容量为 4 的切片y。 +切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。下图给出了一个长度为 2,容量为 4 的切片 y。 - `y[0] = 3` 且 `y[1] = 5`。 - 切片 `y[0:4]` 由 元素 3,5,7 和 11 组成。 @@ -104,13 +104,13 @@ func main() { 如果 s2 是一个 slice,你可以将 s2 向后移动一位 `s2 = s2[1:]`,但是末尾没有移动。切片只能向后移动,`s2 = s2[-1:]` 会导致编译错误。切片不能被重新分片以获取数组的前一个元素。 -**注意** 绝对不要用指针指向 slice。切片本身已经是一个引用类型,所以它本身就是一个指针!! +**注意** 绝对不要用指针指向 slice。切片本身已经是一个引用类型,所以它本身就是一个指针!! 问题 7.2: 给定切片 `b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'}`,那么 `b[1:4]`、`b[:2]`、`b[2:]` 和 `b[:]` 分别是什么? ## 7.2.2 将切片传递给函数 -如果你有一个函数需要对数组做操作,你可能总是需要把参数声明为切片。当你调用该函数时,把数组分片,创建为一个 切片引用并传递给该函数。这里有一个计算数组元素和的方法: +如果你有一个函数需要对数组做操作,你可能总是需要把参数声明为切片。当你调用该函数时,把数组分片,创建为一个切片引用并传递给该函数。这里有一个计算数组元素和的方法: ```go func sum(a []int) int { @@ -129,7 +129,7 @@ func main() { ## 7.2.3 用 make() 创建一个切片 -当相关数组还没有定义时,我们可以使用 make() 函数来创建一个切片 同时创建好相关数组:`var slice1 []type = make([]type, len)`。 +当相关数组还没有定义时,我们可以使用 make() 函数来创建一个切片,同时创建好相关数组:`var slice1 []type = make([]type, len)`。 也可以简写为 `slice1 := make([]type, len)`,这里 `len` 是数组的长度并且也是 `slice` 的初始长度。 @@ -188,7 +188,7 @@ func main() { The length of slice1 is 10 The capacity of slice1 is 10 -因为字符串是纯粹不可变的字节数组,它们也可以被切分成 切片。 +因为字符串是纯粹不可变的字节数组,它们也可以被切分成切片。 练习 7.4: fobinacci_funcarray.go: 为练习 7.3 写一个新的版本,主函数调用一个使用序列个数作为参数的函数,该函数返回一个大小为序列个数的 Fibonacci 切片。 @@ -196,8 +196,8 @@ func main() { 看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。 -- new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为\*T的内存地址:这种方法 **返回一个指向类型为 T,值为 0 的地址的指针**,它适用于值类型如数组和结构体(参见第 10 章);它相当于 `&T{}`。 -- make(T) **返回一个类型为 T 的初始值**,它只适用于3种内建的引用类型:切片、map 和 channel(参见第 8 章,第 13 章)。 +- new(T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 \*T 的内存地址:这种方法 **返回一个指向类型为 T,值为 0 的地址的指针**,它适用于值类型如数组和结构体(参见第 10 章);它相当于 `&T{}`。 +- make(T) **返回一个类型为 T 的初始值**,它只适用于 3 种内建的引用类型:切片、map 和 channel(参见第 8 章,第 13 章)。 换言之,new 函数分配内存,make 函数初始化;下图给出了区别: @@ -224,26 +224,26 @@ var v []int = make([]int, 10, 50) v := make([]int, 10, 50) ``` -这样分配一个有 50 个 int 值的数组,并且创建了一个长度为 10,容量为 50 的 切片 v,该 切片 指向数组的前 10 个元素。 +这样分配一个有 50 个 int 值的数组,并且创建了一个长度为 10,容量为 50 的 切片 v,该切片指向数组的前 10 个元素。 **问题 7.3** 给定 `s := make([]byte, 5)`,len(s) 和 cap(s) 分别是多少?`s = s[2:4]`,len(s) 和 cap(s) 又分别是多少? **问题 7.4** 假设 `s1 := []byte{'p', 'o', 'e', 'm'}` 且 `s2 := s1[2:]`,s2 的值是多少?如果我们执行 `s2[1] = 't'`,s1 和 s2 现在的值又分别是多少? -*译者注:如何理解new、make、slice、map、channel的关系* +*译者注:如何理解 new、make、slice、map、channel 的关系* -*1.slice、map以及channel都是golang内建的一种引用类型,三者在内存中存在多个组成部分, -需要对内存组成部分初始化后才能使用,而make就是对三者进行初始化的一种操作方式* +*1.slice、map 以及 channel 都是 golang 内建的一种引用类型,三者在内存中存在多个组成部分, +需要对内存组成部分初始化后才能使用,而 make 就是对三者进行初始化的一种操作方式* *2. new 获取的是存储指定变量内存地址的一个变量,对于变量内部结构并不会执行相应的初始化操作, -所以slice、map、channel需要make进行初始化并获取对应的内存地址,而非new简单的获取内存地址* +所以 slice、map、channel 需要 make 进行初始化并获取对应的内存地址,而非 new 简单的获取内存地址* -## 7.2.5 多维 切片 +## 7.2.5 多维切片 和数组一样,切片通常也是一维的,但是也可以由一维组合成高维。通过分片的分片(或者切片的数组),长度可以任意动态变化,所以 Go 语言的多维切片可以任意切分。而且,内层的切片必须单独分配(通过 make 函数)。 ## 7.2.6 bytes 包 -类型 `[]byte` 的切片十分常见,Go 语言有一个 bytes 包专门用来解决这种类型的操作方法。 +类型 `[]byte` 的切片十分常见,Go 语言有一个 bytes 包专门用来提供这种类型的操作方法。 bytes 包和字符串包十分类似(参见第 4.7 节)。而且它还包含一个十分有用的类型 Buffer: diff --git a/eBook/07.3.md b/eBook/07.3.md index 43a3ab5..cc07529 100644 --- a/eBook/07.3.md +++ b/eBook/07.3.md @@ -89,7 +89,7 @@ b) 如果 a) 无法正常工作,写一个 for 循环让值可以 double。 **问题 7.6** 通过使用省略号操作符 `...` 来实现累加方法。 -**练习 7.7** sum_array.go +**练习 7.7** [sum_array.go](exercises/chapter_7/sum_array.go) a) 写一个 Sum 函数,传入参数为一个 32 位 float 数组成的数组 arrF,返回该数组的所有数字和。 @@ -97,7 +97,7 @@ a) 写一个 Sum 函数,传入参数为一个 32 位 float 数组成的数组 b) 写一个 SumAndAverage 方法,返回两个 int 和 float32 类型的未命名变量的和与平均值。 -**练习 7.8** min_max.go +**练习 7.8** [min_max.go](exercises/chapter_7/min_max.go) 写一个 minSlice 方法,传入一个 int 的切片并且返回最小值,再写一个 maxSlice 方法返回最大值。 diff --git a/eBook/07.5.md b/eBook/07.5.md index 9418b23..c581a08 100644 --- a/eBook/07.5.md +++ b/eBook/07.5.md @@ -22,7 +22,7 @@ func main() { } ``` -`func append(s[]T, x ...T) []T` 其中 append 方法将 0 个或多个具有相同类型 s 的元素追加到切片后面并且返回新的切片;追加的元素必须和原切片的元素同类型。如果 s 的容量不足以存储新增元素,append 会分配新的切片来保证已有切片元素和新增元素的存储。因此,返回的切片可能已经指向一个不同的相关数组了。append 方法总是返回成功,除非系统内存耗尽了。 +`func append(s[]T, x ...T) []T` 其中 append 方法将 0 个或多个具有相同类型 s 的元素追加到切片后面并且返回新的切片;追加的元素必须和原切片的元素是同类型。如果 s 的容量不足以存储新增元素,append 会分配新的切片来保证已有切片元素和新增元素的存储。因此,返回的切片可能已经指向一个不同的相关数组了。append 方法总是返回成功,除非系统内存耗尽了。 如果你想将切片 y 追加到切片 x 后面,只要将第二个参数扩展成一个列表即可:`x = append(x, y...)`。 @@ -60,7 +60,7 @@ func AppendByte(slice []byte, data ...byte) []byte { **练习 7.12** -写一个函数 RemoveStringSlice 将从 start 到 end 索引的元素从切片 中移除。 +写一个函数 RemoveStringSlice 将从 start 到 end 索引的元素从切片中移除。 ## 链接 diff --git a/eBook/07.6.md b/eBook/07.6.md index 2151b83..523b396 100644 --- a/eBook/07.6.md +++ b/eBook/07.6.md @@ -2,9 +2,9 @@ ## 7.6.1 从字符串生成字节切片 -假设 s 是一个字符串(本质上是一个字节数组),那么就可以直接通过 `c := []byte(s)` 来获取一个字节的切片 c。另外,您还可以通过 copy 函数来达到相同的目的:`copy(dst []byte, src string)`。 +假设 s 是一个字符串(本质上是一个字节数组),那么就可以直接通过 `c := []byte(s)` 来获取一个字节的切片 c 。另外,您还可以通过 copy 函数来达到相同的目的:`copy(dst []byte, src string)`。 -同样的,还可以使用 for-range 来获得每个元素(Listing 7.13—for_string.go): +同样的,还可以使用 for-range 来获得每个元素(Listing 7.13 — [for_string.go](examples/chapter_7/for_string.go)): ```go package main @@ -37,7 +37,7 @@ b = append(b, s...) ## 7.6.2 获取字符串的某一部分 -使用 `substr := str[start:end]` 可以从字符串 str 获取到从索引 start 开始到 `end-1` 位置的子字符串。同样的,`str[start:]` 则表示获取从 start 开始到 `len(str)-1` 位置的子字符串。而 `str[:end]` 表示获取从 0 开始到 `end-1` 的子字符串。 +使用 `substr := str[start:end]` 可以从字符串 str 获取到从索引 `start` 开始到 `end-1` 位置的子字符串。同样的,`str[start:]` 则表示获取从 `start` 开始到 `len(str)-1` 位置的子字符串。而 `str[:end]` 表示获取从 0 开始到 `end-1` 的子字符串。 ## 7.6.3 字符串和切片的内存结构 diff --git a/eBook/08.1.md b/eBook/08.1.md index e503d69..b72f675 100644 --- a/eBook/08.1.md +++ b/eBook/08.1.md @@ -23,7 +23,7 @@ map 传递给函数的代价很小:在 32 位机器上占 4 个字节,64 位 map 也可以用函数作为自己的值,这样就可以用来做分支结构(详见第 5 章):key 用来选择要执行的函数。 -如果 key1 是 map1 的key,那么 `map1[key1]` 就是对应 key1 的值,就如同数组索引符号一样(数组可以视为一种简单形式的 map,key 是从 0 开始的整数)。 +如果 key1 是 map1 的 key,那么 `map1[key1]` 就是对应 key1 的值,就如同数组索引符号一样(数组可以视为一种简单形式的 map,key 是从 0 开始的整数)。 key1 对应的值可以通过赋值符号来设置为 val1:`map1[key1] = val1`。 @@ -80,7 +80,7 @@ mapAssigned 也是 mapLit 的引用,对 mapAssigned 的修改也会影响到 m **不要使用 new,永远用 make 来构造 map** -**注意** 如果你错误的使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址: +**注意** 如果你错误地使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址: ```go mapCreated := new(map[string]float32) @@ -130,7 +130,7 @@ noteFrequency := map[string]float32 { ## 8.1.3 用切片作为 map 的值 -既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理unix机器上的所有进程,以父进程(pid 为整型)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 `[]int` 类型或者其他类型的切片,就可以优雅的解决这个问题。 +既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理unix机器上的所有进程,以父进程(pid 为整型)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 `[]int` 类型或者其他类型的切片,就可以优雅地解决这个问题。 这里有一些定义这种 map 的例子: diff --git a/eBook/08.3.md b/eBook/08.3.md index d4025cb..1d5842e 100644 --- a/eBook/08.3.md +++ b/eBook/08.3.md @@ -8,7 +8,7 @@ for key, value := range map1 { } ``` -第一个返回值 key 是 map 中的 key 值,第二个返回值则是该 key 对应的 value 值;这两个都是仅 for 循环内部可见的局部变量。其中第一个返回值key值是一个可选元素。如果你只关心值,可以这么使用: +第一个返回值 key 是 map 中的 key 值,第二个返回值则是该 key 对应的 value 值;这两个都是仅 for 循环内部可见的局部变量。其中第一个返回值 key 值是一个可选元素。如果你只关心值,可以这么使用: ```go for _, value := range map1 { diff --git a/eBook/08.4.md b/eBook/08.4.md index e4e9bba..7e790b6 100644 --- a/eBook/08.4.md +++ b/eBook/08.4.md @@ -1,6 +1,6 @@ # 8.4 map 类型的切片 -假设我们想获取一个 map 类型的切片,我们必须使用两次 `make()` 函数,第一次分配切片,第二次分配 切片中每个 map 元素(参见下面的例子 8.4)。 +假设我们想获取一个 map 类型的切片,我们必须使用两次 `make()` 函数,第一次分配切片,第二次分配切片中每个 map 元素(参见下面的例子 8.4)。 示例 8.4 [maps_forrange2.go](examples/chapter_8/maps_forrange2.go): diff --git a/eBook/08.5.md b/eBook/08.5.md index 42847ad..ca8fe29 100644 --- a/eBook/08.5.md +++ b/eBook/08.5.md @@ -50,7 +50,7 @@ func main() { sorted: Key: alpha, Value: 34 / Key: bravo, Value: 56 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: echo, Value: 56 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: kili, Value: 43 / Key: lima, Value: 98 / -但是如果你想要一个排序的列表你最好使用结构体切片,这样会更有效: +但是如果你想要一个排序的列表,那么最好使用结构体切片,这样会更有效: ```go type name struct { diff --git a/eBook/09.1.md b/eBook/09.1.md index 542beb8..46724af 100644 --- a/eBook/09.1.md +++ b/eBook/09.1.md @@ -6,7 +6,7 @@ - `unsafe`: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。 - `syscall`-`os`-`os/exec`: - - `os`: 提供给我们一个平台无关性的操作系统功能接口,采用类Unix设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。 + - `os`: 提供给我们一个平台无关性的操作系统功能接口,采用类 Unix 设计,隐藏了不同操作系统间的差异,让不同的文件系统和操作系统对象表现一致。 - `os/exec`: 提供我们运行外部操作系统命令和程序的方式。 - `syscall`: 底层的外部包,提供了操作系统底层调用的基本接口。 @@ -32,7 +32,7 @@ func main() { } ``` -- `archive/tar` 和 `/zip-compress`:压缩(解压缩)文件功能。 +- `archive/tar` 和 `/zip-compress`:压缩(解压缩)文件功能。 - `fmt`-`io`-`bufio`-`path/filepath`-`flag`: - `fmt`: 提供了格式化输入输出功能。 - `io`: 提供了基本输入输出功能,大多数是围绕系统功能的封装。 @@ -66,10 +66,10 @@ for e := l.Front(); e != nil; e = e.Next() { - `time`-`log`: - `time`: 日期和时间的基本操作。 - - `log`: 记录程序运行时产生的日志,我们将在后面的章节使用它。 + - `log`: 记录程序运行时产生的日志,我们将在后面的章节使用它。 - `encoding/json`-`encoding/xml`-`text/template`: - `encoding/json`: 读取并解码和写入并编码 JSON 数据。 - - `encoding/xml`:简单的 XML1.0 解析器,有关 JSON 和 XML 的实例请查阅第 12.9/10 章节。 + - `encoding/xml`: 简单的 XML1.0 解析器,有关 JSON 和 XML 的实例请查阅第 12.9/10 章节。 - `text/template`:生成像 HTML 一样的数据与文本混合的数据驱动模板(参见第 15.7 节)。 - `net`-`net/http`-`html`:(参见第 15 章) - `net`: 网络数据的基本操作。 diff --git a/eBook/09.11.md b/eBook/09.11.md index 578b1ea..3dd6fdf 100644 --- a/eBook/09.11.md +++ b/eBook/09.11.md @@ -1,12 +1,12 @@ # 9.11 在 Go 程序中使用外部库 -(本节我们将创建一个 Web 应用和它的 Google App Engine 版本,在第 19 和 21 章分别说明,当你阅读到这些章节时可以再回到这个例子。) +(本节我们将创建一个 Web 应用和它的 Google App Engine 版本,在第 19 和 21 章分别说明,当你阅读到这些章节时可以再回到这个例子。) 当开始一个新项目或增加新的功能到现有的项目,你可以通过在应用程序中使用已经存在的库来节省开发时间。为了做到这一点,你必须理解库的 API(应用编程接口),那就是:库中有哪些方法可以调用,如何调用。你可能没有这个库的源代码,但作者肯定有记载的 API 以及详细介绍了如何使用它。 -作为一个例子,我们将使用谷歌的 API 的 urlshortener 编写一个小程序:你可以尝试一下在 http://goo.gl/ 输入一个像 "http://www.destandaard.be" 这样的URL,你会看到一个像 "http://goo.gl/O9SUO" 这样更短的 URL 返回,也就是说,在 Twitter 之类的服务中这是非常容易嵌入的。谷歌 urlshortener 服务的文档可以在 "http://code.google.com/apis/urlshortener/" 找到。(第 19 章,我们将开发自己版本的 urlshortener)。 +作为一个例子,我们将使用谷歌的 API 的 urlshortener 编写一个小程序:你可以尝试一下在 http://goo.gl/ 输入一个像 "http://www.destandaard.be" 这样的 URL,你会看到一个像 "http://goo.gl/O9SUO" 这样更短的 URL 返回,也就是说,在 Twitter 之类的服务中这是非常容易嵌入的。谷歌 urlshortener 服务的文档可以在 "http://code.google.com/apis/urlshortener/" 找到。(第 19 章,我们将开发自己版本的 urlshortener)。 -谷歌将这项技术提供给其他开发者,作为 API 我们可以在我们自己的应用程序中调用(释放到指定的限制)。他们也生成了一个 Go 语言客户端库使其变得更容易。 +谷歌将这项技术提供给其他开发者,我们可以在我们自己的应用程序中调用 API (释放到指定的限制)。他们也生成了一个 Go 语言客户端库使调用变得更容易。 备注:谷歌让通过使用 Google API Go 客户端服务的开发者生活变得更简单,Go 客户端程序自动生成于 Google 库的 JSON 描述。更多详情在 [项目页面](http://code.google.com/p/google-api-go-client/) 查看。 @@ -24,7 +24,7 @@ go install 将下载源码,编译并安装包 import "google.golang.org/api/urlshortener/v1" -现在我们写一个 Web 应用(参见第 15 章 4-8 节)通过表单实现短地址和长地址的相互转换。我们将使用 `template` 包并写三个处理函数:root 函数通过执行表单模板来展示表单。short 函数将长地址转换为短地址,long 函数逆向转换。 +现在我们写一个 Web 应用(参见第 15 章 4-8 节)通过表单实现短地址和长地址的相互转换。我们将使用 `template` 包并写三个处理函数:root 函数通过执行表单模板来展示表单,short 函数将长地址转换为短地址,long 函数逆向转换。 要调用 urlshortener 接口必须先通过 http 包中的默认客户端创建一个服务实例 urlshortenerSvc: ```go @@ -39,7 +39,7 @@ url, _ := urlshortenerSvc.Url.Insert(&urlshortener.Url{LongUrl: longUrl}).Do() 返回 `url` 的 `Id` 便是我们需要的短地址。 -我们通过调用服务中的 `Url.Get` 中的 `Do` 方法传入包含短地址的Url数据结构从而获取长地址: +我们通过调用服务中的 `Url.Get` 中的 `Do` 方法传入包含短地址的 Url 数据结构从而获取长地址: ```go url, error := urlshortenerSvc.Url.Get(shwortUrl).Do() @@ -138,7 +138,7 @@ handlers: script: _go_app ``` -现在你可以去到你的项目目录并在终端运行:`dev_appserver.py urlshort` +现在你可以到你的项目目录并在终端运行:`dev_appserver.py urlshort` 在浏览器打开你的 Web应用:http://localhost:8080。 diff --git a/eBook/09.2.md b/eBook/09.2.md index a903c1d..bc50237 100644 --- a/eBook/09.2.md +++ b/eBook/09.2.md @@ -10,7 +10,7 @@ ok, _ := regexp.Match(pat, []byte(searchIn)) ``` -变量 ok 将返回 true 或者 false,我们也可以使用 `MatchString`: +变量 ok 将返回 true 或者 false,我们也可以使用 `MatchString`: ```go ok, _ := regexp.MatchString(pat, searchIn) diff --git a/eBook/09.3.md b/eBook/09.3.md index b207c33..93a773c 100644 --- a/eBook/09.3.md +++ b/eBook/09.3.md @@ -1,10 +1,10 @@ # 9.3 锁和 sync 包 -在一些复杂的程序中,通常通过不同线程执行不同应用来实现程序的并发。当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序!(这通常被称为资源竞争,指不同线程对同一变量使用的竞争)显然这无法让人容忍,那我们该如何解决这个问题呢? +在一些复杂的程序中,通常通过不同线程执行不同应用来实现程序的并发。当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序!(这通常被称为资源竞争,指不同线程对同一变量使用的竞争)显然这无法让人容忍,那我们该如何解决这个问题呢? -经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。 +经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。 -特别是我们之前章节学习的 map 类型是不存在锁的机制来实现这种效果(出于对性能的考虑),所以 map 类型是非线程安全的。当并行访问一个共享的 map 类型的数据,map 数据将会出错。 +特别是我们之前章节学习的 map 类型是不存在锁的机制来实现这种效果(出于对性能的考虑),所以 map 类型是非线程安全的。当并行访问一个共享的 map 类型的数据,map 数据将会出错。 在 Go 语言中这种锁的机制是通过 sync 包中 Mutex 来实现的。sync 来源于 "synchronized" 一词,这意味着线程将有序的对同一变量进行访问。 @@ -42,7 +42,7 @@ type SyncedBuffer struct { } ``` -在 sync 包中还有一个 `RWMutex` 锁:他能通过 `RLock()` 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 `Lock()` 将和普通的 `Mutex` 作用相同。包中还有一个方便的 `Once` 类型变量的方法 `once.Do(call)`,这个方法确保被调用函数只能被调用一次。 +在 sync 包中还有一个 `RWMutex` 锁:它能通过 `RLock()` 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 `Lock()` 将和普通的 `Mutex` 作用相同。包中还有一个方便的 `Once` 类型变量的方法 `once.Do(call)`,这个方法确保被调用函数只能被调用一次。 相对简单的情况下,通过使用 sync 包可以解决同一时间只能一个线程访问变量或 map 类型数据的问题。如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题,这是在 Go 语言中所提倡用来实现并发的技术。我们将在第 14 章对其深入了解,并在第 14.7 节中对这两种方式进行比较。 diff --git a/eBook/09.5.md b/eBook/09.5.md index 1c63d96..4b845eb 100644 --- a/eBook/09.5.md +++ b/eBook/09.5.md @@ -2,9 +2,9 @@ 包是 Go 语言中代码组织和代码编译的主要方式。关于它们的很多基本信息已经在 4.2 章节中给出,最引人注目的便是可见性。现在我们来看看具体如何来使用自己写的包。在下一节,我们将回顾一些标准库中的包,自定义的包和标准库以外的包。 -当写自己包的时候,要使用短小的不含有 `_`(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。 +当写自己包的时候,要使用短小的不含有 `_`(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。 -当前目录下(examples/chapter_9/book/)有一个名为 package_mytest.go 的程序, 它使用了自定义包 pack1 中 pack1.go 的代码。这段程序(连同编译链接生成的 pack1.a)存放在当前目录下一个名为 pack1 的文件夹下。所以链接器将包的对象和主程序对象链接在一起。 +当前目录下(examples/chapter_9/book/)有一个名为 package_mytest.go 的程序, 它使用了自定义包 pack1 中 pack1.go 的代码。这段程序(连同编译链接生成的 pack1.a)存放在当前目录下一个名为 pack1 的文件夹下。所以链接器将包的对象和主程序对象链接在一起。 示例 9.4 [pack1.go](examples/chapter_9/book/pack1/pack1.go): @@ -74,13 +74,13 @@ fmt.Printf("Float from package1: %f\n", pack1.pack1Float) 主程序利用的包必须在主程序编写之前被编译。主程序中每个 pack1 项目都要通过包名来使用:`pack1.Item`。具体使用方法请参见示例 4.6 和 4.7。 -因此,按照惯例,子目录和包之间有着密切的联系:为了区分,不同包存放在不同的目录下,每个包(所有属于这个包中的 go 文件)都存放在和包名相同的子目录下: +因此,按照惯例,子目录和包之间有着密切的联系:为了区分,不同包存放在不同的目录下,每个包(所有属于这个包中的 go 文件)都存放在和包名相同的子目录下: Import with `.` : import . "./pack1" -当使用`.`来做为包的别名时,你可以不通过包名来使用其中的项目。例如:`test := ReturnStr()`。 +当使用 `.` 作为包的别名时,你可以不通过包名来使用其中的项目。例如:`test := ReturnStr()`。 在当前的命名空间导入 pack1 包,一般是为了具有更好的测试效果。 @@ -88,7 +88,7 @@ Import with `_` : import _ "./pack1/pack1" -pack1包只导入其副作用,也就是说,只执行它的init函数并初始化其中的全局变量。 +pack1 包只导入其副作用,也就是说,只执行它的 init 函数并初始化其中的全局变量。 **导入外部安装包:** @@ -133,11 +133,11 @@ init 函数是不能被调用的。 通过 `chmod 777 ./Makefile`确保它的可执行性。 -上面脚本内的include语引入了相应的功能,将自动检测机器的架构并调用正确的编译器和链接器。 +上面脚本内的 include 语句引入了相应的功能,将自动检测机器的架构并调用正确的编译器和链接器。 然后终端执行 make 或 `gomake` 工具:他们都会生成一个包含静态库 pack1.a 的 _obj 目录。 -go install(参见第 9.7 节,从 Go1 的首选方式)同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 `import "pack1"` 代替 `import "path to pack1"`,这样只通过名字就可以将包在程序中导入。 +go install(参见第 9.7 节,从 Go1 的首选方式)同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 `import "pack1"` 代替 `import "path to pack1"`,这样只通过名字就可以将包在程序中导入。 当第 13 章我们遇到使用测试工具进行测试的时候我们将重新回到自己的包的制作和编译这个话题。 @@ -153,7 +153,7 @@ b)一个源文件是否能包含多个包? 在同一个包中创建一个 `IsAM` 函数返回一个布尔值用来判断当前时间是 AM 还是 PM,同样创建 `IsAfternoon` 和 `IsEvening` 函数。 -使用 main_greetings 作出合适的问候(提示:使用 time 包)。 +使用 main_greetings 作出合适的问候(提示:使用 time 包)。 **练习 9.4** 创建一个程序 main_oddven.go 判断前 100 个整数是不是偶数,将判断所用的函数编写在 even 包里。 diff --git a/eBook/09.6.md b/eBook/09.6.md index 8f27f01..357f743 100644 --- a/eBook/09.6.md +++ b/eBook/09.6.md @@ -1,6 +1,6 @@ # 9.6 为自定义包使用 godoc -godoc工具(第 3.6 节)在显示自定义包中的注释也有很好的效果:注释必须以 `//` 开始并无空行放在声明(包,类型,函数)前。godoc 会为每个文件生成一系列的网页。 +godoc 工具(第 3.6 节)在显示自定义包中的注释也有很好的效果:注释必须以 `//` 开始并无空行放在声明(包,类型,函数)前。godoc 会为每个文件生成一系列的网页。 例如: diff --git a/eBook/09.7.md b/eBook/09.7.md index ac36c53..9cd717f 100644 --- a/eBook/09.7.md +++ b/eBook/09.7.md @@ -4,9 +4,9 @@ go install 是 Go 中自动包安装工具:如需要将包安装到本地它 在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。 -go install 使用了 GOPATH 变量(详见第 2.2 节)。 +go install 使用了 GOPATH 变量(详见第 2.2 节)。 -远端包(详见第 9.5 节): +远端包(详见第 9.5 节): 假设我们要安装一个有趣的包 tideland(它包含了许多帮助示例,参见 [项目主页](http://code.google.com/p/tideland-cgl))。 diff --git a/eBook/09.8.md b/eBook/09.8.md index 85177b6..ccc1613 100644 --- a/eBook/09.8.md +++ b/eBook/09.8.md @@ -4,16 +4,16 @@ ## 9.8.1 自定义包的目录结构 -下面的结构给了你一个好的示范(uc 代表通用包名, 名字为粗体的代表目录,斜体代表可执行文件): +下面的结构给了你一个好的示范(uc 代表通用包名, 名字为粗体的代表目录,斜体代表可执行文件): /home/user/goprograms - ucmain.go (uc包主程序) - Makefile (ucmain的makefile) + ucmain.go (uc 包主程序) + Makefile (ucmain 的 makefile) ucmain - src/uc (包含uc包的go源码) + src/uc (包含 uc 包的 go 源码) uc.go uc_test.go - Makefile (包的makefile) + Makefile (包的 makefile) uc.a _obj uc.a @@ -90,7 +90,7 @@ include $(GOROOT)/src/Make.pkg 在第 13.8 节我们将给出另外一个测试例子并进行深入研究。 -备注:有可能你当前的用户不具有足够的资格使用 go install(没有权限)。这种情况下,选择 root 用户 su。确保 Go 环境变量和 Go 源码路径也设置给 su,同样也适用你的普通用户(详见第 2.3 节)。 +备注:有可能你当前的用户不具有足够的资格使用 go install(没有权限)。这种情况下,选择 root 用户 su。确保 Go 环境变量和 Go 源码路径也设置给 su,同样也适用你的普通用户(详见第 2.3 节)。 接下来我们创建主程序 ucmain.go: @@ -122,7 +122,7 @@ GOFILES=\ include $(GOROOT)/src/Make.cmd ``` -执行 gomake 编译 `ucmain.go` 生成可执行文件ucmain +执行 gomake 编译 `ucmain.go` 生成可执行文件 ucmain 运行 `./ucmain` 显示: `USING PACKAGE UC!`。 @@ -130,11 +130,11 @@ include $(GOROOT)/src/Make.cmd 本地包在用户目录下,使用给出的目录结构,以下命令用来从源码安装本地包: - go install /home/user/goprograms/src/uc # 编译安装uc + go install /home/user/goprograms/src/uc # 编译安装 uc cd /home/user/goprograms/uc - go install ./uc # 编译安装uc(和之前的指令一样) + go install ./uc # 编译安装 uc(和之前的指令一样) cd .. - go install . # 编译安装ucmain + go install . # 编译安装 ucmain 安装到 `$GOPATH` 下: diff --git a/eBook/09.9.md b/eBook/09.9.md index 76afbb7..eec2cc1 100644 --- a/eBook/09.9.md +++ b/eBook/09.9.md @@ -21,7 +21,7 @@ 如果您还没有账号,可以去注册一个开源项目的免费帐号。输入正确的帐号密码和有效的邮箱地址并进一步创建用户。然后你将获得一个 Git 命令的列表。本地仓库的操作命令已经完成。一个优秀的系统在你遇到任何问题的时候将 [引导你](http://help.github.com/)。 -在云端创建一个新的 uc 仓库;发布的指令为(`NNNN` 替代用户名): +在云端创建一个新的 uc 仓库;发布的指令为(`NNNN` 替代用户名): ``` git remote add origin git@github.com:NNNN/uc.git @@ -49,7 +49,7 @@ Gomake(和 go install)将通过 `$GOPATH` 下的本地版本进行工作。 - Google Code(hg/Git/svn) - Launchpad(bzr) -版本控制系统可以选择你熟悉的或者本地使用的代码版本控制。Go 核心代码的仓库是使用 Mercurial(hg) 来控制的,所以它是一个最可能保证你可以得到开发者项目中最好的软件。Git 也很出名,同样也适用。如果你从未使用过版本控制,这些网站有一些很好的帮助并且你可以通过在谷歌搜索 "{name} tutorial",(name为你想要使用的版本控制系统),得到许多很好的教程。 +版本控制系统可以选择你熟悉的或者本地使用的代码版本控制。Go 核心代码的仓库是使用 Mercurial(hg) 来控制的,所以它是一个最可能保证你可以得到开发者项目中最好的软件。Git 也很出名,同样也适用。如果你从未使用过版本控制,这些网站有一些很好的帮助并且你可以通过在谷歌搜索 "{name} tutorial"(name为你想要使用的版本控制系统)得到许多很好的教程。 ## 链接 diff --git a/eBook/10.1.md b/eBook/10.1.md index f37b09c..d522e36 100644 --- a/eBook/10.1.md +++ b/eBook/10.1.md @@ -35,7 +35,7 @@ t = new(T) 写这条语句的惯用方法是:`t := new(T)`,变量 `t` 是一个指向 `T`的指针,此时结构体字段的值是它们所属类型的零值。 -声明 `var t T` 也会给 `t` 分配内存,并零值化内存,但是这个时候 `t` 是类型T。在这两种方式中,`t` 通常被称做类型 T 的一个实例(instance)或对象(object)。 +声明 `var t T` 也会给 `t` 分配内存,并零值化内存,但是这个时候 `t` 是类型 T 。在这两种方式中,`t` 通常被称做类型 T 的一个实例(instance)或对象(object)。 示例 10.1 [structs_fields.go](examples/chapter_10/structs_fields.go) 给出了一个非常简单的例子: @@ -79,8 +79,8 @@ func main() { ```go type myStruct struct { i int } -var v myStruct // v是结构体类型变量 -var p *myStruct // p是指向一个结构体类型变量的指针 +var v myStruct // v 是结构体类型变量 +var p *myStruct // p 是指向一个结构体类型变量的指针 v.i p.i ``` @@ -89,7 +89,7 @@ p.i ```go ms := &struct1{10, 15.5, "Chris"} - // 此时ms的类型是 *struct1 + // 此时 ms 的类型是 *struct1 ``` 或者: @@ -186,7 +186,7 @@ func main() { The name of the person is CHRIS WOODWARD The name of the person is CHRIS WOODWARD -在上面例子的第二种情况中,可以直接通过指针,像 `pers2.lastName="Woodward"` 这样给结构体字段赋值,没有像 C++ 中那样需要使用 `->` 操作符,Go 会自动做这样的转换。 +在上面例子的第二种情况中,可以直接通过指针,像 `pers2.lastName = "Woodward"` 这样给结构体字段赋值,没有像 C++ 中那样需要使用 `->` 操作符,Go 会自动做这样的转换。 注意也可以通过解指针的方式来设置值:`(*pers2).lastName = "Woodward"` diff --git a/eBook/10.2.md b/eBook/10.2.md index 1b091d8..a1b0297 100644 --- a/eBook/10.2.md +++ b/eBook/10.2.md @@ -35,9 +35,9 @@ f := NewFile(10, "./test.txt") 这可以和大多数面向对象编程语言中笨拙的初始化方式做个比较:`File f = new File(...)`。 -我们可以说是工厂实例化了类型的一个对象,就像在基于类的OO语言中那样。 +我们可以说是工厂实例化了类型的一个对象,就像在基于类的 OO 语言中那样。 -如果想知道结构体类型T的一个实例占用了多少内存,可以使用:`size := unsafe.Sizeof(T{})`。 +如果想知道结构体类型 T 的一个实例占用了多少内存,可以使用:`size := unsafe.Sizeof(T{})`。 **如何强制使用工厂方法** @@ -74,7 +74,7 @@ new 和 make 这两个内置函数已经在第 [7.2.4](07.2.md) 节通过切片 下面的例子说明了在映射上使用 new 和 make 的区别以及可能发生的错误: -示例 10.4 new_make.go(不能编译) +示例 10.4 [new_make.go](examples/chapter_10/new_make.go)(不能编译) ```go package main diff --git a/eBook/10.4.md b/eBook/10.4.md index b4d0732..6008fae 100644 --- a/eBook/10.4.md +++ b/eBook/10.4.md @@ -1,6 +1,6 @@ # 10.4 带标签的结构体 -结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 `reflect` 能获取它。我们将在下一章(第 [11.10 节](11.10.md))中深入的探讨 `reflect`包,它可以在运行时自省类型、属性和方法,比如:在一个变量上调用 `reflect.TypeOf()` 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。 +结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 `reflect` 能获取它。我们将在下一章(第 [11.10 节](11.10.md))中深入的探讨 `reflect` 包,它可以在运行时自省类型、属性和方法,比如:在一个变量上调用 `reflect.TypeOf()` 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。 示例 10.7 [struct_tag.go](examples/chapter_10/struct_tag.go): diff --git a/eBook/10.6.md b/eBook/10.6.md index b891e0e..79ce6f0 100644 --- a/eBook/10.6.md +++ b/eBook/10.6.md @@ -41,7 +41,7 @@ func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... } `recv` 就像是面向对象语言中的 `this` 或 `self`,但是 Go 中并没有这两个关键字。随个人喜好,你可以使用 `this` 或 `self` 作为 receiver 的名字。下面是一个结构体上的简单方法的例子: -示例 10.10 method .go: +示例 10.10 [method1 .go](examples/chapter_10/method1.go): ```go package main @@ -82,7 +82,7 @@ func (tn *TwoInts) AddToParam(param int) int { 下面是非结构体类型上方法的例子: -示例 10.11 method2.go: +示例 10.11 [method2.go](examples/chapter_10/method2.go): ```go package main @@ -143,7 +143,7 @@ func (t time.Time) first3Chars() string { 但是有一个间接的方式:可以先定义该类型(比如:int 或 float)的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。 -示例 10.12 method_on_time.go: +示例 10.12 [method_on_time.go](examples/chapter_10/method_on_time.go): ```go package main @@ -162,9 +162,9 @@ func (t myTime) first3Chars() string { } func main() { m := myTime{time.Now()} - // 调用匿名Time上的String方法 + // 调用匿名 Time 上的 String 方法 fmt.Println("Full time now:", m.String()) - // 调用myTime.first3Chars + // 调用 myTime.first3Chars fmt.Println("First 3 chars:", m.first3Chars()) } @@ -198,9 +198,9 @@ First 3 chars: Mon 如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。 -下面的例子 `pointer_value.go` 作了说明:`change()`接受一个指向 B 的指针,并改变它内部的成员;`write()` 通过拷贝接受 B 的值并只输出B的内容。注意 Go 为我们做了探测工作,我们自己并没有指出是否在指针上调用方法,Go 替我们做了这些事情。b1 是值而 b2 是指针,方法都支持运行了。 +下面的例子 `pointer_value.go` 作了说明:`change()`接受一个指向 B 的指针,并改变它内部的成员;`write()` 通过拷贝接受 B 的值并只输出 B 的内容。注意 Go 为我们做了探测工作,我们自己并没有指出是否在指针上调用方法,Go 替我们做了这些事情。b1 是值而 b2 是指针,方法都支持运行了。 -示例 10.13 pointer_value.go: +示例 10.13 [pointer_value.go](examples/chapter_10/pointer_value.go): ```go package main @@ -218,11 +218,11 @@ func (b *B) change() { b.thing = 1 } func (b B) write() string { return fmt.Sprint(b) } func main() { - var b1 B // b1是值 + var b1 B // b1 是值 b1.change() fmt.Println(b1.write()) - b2 := new(B) // b2是指针 + b2 := new(B) // b2 是指针 b2.change() fmt.Println(b2.write()) } @@ -233,7 +233,7 @@ func main() { */ ``` -试着在 `write()` 中改变接收者b的值:将会看到它可以正常编译,但是开始的 b 没有被改变。 +试着在 `write()` 中改变接收者 b 的值:将会看到它可以正常编译,但是开始的 b 没有被改变。 我们知道方法将指针作为接收者不是必须的,如下面的例子,我们只是需要 `Point3` 的值来做计算: @@ -251,7 +251,7 @@ func (p Point3) Abs() float64 { 可以使用 `p3.Abs()` 来替代 `(*p3).Abs()`。 -像例子 10.10(method1.go)中接收者类型是 `*TwoInts` 的方法 `AddThem()`,它能在类型 `TwoInts` 的值上被调用,这是自动间接发生的。 +像例子 10.10([method1.go](examples/chapter_10/method1.go))中接收者类型是 `*TwoInts` 的方法 `AddThem()`,它能在类型 `TwoInts` 的值上被调用,这是自动间接发生的。 因此 `two2.AddThem` 可以替代 `(&two2).AddThem()`。 @@ -263,7 +263,7 @@ func (p Point3) Abs() float64 { **指针方法和值方法都可以在指针或非指针上被调用**,如下面程序所示,类型 `List` 在值上有一个方法 `Len()`,在指针上有一个方法 `Append()`,但是可以看到两个方法都可以在两种类型的变量上被调用。 -示例 10.14 methodset1.go: +示例 10.14 [methodset1.go](examples/chapter_10/methodset1.go): ```go package main @@ -296,7 +296,7 @@ func main() { 这可以通过面向对象语言一个众所周知的技术来完成:提供 getter 和 setter 方法。对于 setter 方法使用 Set 前缀,对于 getter 方法只使用成员名。 -示例 10.15 person2.go: +示例 10.15 [person2.go](examples/chapter_10/person2.go): ```go package person @@ -315,7 +315,7 @@ func (p *Person) SetFirstName(newName string) { } ``` -示例 10.16—use_person2.go: +示例 10.16—[use_person2.go](examples/chapter_10/use_person2.go): ```go package main @@ -368,7 +368,7 @@ func (c *Car) GoToWorkIn() { } ``` -下面是 `method3.go` 的完整例子,它展示了内嵌结构体上的方法可以直接在外层类型的实例上调用: +下面是 [method3.go](examples/chapter_10/method3.go) 的完整例子,它展示了内嵌结构体上的方法可以直接在外层类型的实例上调用: ```go package main @@ -393,15 +393,15 @@ type NamedPoint struct { func main() { n := &NamedPoint{Point{3, 4}, "Pythagoras"} - fmt.Println(n.Abs()) // 打印5 + fmt.Println(n.Abs()) // 打印 5 } ``` -内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法。当然类型可以有只作用于本身实例而不作用于内嵌“父”类型上的方法, +内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法。当然类型可以有只作用于本身实例而不作用于内嵌“父”类型上的方法。 可以覆写方法(像字段一样):和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法。 -在示例 10.18 method4.go 中添加: +在示例 10.18 [method4.go](examples/chapter_10/method4.go) 中添加: ```go func (n *NamedPoint) Abs() float64 { @@ -435,7 +435,7 @@ B:内嵌:内嵌(匿名地)所需功能类型,像前一节 10.6.5 所 方式 A 可以通过如下方法实现(使用了第 10.7 节中的 `String()` 功能): -示例 10.19 embed_func1.go: +示例 10.19 [embed_func1.go](examples/chapter_10/embed_func1.go): ```go package main @@ -485,7 +485,7 @@ func (c *Customer) Log() *Log { 1 - Yes we can! 2 - After me the world will be a better place! -相对的方式 B 可能会像这样: +相对的方式 B 可能会像这样([embed_func2.go](examples/chapter_10/embed_func2.go)): ```go package main @@ -541,7 +541,7 @@ func (c *Customer) String() string { 作为一个例子,假设有一个类型 `CameraPhone`,通过它可以 `Call()`,也可以 `TakeAPicture()`,但是第一个方法属于类型 `Phone`,第二个方法属于类型 `Camera`。 -只要嵌入这两个类型就可以解决这个问题,如下所示: +只要嵌入这两个类型就可以解决这个问题,如下所示([mult_inheritance.go](examples/chapter_10/mult_inheritance.go)): ```go package main diff --git a/eBook/10.7.md b/eBook/10.7.md index b8e8e0d..07cfbff 100644 --- a/eBook/10.7.md +++ b/eBook/10.7.md @@ -4,7 +4,7 @@ 我们使用第 10.4 节中程序的类型来进行测试: -示例 10.22 method_string.go: +示例 10.22 [method_string.go](examples/chapter_10/method_string.go): ```go package main @@ -88,7 +88,7 @@ type T struct { ![](images/10.7_fig.jpg?raw=true) -它的格子包含数据,比如整数 i、j、k 和 l 等等,格子从底部(索引 0)至顶部(索引 n)来索引。这个例子中假定 `n=3`,那么一共有 4 个格子。 +它的格子包含数据,比如整数 i、j、k 和 l 等等,格子从底部(索引 0)至顶部(索引 n)来索引。这个例子中假定 `n = 3`,那么一共有 4 个格子。 一个新栈中所有格子的值都是 0。 diff --git a/eBook/11.10.md b/eBook/11.10.md index 8ae457e..f8e05d8 100644 --- a/eBook/11.10.md +++ b/eBook/11.10.md @@ -205,7 +205,7 @@ func main() { value := reflect.ValueOf(secret) // typ := reflect.TypeOf(secret) // main.NotknownType // alternative: - //typ := value.Type() // main.NotknownType + // typ := value.Type() // main.NotknownType fmt.Println(typ) knd := value.Kind() // struct fmt.Println(knd) @@ -214,7 +214,7 @@ func main() { for i := 0; i < value.NumField(); i++ { fmt.Printf("Field %d: %v\n", i, value.Field(i)) // error: panic: reflect.Value.SetString using value obtained using unexported field - //value.Field(i).SetString("C#") + // value.Field(i).SetString("C#") } // call the first method, which is String(): diff --git a/eBook/11.11.md b/eBook/11.11.md index c1f7a18..d3b6c6a 100644 --- a/eBook/11.11.md +++ b/eBook/11.11.md @@ -8,7 +8,7 @@ Printf 的函数声明为: func Printf(format string, args ... interface{}) (n int, err error) ``` -Printf 中的 `...` 参数为空接口类型。Printf 使用反射包来解析这个参数列表。所以,Printf 能够知道它每个参数的类型。因此格式化字符串中只有%d而没有 %u 和 %ld,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print 和 Println 在没有格式字符串的情况下还能如此漂亮地输出。 +Printf 中的 `...` 参数为空接口类型。Printf 使用反射包来解析这个参数列表。所以,Printf 能够知道它每个参数的类型。因此格式化字符串中只有 %d 而没有 %u 和 %ld,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print 和 Println 在没有格式字符串的情况下还能如此漂亮地输出。 为了让大家更加具体地了解 Printf 中的反射,我们实现了一个简单的通用输出函数。其中使用了 type-switch 来推导参数类型,并根据类型来输出每个参数的值(这里用了 10.7 节中练习 10.13 的部分代码) diff --git a/eBook/11.12.md b/eBook/11.12.md index 84ff1db..8c7ed2a 100644 --- a/eBook/11.12.md +++ b/eBook/11.12.md @@ -14,7 +14,7 @@ Go 中的接口跟 Java/C# 类似:都是必须提供一个指定方法集的 类似于 Python 和 Ruby 这类动态语言中的 `动态类型(duck typing)`;这意味着对象可以根据提供的方法被处理(例如,作为参数传递给函数),而忽略它们的实际类型:它们能做什么比它们是什么更重要。 -这在程序 duck_dance.go 中得以阐明,函数 DuckDance 接受一个 IDuck 接口类型变量。仅当 DuckDance 被实现了 IDuck 接口的类型调用时程序才能编译通过。 +这在程序 [duck_dance.go](examples/chapter_11/duck_dance.go) 中得以阐明,函数 DuckDance 接受一个 IDuck 接口类型变量。仅当 DuckDance 被实现了 IDuck 接口的类型调用时程序才能编译通过。 示例 11.16 [duck_dance.go](examples/chapter_11/duck_dance.go): @@ -119,7 +119,8 @@ Go 的接口提高了代码的分离度,改善了代码的复用性,使得 `提取接口` 是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。 Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。 -我们来扩展 11.1 节的示例 11.2 interfaces_poly.go,假设我们需要一个新的接口 `TopologicalGenus`,用来给 shape 排序(这里简单地实现为返回 int)。我们需要做的是给想要满足接口的类型实现 `Rank()` 方法: + +我们来扩展 11.1 节的示例 11.2 [interfaces_poly.go](examples/chapter_11/interfaces_poly.go),假设我们需要一个新的接口 `TopologicalGenus`,用来给 shape 排序(这里简单地实现为返回 int)。我们需要做的是给想要满足接口的类型实现 `Rank()` 方法: 示例 11.17 [multi_interfaces_poly.go](examples/chapter_11/multi_interfaces_poly.go): @@ -229,7 +230,7 @@ func (b Bar) Foo() {} fmt.Printf(format string, a ...interface{}) (n int, errno error) ``` -这个函数通过枚举 `slice` 类型的实参动态确定所有参数的类型。并查看每个类型是否实现了 `String()` 方法,如果是就用于产生输出信息。我们可以回到 11.10 节查看这些细节。 +这个函数通过枚举 `slice` 类型的实参动态确定所有参数的类型,并查看每个类型是否实现了 `String()` 方法,如果是就用于产生输出信息。我们可以回到 11.10 节查看这些细节。 ## 11.12.6 接口的继承 @@ -275,7 +276,7 @@ type ReaderWriter struct { 在练习 7.13 中我们定义了一个 map 函数来使用 int 切片 (map_function.go)。 -通过空接口和类型断言,现在我们可以写一个可以应用于许多类型的 `泛型` 的 map 函数,为 int 和 string 构建一个把 int 值加倍和将字符串值与其自身连接(译者注:即`"abc"`变成`"abcabc"`)的 map 函数 `mapFunc`。 +通过空接口和类型断言,现在我们可以写一个可以应用于许多类型的 `泛型` 的 map 函数,为 int 和 string 构建一个把 int 值加倍和将字符串值与其自身连接(译者注:即 `"abc"` 变成 `"abcabc"` )的 map 函数 `mapFunc`。 提示:为了可读性可以定义一个 interface{} 的别名,比如:type obj interface{} diff --git a/eBook/11.4.md b/eBook/11.4.md index 3b5faf1..6a65cb8 100644 --- a/eBook/11.4.md +++ b/eBook/11.4.md @@ -19,7 +19,7 @@ default: 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` 。 @@ -64,7 +64,7 @@ func classifier(items ...interface{}) { 在处理来自于外部的、类型未知的数据时,比如解析诸如 JSON 或 XML 编码的数据,类型测试和转换会非常有用。 -在示例 12.17(xml.go)中解析 XML 文档时,我们就会用到 `type-switch`。 +在示例 12.17([xml.go](examples/chapter_12/xml.go))中解析 XML 文档时,我们就会用到 `type-switch`。 **练习 11.4** simple_interface2.go: diff --git a/eBook/11.6.md b/eBook/11.6.md index 48d669a..cf24220 100644 --- a/eBook/11.6.md +++ b/eBook/11.6.md @@ -46,13 +46,13 @@ func main() { // 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 + 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 + 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") @@ -68,7 +68,7 @@ func main() { **总结** -在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以从具体类型 `P` 直接可以辨识的: +在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以根据具体类型 `P` 直接辨识的: - 指针方法可以通过指针调用 - 值方法可以通过值调用 diff --git a/eBook/11.7.md b/eBook/11.7.md index ecba9f0..de7ff32 100644 --- a/eBook/11.7.md +++ b/eBook/11.7.md @@ -1,6 +1,6 @@ # 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)` 方法。 排序函数的算法只会使用到这三个方法(可以使用任何排序算法来实现,此处我们使用冒泡排序): @@ -202,17 +202,17 @@ b). 定义一个新接口 `PeriInterface`,它有一个 `Perimeter` 方法。 **练习 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) -- `Fill()`:创建一个包含10个随机浮点数的数组(参考4.5.2.6) +- `Fill()`:创建一个包含 10 个随机浮点数的数组(参考4.5.2.6) 在主程序中新建一个此类型的变量,然后对它排序并进行测试。 diff --git a/eBook/11.8.md b/eBook/11.8.md index a6d71b1..44fdea1 100644 --- a/eBook/11.8.md +++ b/eBook/11.8.md @@ -14,7 +14,7 @@ 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` 都是不带缓冲的,`bufio` 包里提供了对应的带缓冲的操作,在读写 `UTF-8` 编码的文本文件时它们尤其有用。在 第12章 我们会看到很多在实战中使用它们的例子。 diff --git a/eBook/11.9.md b/eBook/11.9.md index 08444ba..420db20 100644 --- a/eBook/11.9.md +++ b/eBook/11.9.md @@ -107,7 +107,7 @@ func main() { **练习 11.9** simple_interface3.go: -继续 练习11.2,在它中添加一个 `gI` 函数,它不再接受 `Simpler` 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 `Simpler` 类型。最后在 `main` 使用 `gI` 取代 `fI` 函数并调用它。确保你的代码足够安全。 +继续练习 11.2,在它中添加一个 `gI` 函数,它不再接受 `Simpler` 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 `Simpler` 类型。最后在 `main` 使用 `gI` 取代 `fI` 函数并调用它。确保你的代码足够安全。 ## 11.9.2 构建通用类型或包含不同类型变量的数组 @@ -143,7 +143,7 @@ func (p *Vector) Set(i int, e Element) { **练习 11.10** min_interface.go / minmain.go: -仿照11.7中开发的 `Sorter` 接口,创建一个 `Miner` 接口并实现一些必要的操作。函数 `Min` 接受一个 `Miner` 类型变量的集合,然后计算并返回集合中最小的元素。 +仿照 11.7 中开发的 `Sorter` 接口,创建一个 `Miner` 接口并实现一些必要的操作。函数 `Min` 接受一个 `Miner` 类型变量的集合,然后计算并返回集合中最小的元素。 ## 11.9.3 复制数据切片至空接口切片 @@ -170,7 +170,7 @@ for i, d := range dataSlice { ## 11.9.4 通用类型的节点数据结构 -在10.1中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 `NewNode` 方法,及设置数据的 `SetData` 方法。 +在 10.1 中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 `NewNode` 方法,及设置数据的 `SetData` 方法。 示例 11.10 [node_structures.go](examples/chapter_11/node_structures.go): diff --git a/eBook/12.1.md b/eBook/12.1.md index f0cbae2..f07fe01 100644 --- a/eBook/12.1.md +++ b/eBook/12.1.md @@ -126,7 +126,7 @@ func main() { } ``` -注意:Unix和Windows的行结束符是不同的! +注意:Unix 和 Windows 的行结束符是不同的! **练习** diff --git a/eBook/12.10.md b/eBook/12.10.md index ec610c5..4f2f4d8 100644 --- a/eBook/12.10.md +++ b/eBook/12.10.md @@ -11,7 +11,7 @@ 如同 json 包一样,也有 `Marshal()` 和 `UnMarshal()` 从 XML 中编码和解码数据;但这个更通用,可以从文件中读取和写入(或者任何实现了 io.Reader 和 io.Writer 接口的类型) -和 JSON 的方式一样,XML 数据可以序列化为结构,或者从结构反序列化为 XML 数据;这些可以在例子 15.8(twitter_status.go)中看到。 +和 JSON 的方式一样,XML 数据可以序列化为结构,或者从结构反序列化为 XML 数据;这些可以在例子 15.8([twitter_status.go](examples/chapter_15/twitter_status.go))中看到。 encoding/xml 包实现了一个简单的 XML 解析器(SAX),用来解析 XML 数据内容。下面的例子说明如何使用解析器: diff --git a/eBook/12.2.md b/eBook/12.2.md index 4ae5a23..a095e14 100644 --- a/eBook/12.2.md +++ b/eBook/12.2.md @@ -45,7 +45,7 @@ func main() { 接着,我们在一个无限循环中使用 `ReadString('\n')` 或 `ReadBytes('\n')` 将文件的内容逐行(行结束符 '\n')读取出来。 -**注意:** 在之前的例子中,我们看到,Unix和Linux的行结束符是 \n,而Windows的行结束符是 \r\n。在使用 `ReadString` 和 `ReadBytes` 方法的时候,我们不需要关心操作系统的类型,直接使用 \n 就可以了。另外,我们也可以使用 `ReadLine()` 方法来实现相同的功能。 +**注意:** 在之前的例子中,我们看到,Unix 和 Linux 的行结束符是 \n,而 Windows 的行结束符是 \r\n。在使用 `ReadString` 和 `ReadBytes` 方法的时候,我们不需要关心操作系统的类型,直接使用 \n 就可以了。另外,我们也可以使用 `ReadLine()` 方法来实现相同的功能。 一旦读取到文件末尾,变量 `readerError` 的值将变成非空(事实上,其值为常量 `io.EOF`),我们就会执行 `return` 语句从而退出循环。 @@ -160,7 +160,7 @@ filename := filepath.Base(path) ``` 每行的第一个字段为 title,第二个字段为 price,第三个字段为 quantity。内容的格式基本与 示例 12.3c 的相同,除了分隔符改成了分号。请读取出文件的内容,创建一个结构用于存取一行的数据,然后使用结构的切片,并把数据打印出来。 -关于解析 CSV 文件,`encoding/csv` 包提供了相应的功能。具体请参考 [http://golang.org/pkg/encoding/csv/](http://golang.org/pkg/encoding/csv/) +关于解析 CSV 文件,`encoding/csv` 包提供了相应的功能。具体请参考 [http://golang.org/pkg/encoding/csv/](http://golang.org/pkg/encoding/csv/) 。 ## 12.2.2 `compress`包:读取压缩文件 @@ -258,9 +258,9 @@ outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 06 - `os.O_RDONLY`:只读 - `os.O_WRONLY`:只写 - `os.O_CREATE`:创建:如果指定文件不存在,就创建该文件。 -- `os.O_TRUNC`:截断:如果指定文件已存在,就将该文件的长度截为0。 +- `os.O_TRUNC`:截断:如果指定文件已存在,就将该文件的长度截为 0 。 -在读文件的时候,文件的权限是被忽略的,所以在使用 `OpenFile` 时传入的第三个参数可以用0。而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666。 +在读文件的时候,文件的权限是被忽略的,所以在使用 `OpenFile` 时传入的第三个参数可以用 0 。而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666。 然后,我们创建一个写入器(缓冲区)对象: @@ -310,7 +310,7 @@ type Page struct { } ``` -请给这个结构编写一个 `save` 方法,将 Title 作为文件名、Body作为文件内容,写入到文本文件中。 +请给这个结构编写一个 `save` 方法,将 Title 作为文件名、Body 作为文件内容,写入到文本文件中。 再编写一个 `load` 函数,接收的参数是字符串 title,该函数读取出与 title 对应的文本文件。请使用 `*Page` 做为参数,因为这个结构可能相当巨大,我们不想在内存中拷贝它。请使用 `ioutil` 包里的函数(参考章节12.2.1)。 diff --git a/eBook/12.3.md b/eBook/12.3.md index 0f9753e..b270485 100644 --- a/eBook/12.3.md +++ b/eBook/12.3.md @@ -36,7 +36,7 @@ func CopyFile(dstName, srcName string) (written int64, err error) { } ``` -注意 `defer` 的使用:当打开dst文件时发生了错误,那么 `defer` 仍然能够确保 `src.Close()` 执行。如果不这么做,src文件会一直保持打开状态并占用资源。 +注意 `defer` 的使用:当打开 dst 文件时发生了错误,那么 `defer` 仍然能够确保 `src.Close()` 执行。如果不这么做,src 文件会一直保持打开状态并占用资源。 ## 链接 diff --git a/eBook/12.4.md b/eBook/12.4.md index 94904b5..913be1b 100644 --- a/eBook/12.4.md +++ b/eBook/12.4.md @@ -31,11 +31,11 @@ func main() { 但是我们在命令行加入参数,像这样:`os_args John Bill Marc Luke`,将得到这样的输出:`Good Morning Alice John Bill Marc Luke` -这个命令行参数会放置在切片 `os.Args[]` 中(以空格分隔),从索引1开始(`os.Args[0]` 放的是程序本身的名字,在本例中是 `os_args`)。函数 `strings.Join` 以空格为间隔连接这些参数。 +这个命令行参数会放置在切片 `os.Args[]` 中(以空格分隔),从索引 1 开始(`os.Args[0]` 放的是程序本身的名字,在本例中是 `os_args`)。函数 `strings.Join` 以空格为间隔连接这些参数。 **练习 12.5**:[hello_who.go](exercises/chapter_12/hello_who.go) -写一个"Hello World"的变种程序:把人的名字作为程序命令行执行的一个参数,比如: `hello_who Evan Michael Laura` 那么会输出`Hello Evan Michael Laura`! +写一个"Hello World"的变种程序:把人的名字作为程序命令行执行的一个参数,比如: `hello_who Evan Michael Laura` 那么会输出 `Hello Evan Michael Laura!` ## 12.4.2 flag 包 @@ -52,7 +52,7 @@ type Flag struct { } ``` -下面的程序 `echo.go` 模拟了 Unix 的 echo 功能: +下面的程序 [echo.go](examples/chapter_12/echo.go) 模拟了 Unix 的 echo 功能: ```go package main @@ -86,7 +86,7 @@ func main() { } ``` -`flag.Parse()` 扫描参数列表(或者常量列表)并设置 flag, `flag.Arg(i)` 表示第i个参数。`Parse()` 之后 `flag.Arg(i)` 全部可用,`flag.Arg(0)` 就是第一个真实的 flag,而不是像 `os.Args(0)` 放置程序的名字。 +`flag.Parse()` 扫描参数列表(或者常量列表)并设置 flag, `flag.Arg(i)` 表示第 i 个参数。`Parse()` 之后 `flag.Arg(i)` 全部可用,`flag.Arg(0)` 就是第一个真实的 flag,而不是像 `os.Args(0)` 放置程序的名字。 `flag.Narg()` 返回参数的数量。解析后 flag 或常量就可用了。 `flag.Bool()` 定义了一个默认值是 `false` 的 flag:当在命令行出现了第一个参数(这里是 "n"),flag 被设置成 `true`(NewLine 是 `*bool` 类型)。flag 被解引用到 `*NewLine`,所以当值是 `true` 时将添加一个 Newline("\n")。 @@ -131,4 +131,4 @@ if *processedFlag { // found flag -proc - [目录](directory.md) - 上一节:[文件拷贝](12.3.md) -- 下一节:[用buffer读取文件](12.5.md) +- 下一节:[用 buffer 读取文件](12.5.md) diff --git a/eBook/12.6.md b/eBook/12.6.md index 095ce31..934abf0 100644 --- a/eBook/12.6.md +++ b/eBook/12.6.md @@ -22,7 +22,7 @@ func cat(f *os.File) { } ``` -上面的代码来自于 `cat2.go`,使用了 os 包中的 `os.File` 和 `Read` 方法;`cat2.go` 与 `cat.go` 具有同样的功能。 +上面的代码来自于 [cat2.go](examples/chapter_12/cat2.go),使用了 os 包中的 `os.File` 和 `Read` 方法;`cat2.go` 与 `cat.go` 具有同样的功能。 示例 12.14 [cat2.go](examples/chapter_12/cat2.go): diff --git a/eBook/12.8.md b/eBook/12.8.md index 7255ccb..6e6e3aa 100644 --- a/eBook/12.8.md +++ b/eBook/12.8.md @@ -37,7 +37,8 @@ hello world! - buffered ```go func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) ``` -其不是写入一个文件,而是写入一个 `io.Writer` 接口类型的变量,下面是 `Writer` 接口在 io 包中的定义: + +不是写入一个文件,而是写入一个 `io.Writer` 接口类型的变量,下面是 `Writer` 接口在 io 包中的定义: ```go type Writer interface { @@ -45,7 +46,7 @@ type Writer interface { } ``` -`fmt.Fprintf()` 依据指定的格式向第一个参数内写入字符串,第一个参数必须实现了 `io.Writer` 接口。`Fprintf()` 能够写入任何类型,只要其实现了 `Write` 方法,包括 `os.Stdout`,文件(例如 os.File),管道,网络连接,通道等等,同样的也可以使用 bufio 包中缓冲写入。bufio 包中定义了 `type Writer struct{...}`。 +`fmt.Fprintf()` 依据指定的格式向第一个参数内写入字符串,第一个参数必须实现了 `io.Writer` 接口。`Fprintf()` 能够写入任何类型,只要其实现了 `Write` 方法,包括 `os.Stdout`,文件(例如 os.File),管道,网络连接,通道等等,同样的也可以使用 bufio 包中缓冲写入。bufio 包中定义了 `type Writer struct{...}` 。 bufio.Writer 实现了 Write 方法: @@ -53,13 +54,13 @@ bufio.Writer 实现了 Write 方法: func (b *Writer) Write(p []byte) (nn int, err error) ``` -它还有一个工厂函数:传给它一个 `io.Writer` 类型的参数,它会返回一个带缓冲的 `bufio.Writer` 类型的 `io.Writer`: +它还有一个工厂函数:传给它一个 `io.Writer` 类型的参数,它会返回一个带缓冲的 `bufio.Writer` 类型的 `io.Writer` : ```go func NewWriter(wr io.Writer) (b *Writer) ``` -其适合任何形式的缓冲写入。 +适合任何形式的缓冲写入。 在缓冲写入的最后千万不要忘了使用 `Flush()`,否则最后的输出不会被写入。 @@ -107,4 +108,4 @@ func main() { - [目录](directory.md) - 上一节:[用 defer 关闭文件](12.7.md) -- 下一节:[格式化 Json 数据](12.9.md) +- 下一节:[格式化 JSON 数据](12.9.md) diff --git a/eBook/12.9.md b/eBook/12.9.md index c154db1..8ef3698 100644 --- a/eBook/12.9.md +++ b/eBook/12.9.md @@ -98,9 +98,9 @@ func main() { } ``` -出于安全考虑,在 web 应用中最好使用 `json.MarshalforHTML()` 函数,其对数据执行HTML转码,所以文本可以被安全地嵌在 HTML `