From f5dae8f559adda12026461c8a3d8b477f0cbba6a Mon Sep 17 00:00:00 2001 From: Haigang Zhou <57883305+loftea@users.noreply.github.com> Date: Mon, 9 May 2022 21:42:14 +0800 Subject: [PATCH] =?UTF-8?q?4-5=20=E7=AB=A0=20(#844)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joe Chen --- eBook/04.1.md | 4 +- eBook/04.2.md | 70 ++++++++++++++++----------------- eBook/04.3.md | 8 ++-- eBook/04.4.md | 61 ++++++++++++++--------------- eBook/04.5.md | 105 ++++++++++++++++++++++++-------------------------- eBook/04.6.md | 14 +++---- eBook/04.7.md | 45 +++++++++++----------- eBook/04.8.md | 8 ++-- eBook/04.9.md | 22 +++++------ eBook/05.0.md | 10 ++--- eBook/05.1.md | 88 +++++++++++++++++++++++------------------- eBook/05.2.md | 27 +++++++------ eBook/05.3.md | 22 +++++------ eBook/05.4.md | 24 ++++++------ eBook/05.5.md | 16 ++++---- eBook/05.6.md | 16 ++++---- 16 files changed, 273 insertions(+), 267 deletions(-) diff --git a/eBook/04.1.md b/eBook/04.1.md index 2ab5572..4d1a02b 100644 --- a/eBook/04.1.md +++ b/eBook/04.1.md @@ -3,7 +3,7 @@ Go 的源文件以 `.go` 为后缀名存储在计算机中,这些文件名均由小写字母组成,如 `scanner.go` 。如果文件名由多个部分组成,则使用下划线 `_` 对它们进行分隔,如 `scanner_test.go` 。文件名不包含空格或其他特殊字符。 一个源文件可以包含任意多行的代码,Go 本身没有对源文件的大小进行限制。 - + 你会发现在 Go 代码中的几乎所有东西都有一个名称或标识符。另外,Go 语言也是区分大小写的,这与 C 家族中的其它语言相同。有效的标识符必须以字母(可以使用任何 UTF-8 编码的字符或 `_`)开头,然后紧跟着 0 个或多个字符或 Unicode 数字,如:X56、group1、_x23、i、өԑ12。 以下是无效的标识符: @@ -58,7 +58,7 @@ Go 的源文件以 `.go` 为后缀名存储在计算机中,这些文件名均 之所以刻意地将 Go 代码中的关键字保持的这么少,是为了简化在编译过程第一步中的代码解析。和其它语言一样,关键字不能够作标识符使用。 -除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数(第 6.5 节),它们的作用都将在接下来的章节中进行进一步地讲解。 +除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数([第 6.5 节](06.5.md)),它们的作用都将在接下来的章节中进行进一步地讲解。 diff --git a/eBook/04.2.md b/eBook/04.2.md index 2ba536f..f7904f8 100644 --- a/eBook/04.2.md +++ b/eBook/04.2.md @@ -18,15 +18,15 @@ func main() { 如同其它一些编程语言中的类库或命名空间的概念,每个 Go 文件都属于且仅属于一个包。一个包可以由许多以 `.go` 为扩展名的源文件组成,因此文件名和包名一般来说都是不相同的。 -你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:`package main`。`package main`表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 `main` 的包。 +你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:`package main`。`package main` 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 `main` 的包。 -一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 `package main` 来指明这些文件都属于 main 包。如果你打算编译包名不是为 main 的源文件,如 `pack1`,编译后产生的对象文件将会是 `pack1.a` 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母。 +一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 `package main` 来指明这些文件都属于 `main` 包。如果你打算编译包名不是为 main 的源文件,如 `pack1`,编译后产生的对象文件将会是 `pack1.a` 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母。 **标准库** 在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。在 Windows 下,标准库的位置在 Go 根目录下的子目录 `pkg\windows_386` 中;在 Linux 下,标准库在 Go 根目录下的子目录 `pkg\linux_amd64` 中(如果是安装的是 32 位,则在 `linux_386` 目录中)。一般情况下,标准包会存放在 `$GOROOT/pkg/$GOOS_$GOARCH/` 目录下。 -Go 的标准库包含了大量的包(如:fmt 和 os),但是你也可以创建自己的包(第 9 章)。 +Go 的标准库包含了大量的包(如:`fmt` 和 `os`),但是你也可以创建自己的包([第 9 章](.\09.0.md))。 如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。 @@ -62,7 +62,7 @@ import "os" import "fmt"; import "os" ``` -但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义): +但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 `const`、`var` 和 `type` 的声明或定义): ```go import ( @@ -84,7 +84,7 @@ import ("fmt"; "os") *译者注:以相对路径在GOPATH下导入包会产生报错信息* *报错信息:local import "./XXX" in non-local package* - + *引用:[Go programs cannot use relative import paths within a work space.](https://golang.org/cmd/go/#hdr-Relative_import_paths )* *注解:在GOPATH外可以以相对路径的形式执行go build(go install 不可以)* @@ -103,7 +103,7 @@ import ("fmt"; "os") 因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。 -假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:`pack1.Thing`(pack1 在这里是不可以省略的)。 +假设在包 `pack1` 中我们有一个变量或函数叫做 `Thing`(以 T 开头,所以它能够被导出),那么在当前包中导入 `pack1` 包,`Thing` 就可以像面向对象语言那样使用点标记来调用:`pack1.Thing`(pack1 在这里是不可以省略的)。 因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于它们的包名,例如 `pack1.Thing` 和 `pack2.Thing`。 @@ -123,11 +123,11 @@ func main() { **注意事项** -如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 `imported and not used: os`,这正是遵循了 Go 的格言:“没有不必要的代码!“。 +如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 `imported and not used: os`,这正是遵循了 Go 的格言:“没有不必要的代码!”。 **包的分级声明和初始化** -你可以在使用 `import` 导入包之后定义或声明 0 个或多个常量(const)、变量(var)和类型(type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用(如 [gotemplate.go](examples/chapter_4/gotemplate.go) 源文件中的 c 和 v),然后声明一个或多个函数(func)。 +你可以在使用 `import` 导入包之后定义或声明 0 个或多个常量 (const)、变量 (var) 和类型 (type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用(如 [gotemplate.go](examples/chapter_4/gotemplate.go) 源文件中的 `c` 和 `v`),然后声明一个或多个函数 (func)。 ## 4.2.2 函数 @@ -139,7 +139,7 @@ func functionName() 你可以在括号 `()` 中写入 0 个或多个函数的参数(使用逗号 `,` 分隔),每个参数的名称后面必须紧跟着该参数的类型。 -main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。如果你的 main 包的源代码没有包含 main 函数,则会引发构建错误 `undefined: main.main`。main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误: +`main()` 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 `init()` 函数则会先执行该函数)。如果你的 `main` 包的源代码没有包含 `main()` 函数,则会引发构建错误 `undefined: main.main`。`main()` 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。如果你不小心为 `main()` 函数添加了参数或者返回类型,将会引发构建错误: func main must have no arguments and no return values results. @@ -161,7 +161,7 @@ main 函数是每一个可执行程序所必须包含的,一般来说都是在 func Sum(a, b int) int { return a + b } ``` -对于大括号 `{}` 的使用规则在任何时候都是相同的(如:if 语句等)。 +对于大括号 `{}` 的使用规则在任何时候都是相同的(如:`if` 语句等)。 因此符合规范的函数一般写成如下的形式: @@ -173,8 +173,8 @@ func functionName(parameter_list) (return_value_list) { 其中: -- parameter_list 的形式为 (param1 type1, param2 type2, …) -- return_value_list 的形式为 (ret1 type1, ret2 type2, …) +- `parameter_list` 的形式为 `(param1 type1, param2 type2, …)` +- `return_value_list` 的形式为 `(ret1 type1, ret2 type2, …)` 只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。 @@ -188,13 +188,13 @@ fmt.Println("hello, world") `Print` 和 `Println` 这两个函数也支持使用变量,如:`fmt.Println(arr)`。如果没有特别指定,它们会以默认的打印格式将变量 `arr` 输出到控制台。 -单纯地打印一个字符串或变量甚至可以使用预定义的方法来实现,如:`print`、`println:print("ABC")`、`println("ABC")`、`println(i)`(带一个变量 i)。 +单纯地打印一个字符串或变量甚至可以使用预定义的方法来实现,如:`print`、`println:print("ABC")`、`println("ABC")`、`println(i)`(带一个变量 `i`)。 这些函数只可以用于调试阶段,在部署程序的时候务必将它们替换成 `fmt` 中的相关函数。 当被调用函数的代码执行到结束符 `}` 或返回语句时就会返回,然后程序继续执行调用该函数之后的代码。 -程序正常退出的代码为 0 即 `Program exited with code 0`;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。 +程序正常退出的代码为 `0` 即 `Program exited with code 0`;如果程序因为异常而被终止,则会返回非零值,如:`1`。这个数值可以用来测试是否成功执行一个程序。 ## 4.2.3 注释 @@ -212,11 +212,11 @@ func main() { 上面这个例子通过打印 `Καλημέρα κόσμε; or こんにちは 世界` 展示了如何在 Go 中使用国际化字符,以及如何使用注释。 -注释不会被编译,但可以通过 godoc 来使用(第 3.6 节)。 +注释不会被编译,但可以通过 godoc 来使用([第 3.6 节](03.6.md))。 单行注释是最常见的注释形式,你可以在任何地方使用以 `//` 开头的单行注释。多行注释也叫块注释,均已以 `/*` 开头,并以 `*/` 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。 -每一个包应该有相关注释,在 package 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。当开发人员需要了解包的一些情况时,自然会用 godoc 来显示包的文档说明,在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。另外,在多段注释之间应以空行分隔加以区分。 +每一个包应该有相关注释,在 `package` 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。当开发人员需要了解包的一些情况时,自然会用 godoc 来显示包的文档说明,在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。另外,在多段注释之间应以空行分隔加以区分。 示例: @@ -240,15 +240,15 @@ func enterOrbit() error { } ``` -godoc 工具(第 3.6 节)会收集这些注释并产生一个技术文档。 +godoc 工具([第 3.6 节](03.6.md))会收集这些注释并产生一个技术文档。 ## 4.2.4 类型 -变量(或常量)包含数据,这些数据可以有不同的数据类型,简称类型。使用 var 声明的变量的值会自动初始化为该类型的零值。类型定义了某个变量的值的集合与可对其进行操作的集合。 +变量(或常量)包含数据,这些数据可以有不同的数据类型,简称类型。使用 `var` 声明的变量的值会自动初始化为该类型的零值。类型定义了某个变量的值的集合与可对其进行操作的集合。 -类型可以是基本类型,如:int、float、bool、string;结构化的(复合的),如:struct、array、slice、map、channel;只描述类型的行为的,如:interface。 +类型可以是基本类型,如:`int`、`float`、`bool`、`string`;结构化的(复合的),如:`struct`、`array`、切片 (slice)、`map`、通道 (channel);只描述类型的行为的,如:`interface`。 -结构化的类型没有真正的值,它使用 nil 作为默认值(在 Objective-C 中是 nil,在 Java 中是 null,在 C 和 C++ 中是NULL或 0)。值得注意的是,Go 语言中不存在类型继承。 +结构化的类型没有真正的值,它使用 `nil` 作为默认值(在 Objective-C 中是 nil,在 Java 中是 null,在 C 和 C++ 中是 NULL 或 0)。值得注意的是,Go 语言中不存在类型继承。 函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如: @@ -256,7 +256,7 @@ godoc 工具(第 3.6 节)会收集这些注释并产生一个技术文档。 func FunctionName (a typea, b typeb) typeFunc ``` -你可以在函数体中的某处返回使用类型为 typeFunc 的变量 var: +你可以在函数体中的某处返回使用类型为 `typeFunc` 的变量 `var`: ```go return var @@ -268,7 +268,7 @@ return var func FunctionName (a typea, b typeb) (t1 type1, t2 type2) ``` -示例: 函数 Atoi (第 4.7 节):`func Atoi(s string) (i int, err error)` +示例: 函数 `Atoi()`([第 4.7 节](04.7.md)):`func Atoi(s string) (i int, err error)` 返回的形式: @@ -276,9 +276,9 @@ func FunctionName (a typea, b typeb) (t1 type1, t2 type2) return var1, var2 ``` -这种多返回值一般用于判断某个函数是否执行成功(true/false)或与其它返回值一同返回错误消息(详见之后的并行赋值)。 +这种多返回值一般用于判断某个函数是否执行成功 (true/false) 或与其它返回值一同返回错误消息(详见之后的并行赋值)。 -使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体(第 10 章),但是也可以定义一个已经存在的类型的别名,如: +使用 `type` 关键字可以定义你自己的类型,你可能想要定义一个结构体([第 10 章](10.0.md)),但是也可以定义一个已经存在的类型的别名,如: ```go type IZ int @@ -292,7 +292,7 @@ type IZ int var a IZ = 5 ``` -这里我们可以看到 int 是变量 a 的底层类型,这也使得它们之间存在相互转换的可能(第 4.2.6 节)。 +这里我们可以看到 `int` 是变量 `a` 的底层类型,这也使得它们之间存在相互转换的可能([第 4.2.6 节](04.2.md))。 如果你有多个类型需要定义,可以使用因式分解关键字的方式,例如: @@ -308,14 +308,14 @@ type ( ## 4.2.5 Go 程序的一般结构 -下面的程序可以被顺利编译但什么都做不了,不过这很好地展示了一个 Go 程序的首选结构。这种结构并没有被强制要求,编译器也不关心 main 函数在前还是变量的声明在前,但使用统一的结构能够在从上至下阅读 Go 代码时有更好的体验。 +下面的程序可以被顺利编译但什么都做不了,不过这很好地展示了一个 Go 程序的首选结构。这种结构并没有被强制要求,编译器也不关心 `main()` 函数在前还是变量的声明在前,但使用统一的结构能够在从上至下阅读 Go 代码时有更好的体验。 所有的结构将在这一章或接下来的章节中进一步地解释说明,但总体思路如下: -- 在完成包的 import 之后,开始对常量、变量和类型的定义或声明。 -- 如果存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。 -- 如果当前包是 main 包,则定义 main 函数。 -- 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。 +- 在完成包的 `import` 之后,开始对常量、变量和类型的定义或声明。 +- 如果存在 `init()` 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。 +- 如果当前包是 `main` 包,则定义 `main()` 函数。 +- 然后定义其余的函数,首先是类型的方法,接着是按照 `main()` 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。 示例 4.4 [gotemplate.go](examples/chapter_4/gotemplate.go) @@ -353,10 +353,10 @@ func Func1() { // exported function Func1 Go 程序的执行(程序启动)顺序如下: -1. 按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程: +1. 按顺序导入所有被 `main` 包引用的其它包,然后在每个包中执行如下流程: 2. 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。 -3. 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。 -4. 在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。 +3. 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 `init()` 函数的话,则调用该函数。 +4. 在完成这一切之后,`main` 也执行同样的过程,最后调用 `main()` 函数开始执行程序。 ## 4.2.6 类型转换 @@ -375,7 +375,7 @@ a := 5.0 b := int(a) ``` -但这只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 int16 转换为 int32)。当从一个取值范围较大的转换到取值范围较小的类型时(例如将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失(截断)的情况。当编译器捕捉到非法的类型转换时会引发编译时错误,否则将引发运行时错误。 +但这只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 `int16` 转换为 `int32`)。当从一个取值范围较大的转换到取值范围较小的类型时(例如将 `int32` 转换为 `int16` 或将 `float32` 转换为 `int`),会发生精度丢失(截断)的情况。当编译器捕捉到非法的类型转换时会引发编译时错误,否则将引发运行时错误。 具有相同底层类型的变量之间可以相互转换: @@ -387,7 +387,7 @@ d := IZ(c) ## 4.2.7 Go 命名规范 -干净、可读的代码和简洁性是 Go 追求的主要目标。通过 gofmt 来强制实现统一的代码风格。Go 语言中对象的命名也应该是简洁且有意义的。像 Java 和 Python 中那样使用混合着大小写和下划线的冗长的名称会严重降低代码的可读性。名称不需要指出自己所属的包,因为在调用的时候会使用包名作为限定符。返回某个对象的函数或方法的名称一般都是使用名词,没有 `Get...` 之类的字符,如果是用于修改某个对象,则使用 `SetName`。有必须要的话可以使用大小写混合的方式,如 MixedCaps 或 mixedCaps,而不是使用下划线来分割多个名称。 +干净、可读的代码和简洁性是 Go 追求的主要目标。通过 gofmt 来强制实现统一的代码风格。Go 语言中对象的命名也应该是简洁且有意义的。像 Java 和 Python 中那样使用混合着大小写和下划线的冗长的名称会严重降低代码的可读性。名称不需要指出自己所属的包,因为在调用的时候会使用包名作为限定符。返回某个对象的函数或方法的名称一般都是使用名词,没有 `Get...` 之类的字符,如果是用于修改某个对象,则使用 `SetName()`。有必须要的话可以使用大小写混合的方式,如 `MixedCaps()` 或 `mixedCaps()`,而不是使用下划线来分割多个名称。 ## 链接 diff --git a/eBook/04.3.md b/eBook/04.3.md index addf50e..f3f834c 100644 --- a/eBook/04.3.md +++ b/eBook/04.3.md @@ -27,7 +27,7 @@ f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 i - 正确的做法:`const c1 = 2/3` - 错误的做法:`const c2 = getNumber()` // 引发构建错误: `getNumber() used as value` -**因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()。** +**因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:`len()`。** 数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出: @@ -64,7 +64,7 @@ const ( ) ``` -现在,数字 0、1 和 2 分别代表未知性别、女性和男性。这些枚举值可以用于测试某个变量或常量的实际值,比如使用 switch/case 结构 (第 5.3 节). +现在,数字 `0`、`1` 和 `2` 分别代表未知性别、女性和男性。这些枚举值可以用于测试某个变量或常量的实际值,比如使用 switch/case 结构([第 5.3 节](.\05.3.md))。 在这个例子中,`iota` 可以被用作枚举值: @@ -116,9 +116,9 @@ const ( ) ``` -( **译者注:关于 iota 的使用涉及到非常复杂多样的情况,这里作者解释的并不清晰,因为很难对 iota 的用法进行直观的文字描述。如希望进一步了解,请观看视频教程 [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming) [第四课:常量与运算符](https://github.com/Unknwon/go-fundamental-programming/blob/master/lectures/lecture4.md)** ) +( **译者注:关于 `iota` 的使用涉及到非常复杂多样的情况,这里作者解释的并不清晰,因为很难对 `iota` 的用法进行直观的文字描述。如希望进一步了解,请观看视频教程 [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming) [第四课:常量与运算符](https://github.com/Unknwon/go-fundamental-programming/blob/master/lectures/lecture4.md)** ) -`iota` 也可以用在表达式中,如:`iota + 50`。在每遇到一个新的常量块或单个常量声明时, `iota` 都会重置为 0( **简单地讲,每遇到一次 const 关键字,iota 就重置为 0** )。 +`iota` 也可以用在表达式中,如:`iota + 50`。在每遇到一个新的常量块或单个常量声明时, `iota` 都会重置为 0( **简单地讲,每遇到一次 const 关键字,`iota` 就重置为 0** )。 当然,常量之所以为常量就是恒定不变的量,因此我们无法在程序运行过程中修改它的值;如果你在代码中试图修改常量的值则会引发编译错误。 diff --git a/eBook/04.4.md b/eBook/04.4.md index cb3d2dc..54b861d 100644 --- a/eBook/04.4.md +++ b/eBook/04.4.md @@ -6,7 +6,7 @@ 需要注意的是,Go 和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。Go 为什么要选择这么做呢? -首先,它是为了避免像 C 语言中那样含糊不清的声明形式,例如:`int* a, b;`。在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写(你可以在 [Go 语言的声明语法](http://blog.golang.org/2010/07/gos-declaration-syntax.html) 页面找到有关于这个话题的更多讨论)。 +首先,它是为了避免像 C 语言中那样含糊不清的声明形式,例如:`int* a, b;`。在这个例子中,只有 `a` 是指针而 `b` 不是。如果你想要这两个变量都是指针,则需要将它们分开书写(你可以在 [Go 语言的声明语法](http://blog.golang.org/2010/07/gos-declaration-syntax.html) 页面找到有关于这个话题的更多讨论)。 而在 Go 中,则可以很轻松地将它们都声明为指针类型: @@ -36,7 +36,7 @@ var ( 这种因式分解关键字的写法一般用于声明全局变量。 -当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。 +当一个变量被声明之后,系统自动赋予它该类型的零值:`int` 为 `0`,`float32(64)` 为 `0.0`,bool 为 `false`,`string` 为空字符串,指针为 `nil`。记住,所有的内存在 Go 中都是经过初始化的。 变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips` 和 `startDate`。 @@ -44,7 +44,7 @@ var ( 一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。 -在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。在第 5 章,我们将会学习到像 if 和 for 这些控制结构,而在这些结构中声明的变量的作用域只在相应的代码块内。一般情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)判断。 +在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。在 [第 5 章](05.0.md),我们将会学习到像 `if` 和 `for` 这些控制结构,而在这些结构中声明的变量的作用域只在相应的代码块内。一般情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)判断。 尽管变量的标识符必须是唯一的,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。 @@ -57,7 +57,7 @@ a = 15 b = false ``` -一般情况下,当变量a和变量b之间类型相同时,才能进行如`a = b`的赋值。 +一般情况下,当变量a和变量b之间类型相同时,才能进行如 `a = b` 的赋值。 声明与赋值(初始化)语句也可以组合起来。 @@ -113,7 +113,7 @@ var ( a := 1 ``` -下面这个例子展示了如何通过`runtime`包在运行时获取所在的操作系统类型,以及如何通过 `os` 包中的函数 `os.Getenv()` 来获取环境变量中的值,并保存到 string 类型的局部变量 path 中。 +下面这个例子展示了如何通过 `runtime` 包在运行时获取所在的操作系统类型,以及如何通过 `os` 包中的函数 `os.Getenv()` 来获取环境变量中的值,并保存到 `string` 类型的局部变量 `path` 中。 示例 4.5 [goos.go](examples/chapter_4/goos.go) @@ -136,44 +136,45 @@ func main() { 如果你在 Windows 下运行这段代码,则会输出 `The operating system is: windows` 以及相应的环境变量的值;如果你在 Linux 下运行这段代码,则会输出 `The operating system is: linux` 以及相应的的环境变量的值。 -这里用到了 `Printf` 的格式化输出的功能(第 4.4.3 节)。 +这里用到了 `Printf` 的格式化输出的功能([第 4.4.3 节](.\04.4.md))。 ## 4.4.2 值类型和引用类型 -程序中所用到的内存在计算机中使用一堆箱子来表示(这也是人们在讲解它的时候的画法),这些箱子被称为 “ 字 ”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位(4 字节)或 64 位(8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。 +程序中所用到的内存在计算机中使用一堆箱子来表示(这也是人们在讲解它的时候的画法),这些箱子被称为“字”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位(4 字节)或 64 位(8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。 -所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值: +所有像 `int`、`float`、`bool` 和 `string` 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值: -![](images/4.4.2_fig4.1.jpg?raw=true) + -另外,像数组(第 7 章)和结构(第 10 章)这些复合类型也是值类型。 +另外,像数组([第 7 章](.\07.0.md))和结构([第 10 章](.\10.0md))这些复合类型也是值类型。 -当使用等号 `=` 将一个变量的值赋值给另一个变量时,如:`j = i`,实际上是在内存中将 i 的值进行了拷贝: +当使用等号 `=` 将一个变量的值赋值给另一个变量时,如:`j = i`,实际上是在内存中将 `i` 的值进行了拷贝: -![](images/4.4.2_fig4.2.jpg?raw=true) + -你可以通过 &i 来获取变量 i 的内存地址(第 4.9 节),例如:0xf840000040(每次的地址都可能不一样)。值类型的变量的值存储在栈中。 +你可以通过 `&i` 来获取变量 `i` 的内存地址([第 4.9 节](.\04.9.md)),例如:`0xf840000040`(每次的地址都可能不一样)。值类型的变量的值存储在栈中。 内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。 更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。 -一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。 +一个引用类型的变量 `r1` 存储的是 `r1` 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。 -![](images/4.4.2_fig4.3.jpg?raw=true) + -这个内存地址被称之为指针(你可以从上图中很清晰地看到,第 4.9 节将会详细说明),这个指针实际上也被存在另外的某一个字中。 +这个内存地址被称之为指针(你可以从上图中很清晰地看到,[第 4.9 节](.\04.9.md) 将会详细说明),这个指针实际上也被存在另外的某一个字中。 同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。 当使用赋值语句 `r2 = r1` 时,只有引用(地址)被复制。 -如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。 +如果 `r1` 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,`r2` 也会受到影响。 -在 Go 语言中,指针(第 4.9 节)属于引用类型,其它的引用类型还包括 slices(第 7 章),maps(第 8 章)和 channel(第 13 章)。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。 +在 Go 语言中,指针([第 4.9 节](.\04.9.md))属于引用类型,其它的引用类型还包括 slices([第 7 章](07.0.md)),maps([第 8 章](08.0.md))和 channel([第 13 章](13.0.md))。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。 ## 4.4.3 打印 -函数 `Printf` 可以在 fmt 包外部使用,这是因为它以大写字母 P 开头,该函数主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数: + +函数 `Printf` 可以在 `fmt` 包外部使用,这是因为它以大写字母 P 开头,该函数主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数: ```go func Printf(format string, list of variables to be printed) @@ -197,7 +198,7 @@ fmt.Print("Hello:", 23) 我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,而这个时候再在 Example 4.4.1 的最后一个声明语句写上 `var` 关键字就显得有些多余了,因此我们可以将它们简写为 `a := 50` 或 `b := false`。 -a 和 b 的类型(int 和 bool)将由编译器自动推断。 +`a` 和 `b` 的类型(`int` 和 `bool`)将由编译器自动推断。 这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 `:=` 可以高效地创建一个新的变量,称之为初始化声明。 @@ -205,9 +206,9 @@ a 和 b 的类型(int 和 bool)将由编译器自动推断。 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:`a := 20` 就是不被允许的,编译器会提示错误 `no new variables on left side of :=`,但是 `a = 20` 是可以的,因为这是给相同的变量赋予一个新的值。 -如果你在定义变量 a 之前使用它,则会得到编译错误 `undefined: a`。 +如果你在定义变量 `a` 之前使用它,则会得到编译错误 `undefined: a`。 -如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a: +如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 `a`: ```go func main() { @@ -218,7 +219,7 @@ func main() { 尝试编译这段代码将得到错误 `a declared and not used`。 -此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 `fmt.Println("hello, world", a)` 会移除错误。 +此外,单纯地给 `a` 赋值也是不够的,这个值必须被使用,所以使用 `fmt.Println("hello, world", a)` 会移除错误。 但是全局变量是允许声明但不使用。 @@ -238,13 +239,13 @@ var a, b, c int a, b, c = 5, 7, "abc" ``` -上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用: +上面这行假设了变量 `a`,`b` 和 `c` 都已经被声明,否则的话应该这样使用: ```go a, b, c := 5, 7, "abc" ``` -右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 `5`, b 的值是 `7`,c 的值是 `"abc"`。 +右边的这些值以相同的顺序赋值给左边的变量,所以 `a` 的值是 `5`, `b` 的值是 `7`,`c` 的值是 `"abc"`。 这被称为 **并行** 或 **同时** 赋值。 @@ -260,9 +261,9 @@ a, b, c := 5, 7, "abc" ## 4.4.5 init 函数 -变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。 +变量除了可以在全局声明中初始化,也可以在 `init()` 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 `main()` 函数高。 -每个源文件可以包含多个 init 函数,同一个源文件中的 init函数会按照从上到下的顺序执行,如果一个包有多个源文件包含 init 函数的话,则官方鼓励但不保证以文件名的顺序调用。初始化总是以单线程并且按照包的依赖关系顺序执行。 +每个源文件可以包含多个 `init()` 函数,同一个源文件中的 `init()` 函数会按照从上到下的顺序执行,如果一个包有多个源文件包含 `init()` 函数的话,则官方鼓励但不保证以文件名的顺序调用。初始化总是以单线程并且按照包的依赖关系顺序执行。 一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。 @@ -280,9 +281,9 @@ func init() { } ``` -在它的 init 函数中计算变量 Pi 的初始值。 +在它的 `init()` 函数中计算变量 `Pi` 的初始值。 -示例 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 trans(需要init.go目录为./trans/init.go)并且使用到了变量 Pi: +示例 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 `trans`(需要 `init.go` 目录为 `./trans/init.go` )并且使用到了变量 `Pi`: ```go package main @@ -299,7 +300,7 @@ func main() { } ``` -init 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 `backend()`: +`init()` 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 `backend()`: ```go func init() { diff --git a/eBook/04.5.md b/eBook/04.5.md index 0f1fc0b..2714245 100644 --- a/eBook/04.5.md +++ b/eBook/04.5.md @@ -18,7 +18,7 @@ 两个类型相同的值可以使用相等 `==` 或者不等 `!=` 运算符来进行比较并获得一个布尔型的值。 -当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的值的类型相同的情况下才可以使用。 +当相等运算符两边的值是完全相同的值的时候会返回 `true`,否则返回 `false`,并且只有在两个的值的类型相同的情况下才可以使用。 示例: @@ -28,7 +28,7 @@ aVar == 5 -> false aVar == 10 -> true ``` -当不等运算符两边的值是不同的时候会返回 true,否则返回 false。 +当不等运算符两边的值是不同的时候会返回 `true`,否则返回 `false`。 示例: @@ -38,11 +38,11 @@ aVar != 5 -> true aVar != 10 -> false ``` -Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface,第 11 章),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。 +Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface,[第 11 章](11.0.md)),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。 布尔型的常量和变量也可以通过和逻辑运算符(非 `!`、与 `&&`、或 `||`)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。 -逻辑值可以被用于条件结构中的条件语句(第 5 章),以便测试某个条件是否满足。另外,与 `&&`、或 `||` 与相等 `==` 或不等 `!=` 属于二元运算符,而非 `!` 属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。 +逻辑值可以被用于条件结构中的条件语句([第 5 章](05.0.md)),以便测试某个条件是否满足。另外,与 `&&`、或 `||` 与相等 `==` 或不等 `!=` 属于二元运算符,而非 `!` 属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。 Go 语言中包含以下逻辑运算符: @@ -64,7 +64,7 @@ F && T -> false F && F -> false ``` -只有当两边的值都为 true 的时候,和运算符的结果才是 true。 +只有当两边的值都为 `true` 的时候,和运算符的结果才是 `true`。 或运算符:`||` @@ -75,9 +75,9 @@ F || T -> true F || F -> false ``` -只有当两边的值都为 false 的时候,或运算符的结果才是 false,其中任意一边的值为 true 就能够使得该表达式的结果为 true。 +只有当两边的值都为 `false` 的时候,或运算符的结果才是 `false`,其中任意一边的值为 `true` 就能够使得该表达式的结果为 `true`。 -在 Go 语言中,&& 和 || 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(&& 左边的值为 false,|| 左边的值为 true),运算符右边的表达式将不会被执行。利用这个性质,如果你有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧以减少不必要的运算。 +在 Go 语言中,`&&` 和 `||` 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(`&&` 左边的值为 `false`,`||` 左边的值为 `true`),运算符右边的表达式将不会被执行。利用这个性质,如果你有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧以减少不必要的运算。 利用括号同样可以升级某个表达式的运算优先级。 @@ -85,7 +85,7 @@ F || F -> false 布尔值(以及任何结果为布尔值的表达式)最常用在条件结构的条件语句中,例如:if、for 和 switch 结构(第 5 章)。 -对于布尔值的好的命名能够很好地提升代码的可读性,例如以 `is` 或者 `Is` 开头的 `isSorted`、`isFinished`、`isVisible`,使用这样的命名能够在阅读代码的获得阅读正常语句一样的良好体验,例如标准库中的 `unicode.IsDigit(ch)`(第 4.5.5 节)。 +对于布尔值的好的命名能够很好地提升代码的可读性,例如以 `is` 或者 `Is` 开头的 `isSorted`、`isFinished`、`isVisible`,使用这样的命名能够在阅读代码的获得阅读正常语句一样的良好体验,例如标准库中的 `unicode.IsDigit(ch)`([第 4.5.5 节](04.5.md))。 ## 4.5.2 数字类型 @@ -93,47 +93,47 @@ F || F -> false Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码(详情参见 [二的补码](http://en.wikipedia.org/wiki/Two's_complement) 页面)。 -Go 也有基于架构的类型,例如:int、uint 和 uintptr。 +Go 也有基于架构的类型,例如:`int`、`uint` 和 `uintptr`。 这些类型的长度都是根据运行程序所在的操作系统类型所决定的: - `int` 和 `uint` 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。 - `uintptr` 的长度被设定为足够存放一个指针即可。 -Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。 +Go 语言中没有 float 类型。(Go语言中只有 `float32` 和 `float64`)没有 double 类型。 与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来: 整数: -- int8(-128 -> 127) -- int16(-32768 -> 32767) -- int32(-2,147,483,648 -> 2,147,483,647) -- int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807) +- `int8`(-128 -> 127) +- `int16`(-32768 -> 32767) +- `int32`(-2,147,483,648 -> 2,147,483,647) +- `int64`(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807) 无符号整数: -- uint8(0 -> 255) -- uint16(0 -> 65,535) -- uint32(0 -> 4,294,967,295) -- uint64(0 -> 18,446,744,073,709,551,615) +- `uint8`(0 -> 255) +- `uint16`(0 -> 65,535) +- `uint32`(0 -> 4,294,967,295) +- `uint64`(0 -> 18,446,744,073,709,551,615) 浮点型(IEEE-754 标准): -- float32(+- 1e-45 -> +- 3.4 * 1e38) -- float64(+- 5 * 1e-324 -> 107 * 1e308) +- `float32`(+- 1e-45 -> +- 3.4 * 1e38) +- `float64`(+- 5 * 1e-324 -> 107 * 1e308) -int 型是计算最快的一种类型。 +`int` 型是计算最快的一种类型。 -整型的零值为 0,浮点型的零值为 0.0。 +整型的零值为 `0`,浮点型的零值为 `0.0`。 -float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 `==` 或者 `!=` 来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算。 +`float32` 精确到小数点后 7 位,`float64` 精确到小数点后 15 位。由于精确度的缘故,你在使用 `==` 或者 `!=` 来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算。 -你应该尽可能地使用 float64,因为 `math` 包中所有有关数学运算的函数都会要求接收这个类型。 +你应该尽可能地使用 `float64`,因为 `math` 包中所有有关数学运算的函数都会要求接收这个类型。 -你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。 +你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:`0xFF`),以及使用 `e` 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。 -你可以使用 `a := uint64(0)` 来同时完成类型转换和赋值操作,这样 a 的类型就是 uint64。 +你可以使用 `a := uint64(0)` 来同时完成类型转换和赋值操作,这样 `a` 的类型就是 `uint64`。 Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译): @@ -153,9 +153,9 @@ func main() { 如果你尝试编译该程序,则将得到编译错误 `cannot use a + a (type int) as type int32 in assignment`。 -同样地,int16 也不能够被隐式转换为 int32。 +同样地,`int16` 也不能够被隐式转换为 `int32`。 -下面这个程序展示了通过显式转换来避免这个问题(第 4.2 节)。 +下面这个程序展示了通过显式转换来避免这个问题([第 4.2 节](04.2.md))。 示例 4.9 [casting.go](examples/chapter_4/casting.go) @@ -191,7 +191,7 @@ func main() { **数字值转换** -当进行类似 `a32bitInt = int32(a32Float)` 的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 int 型转换为 int8: +当进行类似 `a32bitInt = int32(a32Float)` 的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 `int` 型转换为 `int8`: ```go func Uint8FromInt(n int) (uint8, error) { @@ -202,7 +202,7 @@ func Uint8FromInt(n int) (uint8, error) { } ``` -或者安全地从 float64 转换为 int: +或者安全地从 `float64` 转换为 `int`: ```go func IntFromFloat64(x float64) int { @@ -217,9 +217,9 @@ func IntFromFloat64(x float64) int { } ``` -不过如果你实际存的数字超出你要转换到的类型的取值范围的话,则会引发 panic(第 13.2 节)。 +不过如果你实际存的数字超出你要转换到的类型的取值范围的话,则会引发 `panic`([第 13.2 节](.\13.2.md))。 -**问题 4.1** int 和 int64 是相同的类型吗? +**问题 4.1** `int` 和 `int64` 是相同的类型吗? ### 4.5.2.2 复数 @@ -238,7 +238,7 @@ fmt.Printf("The value is: %v", c1) // 输出: 5 + 10i ``` -如果 `re` 和 `im` 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得: +如果 `re` 和 `im` 的类型均为 `float32`,那么类型为 `complex64` 的复数 `c` 可以通过以下方式来获得: ```go c = complex(re, im) @@ -248,7 +248,7 @@ c = complex(re, im) 在使用格式化说明符时,可以使用 `%v` 来表示复数,但当你希望只表示其中的一个部分的时候需要使用 `%f`。 -复数支持和其它数字类型一样的运算。当你使用等号 `==` 或者不等号 `!=` 对复数进行比较运算时,注意对精确度的把握。`cmath` 包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。 +复数支持和其它数字类型一样的运算。当你使用等号 `==` 或者不等号 `!=` 对复数进行比较运算时,注意对精确度的把握。`cmath` 包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 `complex128` 作为计算类型,因为相关函数都使用这个类型的参数。 ### 4.5.2.3 位运算 @@ -260,7 +260,7 @@ c = complex(re, im) - 按位与 `&`: - 对应位置上的值经过和运算结果,具体参见和运算符,第 4.5.1 节,并将 T(true)替换为 1,将 F(false)替换为 0 + 对应位置上的值经过和运算结果,具体参见和运算符(第 4.5.1 节),并将 T (true) 替换为 `1`,将 F (false) 替换为 `0` 1 & 1 -> 1 1 & 0 -> 0 @@ -269,7 +269,7 @@ c = complex(re, im) - 按位或 `|`: - 对应位置上的值经过或运算结果,具体参见或运算符,第 4.5.1 节,并将 T(true)替换为 1,将 F(false)替换为 0 + 对应位置上的值经过或运算结果,具体参见或运算符(第 4.5.1 节),并将 T (true) 替换为 `1`,将 F (false) 替换为 `0` 1 | 1 -> 1 1 | 0 -> 1 @@ -285,17 +285,14 @@ c = complex(re, im) 0 ^ 1 -> 1 0 ^ 0 -> 0 -- 位清除 `&^`:将指定位置上的值设置为 0。 +- 位清除 `&^`:将指定位置上的值设置为 `0`。 ```go package main - import "fmt" - func main() { var x uint8 = 15 var y uint8 = 4 - fmt.Printf("%08b\n", x &^ y); // 00001011 } ``` @@ -304,14 +301,14 @@ c = complex(re, im) - 按位补足 `^`: - 该运算符与异或运算符一同使用,即 `m^x`,对于无符号 x 使用“全部位设置为 1”,对于有符号 x 时使用 `m=-1`。例如: + 该运算符与异或运算符一同使用,即 `m^x`,对于无符号 `x` 使用 “全部位设置为 1” 的规则,对于有符号 `x` 时使用 `m=-1`。例如: ^10 = -01 ^ 10 = -11 - 位左移 `<<`: - 用法:`bitP << n`。 - - `bitP` 的位向左移动 n 位,右侧空白部分使用 0 填充;如果 n 等于 2,则结果是 2 的相应倍数,即 2 的 n 次方。例如: + - `bitP` 的位向左移动 `n` 位,右侧空白部分使用 0 填充;如果 `n` 等于 2,则结果是 2 的相应倍数,即 2 的 `n` 次方。例如: 1 << 10 // 等于 1 KB 1 << 20 // 等于 1 MB @@ -320,13 +317,13 @@ c = complex(re, im) - 位右移 `>>`: - 用法:`bitP >> n`。 - - `bitP` 的位向右移动 n 位,左侧空白部分使用 0 填充;如果 n 等于 2,则结果是当前值除以 2 的 n 次方。 + - `bitP` 的位向右移动 `n` 位,左侧空白部分使用 0 填充;如果 `n` 等于 2,则结果是当前值除以 2 的 n 次方。 当希望把结果赋值给第一个操作数时,可以简写为 `a <<= 2` 或者 `b ^= a & 0xffffffff`。 **位左移常见实现存储单位的用例** -使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举: +使用位左移与 `iota` 计数配合可优雅地实现存储单位的常量枚举: ```go type ByteSize float64 @@ -376,9 +373,9 @@ b3 := 10 > 5 // b3 is true 取余运算符只能作用于整数:`9 % 4 -> 1`。 -整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);第 13 章将会详细讲解如何正确地处理此类情况。 +整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);[第 13 章](13.0.md) 将会详细讲解如何正确地处理此类情况。 -浮点数除以 0.0 会返回一个无穷尽的结果,使用 `+Inf` 表示。 +浮点数除以 `0.0` 会返回一个无穷尽的结果,使用 `+Inf` 表示。 **练习 4.4** 尝试编译 [divby0.go](exercises/chapter_4/divby0.go)。 @@ -391,7 +388,7 @@ b3 := 10 > 5 // b3 is true 同时,带有 `++` 和 `--` 的只能作为语句,而非表达式,因此 `n = i++` 这种写法是无效的,其它像 `f(i++)` 或者 `a[i]=b[i++]` 这些可以用于 C、C++ 和 Java 中的写法在 Go 中也是不允许的。 -在运算时 **溢出** 不会产生错误,Go 会简单地将超出位数抛弃。如果你需要范围无限大的整数或者有理数(意味着只被限制于计算机内存),你可以使用标准库中的 `big` 包,该包提供了类似 `big.Int` 和 `big.Rat` 这样的类型(第 9.4 节)。 +在运算时 **溢出** 不会产生错误,Go 会简单地将超出位数抛弃。如果你需要范围无限大的整数或者有理数(意味着只被限制于计算机内存),你可以使用标准库中的 `big` 包,该包提供了类似 `big.Int` 和 `big.Rat` 这样的类型([第 9.4 节](09.4.md))。 ### 4.5.2.6 随机数 @@ -431,7 +428,7 @@ func main() { 1951252986 / 2029250107 / 762911244 / 1372544545 / 591415086 / / 3 / 0 / 6 / 4 / 2 /22.10 / 65.77 / 65.89 / 16.85 / 75.56 / 46.90 / 55.24 / 55.95 / 25.58 / 70.61 / -函数 `rand.Float32` 和 `rand.Float64` 返回介于 [0.0, 1.0) 之间的伪随机数,其中包括 0.0 但不包括 1.0。函数 `rand.Intn` 返回介于 [0, n) 之间的伪随机数。 +函数 `rand.Float32` 和 `rand.Float64` 返回介于 $[0.0, 1.0)$ 之间的伪随机数,其中包括 `0.0` 但不包括 `1.0`。函数 `rand.Intn` 返回介于 $[0, n)$ 之间的伪随机数。 你可以使用 `rand.Seed(value)` 函数来提供伪随机数的生成种子,一般情况下都会使用当前时间的纳秒级数字(第 4.8 节)。 @@ -454,7 +451,7 @@ func main() { 当你在使用某个类型时,你可以给它起另一个名字,然后你就可以在你的代码中使用新的名字(用于简化名称或解决名称冲突)。 -在 `type TZ int` 中,TZ 就是 int 类型的新名称(用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据。 +在 `type TZ int` 中,`TZ` 就是 `int` 类型的新名称(用于表示程序中的时区),然后就可以使用 `TZ` 来操作 `int` 类型的数据。 示例 4.11 [type.go](examples/chapter_4/type.go) @@ -471,7 +468,7 @@ func main() { } ``` -实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法(第 10 章);TZ 可以自定义一个方法用来输出更加人性化的时区信息。 +实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法([第 10 章](.\10.0.md));`TZ` 可以自定义一个方法用来输出更加人性化的时区信息。 **练习 4.5** 定义一个 `string` 的类型别名 `Rope`,并声明一个该类型的变量。 @@ -479,7 +476,7 @@ func main() { 严格来说,这并不是 Go 语言的一个类型,字符只是整数的特殊用例。`byte` 类型是 `uint8` 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:`var ch byte = 'A'`;字符使用单引号括起来。 -在 ASCII 码表中,A 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的: +在 ASCII 码表中,`'A'` 的值是 `65`,而使用 16 进制表示则为 `41`,所以下面的写法是等效的: ```go var ch byte = 65 或 var ch byte = '\x41' @@ -489,7 +486,7 @@ var ch byte = 65 或 var ch byte = '\x41' 另外一种可能的写法是 `\` 后面紧跟着长度为 3 的 8 进制数,例如:`\377`。 -不过 Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 `rune` 也是 Go 当中的一个类型,并且是 `int32` 的别名。 +不过 Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 `int` 来表示。在文档中,一般使用格式 `U+hhhh` 来表示,其中 `h` 表示一个 16 进制数。其实 `rune` 也是 Go 当中的一个类型,并且是 `int32` 的别名。 在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 `\u` 或者 `\U`。 @@ -514,7 +511,7 @@ fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point 41 - 3B2 - 101234 U+0041 - U+03B2 - U+101234 -格式化说明符 `%c` 用于表示字符;当和字符配合使用时,`%v` 或 `%d` 会输出用于表示该字符的整数;`%U` 输出格式为 U+hhhh 的字符串(另一个示例见第 5.4.4 节)。 +格式化说明符 `%c` 用于表示字符;当和字符配合使用时,`%v` 或 `%d` 会输出用于表示该字符的整数;`%U` 输出格式为 `U+hhhh` 的字符串(另一个示例见[第 5.4.4 节](.\05.4.md))。 包 `unicode` 包含了一些针对测试字符的非常有用的函数(其中 `ch` 代表字符): @@ -522,7 +519,7 @@ fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point - 判断是否为数字:`unicode.IsDigit(ch)` - 判断是否为空白符号:`unicode.IsSpace(ch)` -这些函数返回一个布尔值。包 `utf8` 拥有更多与 rune 类型相关的函数。 +这些函数返回单个布尔值。包 `utf8` 拥有更多与 `rune` 类型相关的函数。 ( **译者注:关于类型的相关讲解,可参考视频教程 《Go编程基础》 第 3 课:[类型与变量](https://github.com/Unknwon/go-fundamental-programming/blob/master/lectures/lecture3.md)** ) diff --git a/eBook/04.6.md b/eBook/04.6.md index 9ffa78c..1e36e46 100644 --- a/eBook/04.6.md +++ b/eBook/04.6.md @@ -1,6 +1,6 @@ # 4.6 字符串 -字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。UTF-8 是被广泛使用的编码格式,是文本文件的标准编码,其它包括 XML 和 JSON 在内,也都使用该编码。由于该编码对占用字节长度的不定性,Go 中的字符串里面的字符也可能根据需要占用 1 至 4 个字节(示例见第 4.6 节),这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。 +字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。UTF-8 是被广泛使用的编码格式,是文本文件的标准编码,其它包括 XML 和 JSON 在内,也都使用该编码。由于该编码对占用字节长度的不定性,Go 中的字符串里面的字符也可能根据需要占用 1 至 4 个字节(示例见[第 4.6 节](04.6.md)),这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。 字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组。 @@ -22,7 +22,7 @@ Go 支持以下 2 种形式的字面值: `This is a raw string \n` 中的 `\n\` 会被原样输出。 -和 C/C++不一样,Go 中的字符串是根据长度限定,而非特殊字符`\0`。 +和 C/C++不一样,Go 中的字符串是根据长度限定,而非特殊字符 `\0`。 `string` 类型的零值为长度为零的字符串,即空字符串 `""`。 @@ -30,8 +30,8 @@ Go 支持以下 2 种形式的字面值: 字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 `[]` 内写入索引,索引从 0 开始计数: -- 字符串 str 的第 1 个字节:`str[0]` -- 第 i 个字节:`str[i - 1]` +- 字符串 `str` 的第 1 个字节:`str[0]` +- 第 `i` 个字节:`str[i - 1]` - 最后 1 个字节:`str[len(str)-1]` 需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。 @@ -61,13 +61,13 @@ s += "world!" fmt.Println(s) //输出 “hello, world!” ``` -在循环中使用加号 `+` 拼接字符串并不是最高效的做法,更好的办法是使用函数 `strings.Join()`(第 4.7.10 节),有没有更好的办法了?有!使用字节缓冲(`bytes.Buffer`)拼接更加给力(第 7.2.6 节)! +在循环中使用加号 `+` 拼接字符串并不是最高效的做法,更好的办法是使用函数 `strings.Join()`([第 4.7.10 节](04.7.md)),有没有更好的办法了?有!使用字节缓冲(`bytes.Buffer`)拼接更加给力([第 7.2.6 节](07.2.md))! -在第 7 章,我们会讲到通过将字符串看作是字节(byte)的切片(slice)来实现对其标准索引法的操作。会在第 5.4.1 节中讲到的 for 循环只会根据索引返回字符串中的纯字节,而在第 5.4.4 节(以及第 7.6.1 节的示例)将会展示如何使用 for-range 循环来实现对 Unicode 字符串的迭代操作。在下一节,我们会学习到许多有关字符串操作的函数和方法,同时 `fmt` 包中的 `fmt.Sprint(x)` 也可以格式化生成并返回你所需要的字符串(第 4.4.3 节)。 +在[第 7 章](07.0.md),我们会讲到通过将字符串看作是字节 (`byte`) 的切片 (slice) 来实现对其标准索引法的操作。会在[第 5.4.1 节](05.4.md) 中讲到的 `for` 循环只会根据索引返回字符串中的纯字节,而在[第 5.4.4 节](.\05.4.md)(以及[第 7.6.1 节](07.6.md) 的示例)将会展示如何使用 for-range 循环来实现对 Unicode 字符串的迭代操作。在下一节,我们会学习到许多有关字符串操作的函数和方法,同时 `fmt` 包中的 `fmt.Sprint(x)` 也可以格式化生成并返回你所需要的字符串([第 4.4.3 节](04.3.md))。 **练习 4.6** [count_characters.go](exercises/chapter_4/count_characters.go) -创建一个用于统计字节和字符(rune)的程序,并对字符串 `asSASA ddd dsjkdsjs dk` 进行分析,然后再分析 `asSASA ddd dsjkdsjsこん dk`,最后解释两者不同的原因(提示:使用 `unicode/utf8` 包)。 +创建一个用于统计字节和字符 (rune) 的程序,并对字符串 `asSASA ddd dsjkdsjs dk` 进行分析,然后再分析 `asSASA ddd dsjkdsjsこん dk`,最后解释两者不同的原因(提示:使用 `unicode/utf8` 包)。 ## 链接 diff --git a/eBook/04.7.md b/eBook/04.7.md index 3350e0b..a742390 100644 --- a/eBook/04.7.md +++ b/eBook/04.7.md @@ -4,13 +4,13 @@ ## 4.7.1 前缀和后缀 -`HasPrefix` 判断字符串 `s` 是否以 `prefix` 开头: +`HasPrefix()` 判断字符串 `s` 是否以 `prefix` 开头: ```go strings.HasPrefix(s, prefix string) bool ``` -`HasSuffix` 判断字符串 `s` 是否以 `suffix` 结尾: +`HasSuffix()` 判断字符串 `s` 是否以 `suffix` 结尾: ```go strings.HasSuffix(s, suffix string) bool @@ -41,7 +41,7 @@ func main() { ## 4.7.2 字符串包含关系 -`Contains` 判断字符串 `s` 是否包含 `substr`: +`Contains()` 判断字符串 `s` 是否包含 `substr`: ```go strings.Contains(s, substr string) bool @@ -49,13 +49,13 @@ strings.Contains(s, substr string) bool ## 4.7.3 判断子字符串或字符在父字符串中出现的位置(索引) -`Index` 返回字符串 `str` 在字符串 `s` 中的索引(`str` 的第一个字符的索引),-1 表示字符串 `s` 不包含字符串 `str`: +`Index()` 返回字符串 `str` 在字符串 `s` 中的索引(`str` 的第一个字符的索引),`-1` 表示字符串 `s` 不包含字符串 `str`: ```go strings.Index(s, str string) int ``` -`LastIndex` 返回字符串 `str` 在字符串 `s` 中最后出现位置的索引(`str` 的第一个字符的索引),-1 表示字符串 `s` 不包含字符串 `str`: +`LastIndex()` 返回字符串 `str` 在字符串 `s` 中最后出现位置的索引(`str` 的第一个字符的索引),`-1` 表示字符串 `s` 不包含字符串 `str`: ```go strings.LastIndex(s, str string) int @@ -105,7 +105,7 @@ func main() { ## 4.7.4 字符串替换 -`Replace` 用于将字符串 `str` 中的前 `n` 个字符串 `old` 替换为字符串 `new`,并返回一个新的字符串,如果 `n = -1` 则替换所有字符串 `old` 为字符串 `new`: +`Replace()` 用于将字符串 `str` 中的前 `n` 个字符串 `old` 替换为字符串 `new`,并返回一个新的字符串,如果 `n = -1` 则替换所有字符串 `old` 为字符串 `new`: ```go strings.Replace(str, old, new string, n int) string @@ -113,7 +113,7 @@ strings.Replace(str, old, new string, n int) string ## 4.7.5 统计字符串出现次数 -`Count` 用于计算字符串 `str` 在字符串 `s` 中出现的非重叠次数: +`Count()` 用于计算字符串 `str` 在字符串 `s` 中出现的非重叠次数: ```go strings.Count(s, str string) int @@ -148,7 +148,7 @@ func main() { ## 4.7.6 重复字符串 -`Repeat` 用于重复 `count` 次字符串 `s` 并返回一个新的字符串: +`Repeat()` 用于重复 `count` 次字符串 `s` 并返回一个新的字符串: ```go strings.Repeat(s, count int) string @@ -179,13 +179,14 @@ func main() { ## 4.7.7 修改字符串大小写 -`ToLower` 将字符串中的 Unicode 字符全部转换为相应的小写字符: + +`ToLower()` 将字符串中的 Unicode 字符全部转换为相应的小写字符: ```go strings.ToLower(s) string ``` -`ToUpper` 将字符串中的 Unicode 字符全部转换为相应的大写字符: +`ToUpper()` 将字符串中的 Unicode 字符全部转换为相应的大写字符: ```go strings.ToUpper(s) string @@ -222,7 +223,7 @@ func main() { ## 4.7.8 修剪字符串 -你可以使用 `strings.TrimSpace(s)` 来剔除字符串开头和结尾的空白符号;如果你想要剔除指定字符,则可以使用 `strings.Trim(s, "cut")` 来将开头和结尾的 `cut` 去除掉。该函数的第二个参数可以包含任何字符,如果你只想剔除开头或者结尾的字符串,则可以使用 `TrimLeft` 或者 `TrimRight` 来实现。 +你可以使用 `strings.TrimSpace(s)` 来剔除字符串开头和结尾的空白符号;如果你想要剔除指定字符,则可以使用 `strings.Trim(s, "cut")` 来将开头和结尾的 `cut` 去除掉。该函数的第二个参数可以包含任何字符,如果你只想剔除开头或者结尾的字符串,则可以使用 `TrimLeft()` 或者 `TrimRight()` 来实现。 ## 4.7.9 分割字符串 @@ -234,7 +235,7 @@ func main() { ## 4.7.10 拼接 slice 到字符串 -`Join` 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串: +`Join()` 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串: ```go strings.Join(sl []string, sep string) string @@ -284,28 +285,28 @@ func main() { 函数 `strings.NewReader(str)` 用于生成一个 `Reader` 并读取字符串中的内容,然后返回指向该 `Reader` 的指针,从其它类型读取内容的函数还有: -- `Read()` 从 []byte 中读取内容。 -- `ReadByte()` 和 `ReadRune()` 从字符串中读取下一个 byte 或者 rune。 +- `Read()` 从 `[]byte` 中读取内容。 +- `ReadByte()` 和 `ReadRune()` 从字符串中读取下一个 `byte` 或者 `rune`。 ## 4.7.12 字符串与其它类型的转换 与字符串相关的类型转换都是通过 `strconv` 包实现的。 -该包包含了一些变量用于获取程序运行的操作系统平台下 int 类型所占的位数,如:`strconv.IntSize`。 +该包包含了一些变量用于获取程序运行的操作系统平台下 `int` 类型所占的位数,如:`strconv.IntSize`。 -任何类型 **T** 转换为字符串总是成功的。 +任何类型 **`T`** 转换为字符串总是成功的。 针对从数字类型转换到字符串,Go 提供了以下函数: -- `strconv.Itoa(i int) string` 返回数字 i 所表示的字符串类型的十进制数。 -- `strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string` 将 64 位浮点型的数字转换为字符串,其中 `fmt` 表示格式(其值可以是 `'b'`、`'e'`、`'f'` 或 `'g'`),`prec` 表示精度,`bitSize` 则使用 32 表示 float32,用 64 表示 float64。 +- `strconv.Itoa(i int) string` 返回数字 `i` 所表示的字符串类型的十进制数。 +- `strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string` 将 64 位浮点型的数字转换为字符串,其中 `fmt` 表示格式(其值可以是 `'b'`、`'e'`、`'f'` 或 `'g'`),`prec` 表示精度,`bitSize` 则使用 32 表示 `float32`,用 64 表示 `float64`。 -将字符串转换为其它类型 **tp** 并不总是可能的,可能会在运行时抛出错误 `parsing "…": invalid argument`。 +将字符串转换为其它类型 **`tp`** 并不总是可能的,可能会在运行时抛出错误 `parsing "…": invalid argument`。 针对从字符串类型转换为数字类型,Go 提供了以下函数: -- `strconv.Atoi(s string) (i int, err error)` 将字符串转换为 int 型。 -- `strconv.ParseFloat(s string, bitSize int) (f float64, err error)` 将字符串转换为 float64 型。 +- `strconv.Atoi(s string) (i int, err error)` 将字符串转换为 `int` 型。 +- `strconv.ParseFloat(s string, bitSize int) (f float64, err error)` 将字符串转换为 `float64` 型。 利用多返回值的特性,这些函数会返回 2 个值,第 1 个是转换后的结果(如果转换成功),第 2 个是可能出现的错误,因此,我们一般使用以下形式来进行从字符串到其它类型的转换: @@ -347,7 +348,7 @@ func main() { The integer is: 666 The new string is: 671 -在第 5.1 节,我们将会利用 if 语句来对可能出现的错误进行分类处理。 +在第 5.1 节,我们将会利用 `if` 语句来对可能出现的错误进行分类处理。 更多有关该包的讨论,请参阅 [官方文档](http://golang.org/pkg/strconv/)( **译者注:国内用户可访问 [该页面](http://docs.studygolang.com/pkg/strconv/)** )。 diff --git a/eBook/04.8.md b/eBook/04.8.md index 768cc5b..ed7f8bf 100644 --- a/eBook/04.8.md +++ b/eBook/04.8.md @@ -4,11 +4,11 @@ 当前时间可以使用 `time.Now()` 获取,或者使用 `t.Day()`、`t.Minute()` 等等来获取时间的一部分;你甚至可以自定义时间格式化字符串,例如: `fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year())` 将会输出 `21.07.2011`。 -Duration 类型表示两个连续时刻所相差的纳秒数,类型为 int64。Location 类型映射某个时区的时间,UTC 表示通用协调世界时间。 +`Duration` 类型表示两个连续时刻所相差的纳秒数,类型为 `int64`。`Location` 类型映射某个时区的时间,UTC 表示通用协调世界时间。 -包中的一个预定义函数 `func (t Time) Format(layout string) string` 可以根据一个格式化字符串来将一个时间 t 转换为相应格式的字符串,你可以使用一些预定义的格式,如:`time.ANSIC` 或 `time.RFC822`。 +包中的一个预定义函数 `func (t Time) Format(layout string) string` 可以根据一个格式化字符串来将一个时间 `t` 转换为相应格式的字符串,你可以使用一些预定义的格式,如:`time.ANSIC` 或 `time.RFC822`。 -一般的格式化设计是通过对于一个标准时间的格式化描述来展现的,这听起来很奇怪(02 Jan 2006 15:04 是 Go 语言的诞生时间且自定义格式化时必须以此时间为基准),但看下面这个例子你就会一目了然: +一般的格式化设计是通过对于一个标准时间的格式化描述来展现的,这听起来很奇怪(`02 Jan 2006 15:04` 是 Go 语言的诞生时间且自定义格式化时必须以此时间为基准),但看下面这个例子你就会一目了然: ```go @@ -56,7 +56,7 @@ func main() { 输出的结果已经写在每行 `//` 的后面。 -如果你需要在应用程序在经过一定时间或周期执行某项任务(事件处理的特例),则可以使用 `time.After` 或者 `time.Ticker`:我们将会在第 14.5 节讨论这些有趣的事情。 另外,`time.Sleep(d Duration)` 可以实现对某个进程(实质上是 goroutine)时长为 d 的暂停。 +如果你需要在应用程序在经过一定时间或周期执行某项任务(事件处理的特例),则可以使用 `time.After()` 或者 `time.Ticker`:我们将会在 [第 14.5 节](14.5.md) 讨论这些有趣的事情。 另外,`time.Sleep(d Duration)` 可以实现对某个进程(实质上是 goroutine)时长为 `d` 的暂停。 ## 链接 diff --git a/eBook/04.9.md b/eBook/04.9.md index e73dd00..6b3652d 100644 --- a/eBook/04.9.md +++ b/eBook/04.9.md @@ -15,19 +15,19 @@ var i1 = 5 fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1) ``` -这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 `i1`:此处使用 *int 表示。如果我们想调用指针 intP,我们可以这样声明它: +这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 `i1`:此处使用 `*int` 表示。如果我们想调用指针 `intP`,我们可以这样声明它: ```go var intP *int ``` -然后使用 `intP = &i1` 是合法的,此时 intP 指向 i1。 +然后使用 `intP = &i1` 是合法的,此时 `intP` 指向 `i1`。 (指针的格式化标识符为 `%p`) -intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变量 i1。 +`intP` 存储了 `i1` 的内存地址;它指向了 `i1` 的位置,它引用了变量 `i1`。 -**一个指针变量可以指向任何一个值的内存地址** 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。 +**一个指针变量可以指向任何一个值的内存地址** 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上 `*` 号(前缀)来获取指针所指向的内容,这里的 `*` 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。 当一个指针被定义后没有分配到任何变量时,它的值为 `nil`。 @@ -39,7 +39,7 @@ intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变 符号 * 可以放在一个指针前,如 `*intP`,那么它将得到这个指针指向地址上所存储的值;这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。 -对于任何一个变量 var, 如下表达式都是正确的:`var == *(&var)`。 +对于任何一个变量 `var`, 如下表达式都是正确的:`var == *(&var)`。 现在,我们应当能理解 pointer.go 的全部内容及其输出: @@ -64,11 +64,11 @@ func main() { 我们可以用下图来表示内存使用的情况: -![](images/4.9_fig4.4.png?raw=true) + -程序 string_pointer.go 为我们展示了指针对string的例子。 +程序 string_pointer.go 为我们展示了指针对 `string` 的例子。 -它展示了分配一个新的值给 *p 并且更改这个变量自己的值(这里是一个字符串)。 +它展示了分配一个新的值给 `*p` 并且更改这个变量自己的值(这里是一个字符串)。 示例 4.22 [string_pointer.go](examples/chapter_4/string_pointer.go) @@ -91,11 +91,11 @@ func main() { Here is the string *p: ciao Here is the string s: ciao -通过对 *p 赋另一个值来更改“对象”,这样 s 也会随之更改。 +通过对 `*p` 赋另一个值来更改“对象”,这样 `s` 也会随之更改。 内存示意图如下: -![](images/4.9_fig4.5.png?raw=true) + **注意事项** @@ -133,7 +133,7 @@ func main() { // runtime error: invalid memory address or nil pointer dereference ``` -**问题 4.2** 列举 Go 语言中 * 号的所有用法。 +**问题 4.2** 列举 Go 语言中 `*` 号的所有用法。 ## 链接 diff --git a/eBook/05.0.md b/eBook/05.0.md index 84bbf80..e3aa05a 100644 --- a/eBook/05.0.md +++ b/eBook/05.0.md @@ -1,14 +1,14 @@ # 5.0 控制结构 -到目前为止,我们看到的 Go 程序都是从 main() 函数开始执行,然后按顺序执行该函数体中的代码。但我们经常会需要只有在满足一些特定情况时才执行某些代码,也就是说在代码里进行条件判断。针对这种需求,Go 提供了下面这些条件结构和分支结构: +到目前为止,我们看到的 Go 程序都是从 `main()` 函数开始执行,然后按顺序执行该函数体中的代码。但我们经常会需要只有在满足一些特定情况时才执行某些代码,也就是说在代码里进行条件判断。针对这种需求,Go 提供了下面这些条件结构和分支结构: -- if-else 结构 -- switch 结构 -- select 结构,用于 channel 的选择(第 14.4 节) +- `if`-`else` 结构 +- `switch` 结构 +- `select` 结构,用于 channel 的选择([第 14.4 节](14.4.md)) 可以使用迭代或循环结构来重复执行一次或多次某段代码(任务): -- for (range) 结构 +- `for` (`range`) 结构 一些如 `break` 和 `continue` 这样的关键字可以用于中途改变循环的状态。 diff --git a/eBook/05.1.md b/eBook/05.1.md index fa91564..9794b7c 100644 --- a/eBook/05.1.md +++ b/eBook/05.1.md @@ -8,7 +8,7 @@ if condition { } ``` -如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行。if 和 else 后的两个代码块是相互独立的分支,只可能执行其中一个。 +如果存在第二个分支,则可以在上面代码的基础上添加 `else` 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行。`if` 和 `else` 后的两个代码块是相互独立的分支,只可能执行其中一个。 ```go if condition { @@ -30,11 +30,11 @@ if condition1 { } ``` -else-if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else-if 结构。如果你必须使用这种形式,则把尽可能先满足的条件放在前面。 +else-if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 `if` 后面加入太多的 else-if 结构。如果你必须使用这种形式,则把尽可能先满足的条件放在前面。 -即使当代码块之间只有一条语句时,大括号也不可被省略(尽管有些人并不赞成,但这还是符合了软件工程原则的主流做法)。 +即使当代码块之间只有一条语句时,大括号也不可被省略(尽管有些人并不赞成,但这还是符合了软件工程原则的主流做法)。 -关键字 if 和 else 之后的左大括号 `{` 必须和关键字在同一行,如果你使用了 else-if 结构,则前段代码块的右大括号 `}` 必须和 else-if 关键字在同一行。这两条规则都是被编译器强制规定的。 +关键字 `if` 和 `else` 之后的左大括号 `{` 必须和关键字在同一行,如果你使用了 else-if 结构,则前段代码块的右大括号 `}` 必须和 else-if 关键字在同一行。这两条规则都是被编译器强制规定的。 非法的 Go 代码: @@ -45,9 +45,9 @@ else { // 无效的 } ``` -要注意的是,在你使用 `gofmt` 格式化代码之后,每个分支内的代码都会缩进 4 个或 8 个空格,或者是 1 个 tab,并且右大括号与对应的 if 关键字垂直对齐。 +要注意的是,在你使用 `gofmt` 格式化代码之后,每个分支内的代码都会缩进 4 个或 8 个空格,或者是 1 个 tab,并且右大括号与对应的 `if` 关键字垂直对齐。 -在有些情况下,条件语句两侧的括号是可以被省略的;当条件比较复杂时,则可以使用括号让代码更易读。条件允许是符合条件,需使用 &&、|| 或 !,你可以使用括号来提升某个表达式的运算优先级,并提高代码的可读性。 +在有些情况下,条件语句两侧的括号是可以被省略的;当条件比较复杂时,则可以使用括号让代码更易读。条件允许是符合条件,需使用 `&&`、`||` 或 `!`,你可以使用括号来提升某个表达式的运算优先级,并提高代码的可读性。 一种可能用到条件语句的场景是测试变量的值,在不同的情况执行不同的语句,不过将在第 5.3 节讲到的 switch 结构会更适合这种情况。 @@ -74,7 +74,7 @@ func main() { 这种做法一般都用在测试 `true` 或者有利条件时,但你也可以使用取反 `!` 来判断值的相反结果,如:`if !bool1` 或者 `if !(condition)`。后者的括号大多数情况下是必须的,如这种情况:`if !(var1 == var2)`。 -当 if 结构内有 break、continue、goto 或者 return 语句时,Go 代码的常见写法是省略 else 部分(另见第 5.2 节)。无论满足哪个条件都会返回 x 或者 y 时,一般使用以下写法: +当 if 结构内有 `break`、`continue`、`goto` 或者 `return` 语句时,Go 代码的常见写法是省略 `else` 部分(另见[第 5.2 节](05.2.md))。无论满足哪个条件都会返回 `x` 或者 `y` 时,一般使用以下写法: ```go if condition { @@ -83,52 +83,60 @@ if condition { return y ``` -**注意事项** 不要同时在 if-else 结构的两个分支里都使用 return 语句,这将导致编译报错 `function ends without a return statement`(你可以认为这是一个编译器的 Bug 或者特性)。( **译者注:该问题已经在 Go 1.1 中被修复或者说改进** ) +**注意事项** 不要同时在 if-else 结构的两个分支里都使用 `return` 语句,这将导致编译报错 `function ends without a return statement`(你可以认为这是一个编译器的 Bug 或者特性)。( **译者注:该问题已经在 Go 1.1 中被修复或者说改进** ) 这里举一些有用的例子: 1. 判断一个字符串是否为空: - `if str == "" { ... }` - `if len(str) == 0 {...}` -2. 判断运行 Go 程序的操作系统类型,这可以通过常量 `runtime.GOOS` 来判断(第 2.2 节)。 +2. 判断运行 Go 程序的操作系统类型,这可以通过常量 `runtime.GOOS` 来判断([第 2.2 节](02.2.md))。 - if runtime.GOOS == "windows" { - . .. - } else { // Unix-like - . .. + ```go + if runtime.GOOS == "windows" { + . .. + } else { // Unix-like + . .. + } + ``` + + 这段代码一般被放在 `init()` 函数中执行。这儿还有一段示例来演示如何根据操作系统来决定输入结束的提示: + + ```go + var prompt = "Enter a digit, e.g. 3 "+ "or %s to quit." + + func init() { + if runtime.GOOS == "windows" { + prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter") + } else { //Unix-like + prompt = fmt.Sprintf(prompt, "Ctrl+D") } - - 这段代码一般被放在 init() 函数中执行。这儿还有一段示例来演示如何根据操作系统来决定输入结束的提示: - - var prompt = "Enter a digit, e.g. 3 "+ "or %s to quit." - - func init() { - if runtime.GOOS == "windows" { - prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter") - } else { //Unix-like - prompt = fmt.Sprintf(prompt, "Ctrl+D") - } - } - + } + ``` + 3. 函数 `Abs()` 用于返回一个整型数字的绝对值: - func Abs(x int) int { - if x < 0 { - return -x - } - return x - } + ```go + func Abs(x int) int { + if x < 0 { + return -x + } + return x + } + ``` 4. `isGreater` 用于比较两个整型数字的大小: - func isGreater(x, y int) bool { - if x > y { - return true - } - return false + ```go + func isGreater(x, y int) bool { + if x > y { + return true } + return false + } + ``` -在第四种情况中,if 可以包含一个初始化语句(如:给一个变量赋值)。这种写法具有固定的格式(在初始化语句后方必须加上分号): +在第四种情况中,`if` 可以包含一个初始化语句(如:给一个变量赋值)。这种写法具有固定的格式(在初始化语句后方必须加上分号): ```go if initialization; condition { @@ -153,7 +161,7 @@ if val := 10; val > max { } ``` -但要注意的是,使用简短方式 `:=` 声明的变量的作用域只存在于 if 结构中(在 if 结构的大括号之间,如果使用 if-else 结构则在 else 代码块中变量也会存在)。如果变量在 if 结构之前就已经存在,那么在 if 结构中,该变量原来的值会被隐藏。最简单的解决方案就是不要在初始化语句中声明变量(见 5.2 节的例 3 了解更多)。 +但要注意的是,使用简短方式 `:=` 声明的变量的作用域只存在于 `if` 结构中(在 `if` 结构的大括号之间,如果使用 if-else 结构则在 `else` 代码块中变量也会存在)。如果变量在 `if` 结构之前就已经存在,那么在 `if` 结构中,该变量原来的值会被隐藏。最简单的解决方案就是不要在初始化语句中声明变量(见[5.2 节的例 3](05.2.md) 了解更多)。 示例 5.2 [ifelse.go](examples/chapter_5/ifelse.go) @@ -186,7 +194,7 @@ func main() { first is 5 or greater cond is not greater than 10 -下面的代码片段展示了如何通过在初始化语句中获取函数 `process()` 的返回值,并在条件语句中作为判定条件来决定是否执行 if 结构中的代码: +下面的代码片段展示了如何通过在初始化语句中获取函数 `process()` 的返回值,并在条件语句中作为判定条件来决定是否执行 `if` 结构中的代码: ```go if value := process(data); value > max { diff --git a/eBook/05.2.md b/eBook/05.2.md index ec4864f..6e3a7ad 100644 --- a/eBook/05.2.md +++ b/eBook/05.2.md @@ -1,14 +1,14 @@ # 5.2 测试多返回值函数的错误 -Go 语言的函数经常使用两个返回值来表示执行是否成功:返回某个值以及 true 表示成功;返回零值(或 nil)和 false 表示失败(第 4.4 节)。当不使用 true 或 false 的时候,也可以使用一个 error 类型的变量来代替作为第二个返回值:成功执行的话,error 的值为 nil,否则就会包含相应的错误信息(Go 语言中的错误类型为 error: `var err error`,我们将会在第 13 章进行更多地讨论)。这样一来,就很明显需要用一个 if 语句来测试执行结果;由于其符号的原因,这样的形式又称之为 comma,ok 模式(pattern)。 +Go 语言的函数经常使用两个返回值来表示执行是否成功:返回某个值以及 `true` 表示成功;返回零值(或 `nil`)和 `false` 表示失败([第 4.4 节](04.4.md))。当不使用 `true` 或 `false` 的时候,也可以使用一个 `error` 类型的变量来代替作为第二个返回值:成功执行的话,`error` 的值为 `nil`,否则就会包含相应的错误信息(Go 语言中的错误类型为 `error`: `var err error`,我们将会在[第 13 章](13.0.md) 进行更多地讨论)。这样一来,就很明显需要用一个 `if` 语句来测试执行结果;由于其符号的原因,这样的形式又称之为“逗号 ok 模式”(comma, ok pattern)。 -在第 4.7 节的程序 `string_conversion.go` 中,函数 `strconv.Atoi` 的作用是将一个字符串转换为一个整数。之前我们忽略了相关的错误检查: +在[第 4.7 节](04.7.md) 的程序 [string_conversion.go](examples/chapter_4/string_conversion.go) 中,函数 `strconv.Atoi()` 的作用是将一个字符串转换为一个整数。之前我们忽略了相关的错误检查: ```go anInt, _ = strconv.Atoi(origStr) ``` -如果 origStr 不能被转换为整数,anInt 的值会变成 0 而 `_` 无视了错误,程序会继续运行。 +如果 `origStr` 不能被转换为整数,`anInt` 的值会变成 `0` 而 `_` 无视了错误,程序会继续运行。 这样做是非常不好的:程序应该在最接近的位置检查所有相关的错误,至少需要暗示用户有错误发生并对函数进行返回,甚至中断程序。 @@ -47,7 +47,7 @@ func main() { } ``` -这是测试 err 变量是否包含一个真正的错误(`if err != nil`)的习惯用法。如果确实存在错误,则会打印相应的错误信息然后通过 return 提前结束函数的执行。我们还可以使用携带返回值的 return 形式,例如 `return err`。这样一来,函数的调用者就可以检查函数执行过程中是否存在错误了。 +这是测试 `err` 变量是否包含一个真正的错误(`if err != nil`)的习惯用法。如果确实存在错误,则会打印相应的错误信息然后通过 `return` 提前结束函数的执行。我们还可以使用携带返回值的 `return` 形式,例如 `return err`。这样一来,函数的调用者就可以检查函数执行过程中是否存在错误了。 **习惯用法** @@ -60,7 +60,7 @@ if err != nil { // 未发生错误,继续执行: ``` -由于本例的函数调用者属于 main 函数,所以程序会直接停止运行。 +由于本例的函数调用者属于 `main` 函数,所以程序会直接停止运行。 如果我们想要在错误发生的同时终止程序的运行,我们可以使用 `os` 包的 `Exit` 函数: @@ -73,11 +73,11 @@ if err != nil { } ``` -(此处的退出代码 1 可以使用外部脚本获取到) +(此处的退出代码 `1` 可以使用外部脚本获取到) 有时候,你会发现这种习惯用法被连续重复地使用在某段代码中。 -当没有错误发生时,代码继续运行就是唯一要做的事情,所以 if 语句块后面不需要使用 else 分支。 +当没有错误发生时,代码继续运行就是唯一要做的事情,所以 `if` 语句块后面不需要使用 `else` 分支。 示例 2:我们尝试通过 `os.Open` 方法打开一个名为 `name` 的只读文件: @@ -90,9 +90,9 @@ doSomething(f) // 当没有错误发生时,文件对象被传入到某个函 doSomething ``` -**练习 5.1** 尝试改写 [string_conversion2.go](examples/chapter_5/string_conversion2.go) 中的代码,要求使用 `:=` 方法来对 err 进行赋值,哪些地方可以被修改? +**练习 5.1** 尝试改写 [string_conversion2.go](examples/chapter_5/string_conversion2.go) 中的代码,要求使用 `:=` 方法来对 `err` 进行赋值,哪些地方可以被修改? -示例 3:可以将错误的获取放置在 if 语句的初始化部分: +示例 3:可以将错误的获取放置在 `if` 语句的初始化部分: **习惯用法** @@ -103,7 +103,7 @@ if err := file.Chmod(0664); err != nil { } ``` -示例 4:或者将 ok-pattern 的获取放置在 if 语句的初始化部分,然后进行判断: +示例 4:或者将 ok-pattern 的获取放置在 `if` 语句的初始化部分,然后进行判断: **习惯用法** @@ -116,7 +116,6 @@ if value, ok := readData(); ok { **注意事项** 如果您像下面一样,没有为多返回值的函数准备足够的变量来存放结果: - ```go func mySqrt(f float64) (v float64, ok bool) { if f < 0 { return } // error case @@ -140,7 +139,7 @@ if ok { fmt.Println(t) } **注意事项 2** -当您将字符串转换为整数时,且确定转换一定能够成功时,可以将 `Atoi` 函数进行一层忽略错误的封装: +当您将字符串转换为整数时,且确定转换一定能够成功时,可以将 `Atoi()` 函数进行一层忽略错误的封装: ```go func atoi (s string) (n int) { @@ -149,13 +148,13 @@ func atoi (s string) (n int) { } ``` -实际上,`fmt` 包(第 4.4.3 节)最简单的打印函数也有 2 个返回值: +实际上,`fmt` 包([第 4.4.3 节](04.4.md))最简单的打印函数也有 2 个返回值: ```go count, err := fmt.Println(x) // number of bytes printed, nil or 0, error ``` -当打印到控制台时,可以将该函数返回的错误忽略;但当输出到文件流、网络流等具有不确定因素的输出对象时,应该始终检查是否有错误发生(另见练习 6.1b)。 +当打印到控制台时,可以将该函数返回的错误忽略;但当输出到文件流、网络流等具有不确定因素的输出对象时,应该始终检查是否有错误发生(另见[练习 6.1b](06.1.md))。 ## 链接 diff --git a/eBook/05.3.md b/eBook/05.3.md index a878465..7dd55e5 100644 --- a/eBook/05.3.md +++ b/eBook/05.3.md @@ -1,6 +1,6 @@ # 5.3 switch 结构 -相比较 C 和 Java 等其它语言而言,Go 语言中的 switch 结构使用上更加灵活。它接受任意形式的表达式: +相比较 C 和 Java 等其它语言而言,Go 语言中的 `switch` 结构使用上更加灵活。它接受任意形式的表达式: ```go switch var1 { @@ -13,13 +13,13 @@ switch var1 { } ``` -变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。前花括号 `{` 必须和 switch 关键字在同一行。 +变量 `var1` 可以是任何类型,而 `val1` 和 `val2` 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。前花括号 `{` 必须和 `switch` 关键字在同一行。 您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:`case val1, val2, val3`。 -每一个 `case` 分支都是唯一的,从上至下逐一测试,直到匹配为止。( Go 语言使用快速的查找算法来测试 switch 条件与 case 分支的匹配情况,直到算法匹配到某个 case 或者进入 default 条件为止。) +每一个 `case` 分支都是唯一的,从上至下逐一测试,直到匹配为止。( Go 语言使用快速的查找算法来测试 `switch` 条件与 `case` 分支的匹配情况,直到算法匹配到某个 `case` 或者进入 `default` 条件为止。) -一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个 switch 代码块,也就是说您不需要特别使用 `break` 语句来表示结束。 +一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个 `switch` 代码块,也就是说您不需要特别使用 `break` 语句来表示结束。 因此,程序也不会自动地去执行下一个分支的代码。如果在执行完每个分支的代码后,还希望继续执行后续分支的代码,可以使用 `fallthrough` 关键字来达到目的。 @@ -45,9 +45,9 @@ switch i { 在 `case ...:` 语句之后,您不需要使用花括号将多行语句括起来,但您可以在分支中进行任意形式的编码。当代码块只有一行时,可以直接放置在 `case` 语句之后。 -您同样可以使用 `return` 语句来提前结束代码块的执行。当您在 switch 语句块中使用 `return` 语句,并且您的函数是有返回值的,您还需要在 switch 之后添加相应的 `return` 语句以确保函数始终会返回。 +您同样可以使用 `return` 语句来提前结束代码块的执行。当您在 `switch` 语句块中使用 `return` 语句,并且您的函数是有返回值的,您还需要在 switch 之后添加相应的 `return` 语句以确保函数始终会返回。 -可选的 `default` 分支可以出现在任何顺序,但最好将它放在最后。它的作用类似与 `if-else` 语句中的 `else`,表示不符合任何已给出条件时,执行相关语句。 +可选的 `default` 分支可以出现在任何顺序,但最好将它放在最后。它的作用类似与 if-else 语句中的 `else`,表示不符合任何已给出条件时,执行相关语句。 示例 5.4 [switch1.go](examples/chapter_5/switch1.go): @@ -75,7 +75,7 @@ func main() { It's equal to 100 -在第 12.1 节,我们会使用 switch 语句判断从键盘输入的字符(详见第 12.2 节的 switch.go)。switch 语句的第二种形式是不提供任何被判断的值(实际上默认为判断是否为 true),然后在每个 case 分支中进行测试不同的条件。当任一分支的测试结果为 true 时,该分支的代码会被执行。这看起来非常像链式的 `if-else` 语句,但是在测试条件非常多的情况下,提供了可读性更好的书写方式。 +在第 12.1 节,我们会使用 `switch` 语句判断从键盘输入的字符(详见[第 12.2 节](12.2.md) 的 [switch.go](.\examples\chapter_12\switch.go))。`switch` 语句的第二种形式是不提供任何被判断的值(实际上默认为判断是否为 `true`),然后在每个 `case` 分支中进行测试不同的条件。当任一分支的测试结果为 `true` 时,该分支的代码会被执行。这看起来非常像链式的 if-else 语句,但是在测试条件非常多的情况下,提供了可读性更好的书写方式。 ```go switch { @@ -101,7 +101,7 @@ switch { } ``` -任何支持进行相等判断的类型都可以作为测试表达式的条件,包括 int、string、指针等。 +任何支持进行相等判断的类型都可以作为测试表达式的条件,包括 `int`、`string`、指针等。 示例 5.4 [switch2.go](examples/chapter_5/switch2.go): @@ -154,7 +154,7 @@ switch result := calculate(); { } ``` -在下面这个代码片段中,变量 a 和 b 被平行初始化,然后作为判断条件: +在下面这个代码片段中,变量 `a` 和 `b` 被平行初始化,然后作为判断条件: ```go switch a, b := x[i], y[j]; { @@ -164,7 +164,7 @@ switch a, b := x[i], y[j]; { } ``` -switch 语句还可以被用于 type-switch(详见第 11.4 节)来判断某个 interface 变量中实际存储的变量类型。 +`switch` 语句还可以被用于 type-switch(详见[第 11.4 节](11.4.md))来判断某个 `interface` 变量中实际存储的变量类型。 **问题 5.1:** @@ -195,7 +195,7 @@ switch 语句还可以被用于 type-switch(详见第 11.4 节)来判断某 **练习 5.2:** [season.go](exercises/chapter_5/season.go): -写一个 Season 函数,要求接受一个代表月份的数字,然后返回所代表月份所在季节的名称(不用考虑月份的日期)。 +写一个 `Season()` 函数,要求接受一个代表月份的数字,然后返回所代表月份所在季节的名称(不用考虑月份的日期)。 ## 链接 diff --git a/eBook/05.4.md b/eBook/05.4.md index 33c8a1f..ea54549 100644 --- a/eBook/05.4.md +++ b/eBook/05.4.md @@ -1,8 +1,8 @@ # 5.4 for 结构 -如果想要重复执行某些语句,Go 语言中您只有 for 结构可以使用。不要小看它,这个 for 结构比其它语言中的更为灵活。 +如果想要重复执行某些语句,Go 语言中您只有 `for` 结构可以使用。不要小看它,这个 `for` 结构比其它语言中的更为灵活。 -**注意事项** 其它许多语言中也没有发现和 do while 完全对等的 for 结构,可能是因为这种需求并不是那么强烈。 +**注意事项** 其它许多语言中也没有发现和 do-while 完全对等的 `for` 结构,可能是因为这种需求并不是那么强烈。 ## 5.4.1 基于计数器的迭代 @@ -32,7 +32,7 @@ func main() { This is the 3 iteration This is the 4 iteration -由花括号括起来的代码块会被重复执行已知次数,该次数是根据计数器(此例为 i)决定的。循环开始前,会执行且仅会执行一次初始化语句 `i := 0;`;这比在循环之前声明更为简短。紧接着的是条件语句 `i < 5;`,在每次循环开始前都会进行判断,一旦判断结果为 false,则退出循环体。最后一部分为修饰语句 `i++`,一般用于增加或减少计数器。 +由花括号括起来的代码块会被重复执行已知次数,该次数是根据计数器(此例为 `i`)决定的。循环开始前,会执行且仅会执行一次初始化语句 `i := 0;`;这比在循环之前声明更为简短。紧接着的是条件语句 `i < 5;`,在每次循环开始前都会进行判断,一旦判断结果为 `false`,则退出循环体。最后一部分为修饰语句 `i++`,一般用于增加或减少计数器。 这三部分组成的循环的头部,它们之间使用分号 `;` 相隔,但并不需要括号 `()` 将它们括起来。例如:`for (i = 0; i < 10; i++) { }`,这是无效的代码! @@ -46,7 +46,7 @@ func main() { for i, j := 0, N; i < j; i, j = i+1, j-1 {} ``` -这得益于 Go 语言具有的平行赋值的特性(可以查看第 7 章 string_reverse.go 中反转数组的示例)。 +这得益于 Go 语言具有的平行赋值的特性(可以查看[第 7 章](07.0.md) [string_reverse.go](.\examples\chapter_7\string_reverse.go) 中反转数组的示例)。 您可以将两个 for 循环嵌套起来: @@ -122,16 +122,16 @@ func main() { Character on position 7 is: ª Character on position 8 is: ž -如果我们打印 str 和 str2 的长度,会分别得到 27 和 9。 +如果我们打印 `str` 和 `str2` 的长度,会分别得到 `27` 和 `9`。 -由此我们可以发现,ASCII 编码的字符占用 1 个字节,既每个索引都指向不同的字符,而非 ASCII 编码的字符(占有 2 到 4 个字节)不能单纯地使用索引来判断是否为同一个字符。我们会在第 5.4.4 节解决这个问题。 +由此我们可以发现,ASCII 编码的字符占用 1 个字节,既每个索引都指向不同的字符,而非 ASCII 编码的字符(占有 2 到 4 个字节)不能单纯地使用索引来判断是否为同一个字符。我们会在[第 5.4.4 节](05.4.md) 解决这个问题。 ### 练习题 **练习 5.4** [for_loop.go](exercises/chapter_5/for_loop.go) -1. 使用 for 结构创建一个简单的循环。要求循环 15 次然后使用 fmt 包来打印计数器的值。 -2. 使用 goto 语句重写循环,要求不能使用 for 关键字。 +1. 使用 `for` 结构创建一个简单的循环。要求循环 15 次然后使用 `fmt` 包来打印计数器的值。 +2. 使用 `goto` 语句重写循环,要求不能使用 `for` 关键字。 **练习 5.5** [for_character.go](exercises/chapter_5/for_character.go) @@ -211,7 +211,7 @@ for t, err = p.Token(); err == nil; t, err = p.Token() { ## 5.4.4 for-range 结构 -这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合(包括数组和 map,详见第 7 和 8 章)。语法上很类似其它语言中 foreach 语句,但您依旧可以获得每次迭代所对应的索引。一般形式为:`for ix, val := range coll { }`。 +这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合(包括数组和 `map`,详见第 [7](07.0.md) 和 [8](08.0.md) 章)。语法上很类似其它语言中的 foreach 语句,但您依旧可以获得每次迭代所对应的索引。一般形式为:`for ix, val := range coll { }`。 要注意的是,`val` 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(**译者注:如果 `val` 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值**)。一个字符串是 Unicode 编码的字符(或称之为 `rune`)集合,因此您也可以用它迭代字符串: @@ -221,7 +221,7 @@ for pos, char := range str { } ``` -每个 rune 字符和索引在 for-range 循环中是一一对应的。它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。 +每个 `rune` 字符和索引在 for-range 循环中是一一对应的。它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。 示例 5.9 [range_string.go](examples/chapter_5/range_string.go): @@ -311,9 +311,9 @@ index int(rune) rune char bytes 15 35486 U+8A9E '語' E8 AA 9E ``` -请将输出结果和 Listing 5.7(for_string.go)进行对比。 +请将输出结果和 Listing 5.7([for_string.go](examples/chapter_5/for_string.go))进行对比。 -我们可以看到,常用英文字符使用 1 个字节表示,而汉字(**译者注:严格来说,“Chinese: 日本語”的Chinese应该是Japanese**)使用 3 个字符表示。 +我们可以看到,常用英文字符使用 1 个字节表示,而汉字(**译者注:严格来说,“Chinese: 日本語”的 Chinese 应该是 Japanese**)使用 3 个字符表示。 **练习 5.9** 以下程序的输出结果是什么? diff --git a/eBook/05.5.md b/eBook/05.5.md index 200b2a1..9572947 100644 --- a/eBook/05.5.md +++ b/eBook/05.5.md @@ -1,6 +1,6 @@ -# 5.5 Break 与 continue +# 5.5 break 与 continue -您可以使用 break 语句重写 for2.go 的代码: +您可以使用 `break` 语句重写 [for2.go](examples/chapter_5/for2.go) 的代码: 示例 5.10 [for3.go](examples/chapter_5/for3.go): @@ -14,11 +14,11 @@ for { } ``` -因此每次迭代都会对条件进行检查(i < 0),以此判断是否需要停止循环。如果退出条件满足,则使用 break 语句退出循环。 +因此每次迭代都会对条件进行检查(`i < 0`),以此判断是否需要停止循环。如果退出条件满足,则使用 `break` 语句退出循环。 -一个 break 的作用范围为该语句出现后的最内部的结构,它可以被用于任何形式的 for 循环(计数器、条件判断等)。但在 switch 或 select 语句中(详见第 13 章),break 语句的作用结果是跳过整个代码块,执行后续的代码。 +一个 `break` 的作用范围为该语句出现后的最内部的结构,它可以被用于任何形式的 `for` 循环(计数器、条件判断等)。但在 `switch` 或 `select` 语句中(详见[第 13 章](13.0.md)),`break` 语句的作用结果是跳过整个代码块,执行后续的代码。 -下面的示例中包含了嵌套的循环体(for4.go),break 只会退出最内层的循环: +下面的示例中包含了嵌套的循环体(for4.go),`break` 只会退出最内层的循环: 示例 5.11 [for4.go](examples/chapter_5/for4.go): @@ -42,7 +42,7 @@ func main() { 012345 012345 012345 -关键字 continue 忽略剩余的循环体而直接进入下一次循环的过程,但不是无条件执行下一次循环,执行之前依旧需要满足循环的判断条件。 +关键字 `continue` 忽略剩余的循环体而直接进入下一次循环的过程,但不是无条件执行下一次循环,执行之前依旧需要满足循环的判断条件。 示例 5.12 [for5.go](examples/chapter_5/for5.go): @@ -66,9 +66,9 @@ func main() { 0 1 2 3 4 6 7 8 9 ``` -显然,5 被跳过了。 +显然,`5` 被跳过了。 -另外,关键字 continue 只能被用于 for 循环中。 +另外,关键字 `continue` 只能被用于 `for` 循环中。 ## 链接 diff --git a/eBook/05.6.md b/eBook/05.6.md index f217545..8ceb880 100644 --- a/eBook/05.6.md +++ b/eBook/05.6.md @@ -1,6 +1,6 @@ # 5.6 标签与 goto -for、switch 或 select 语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(`:`)结尾的单词(gofmt 会将后续代码自动移至下一行)。 +`for`、`switch` 或 `select` 语句都可以配合标签 (label) 形式的标识符使用,即某一行第一个以冒号 (`:`) 结尾的单词(gofmt 会将后续代码自动移至下一行)。 示例 5.13 [for6.go](examples/chapter_5/for6.go): @@ -26,9 +26,9 @@ LABEL1: } ``` -本例中,continue 语句指向 LABEL1,当执行到该语句的时候,就会跳转到 LABEL1 标签的位置。 +本例中,`continue` 语句指向 `LABEL1`,当执行到该语句的时候,就会跳转到 `LABEL1` 标签的位置。 -您可以看到当 j==4 和 j==5 的时候,没有任何输出:标签的作用对象为外部循环,因此 i 会直接变成下一个循环的值,而此时 j 的值就被重设为 0,即它的初始值。如果将 continue 改为 break,则不会只退出内层循环,而是直接退出外层循环了。另外,还可以使用 goto 语句和标签配合使用来模拟循环。 +您可以看到当 `j==4` 和 `j==5` 的时候,没有任何输出:标签的作用对象为外部循环,因此 `i` 会直接变成下一个循环的值,而此时 `j` 的值就被重设为 `0`,即它的初始值。如果将 `continue` 改为 `break`,则不会只退出内层循环,而是直接退出外层循环了。另外,还可以使用 `goto` 语句和标签配合使用来模拟循环。 示例 5.14 [goto.go](examples/chapter_5/goto.go): @@ -49,15 +49,15 @@ func main() { 上面的代码会输出 `01234`。 -使用逆向的 goto 会很快导致意大利面条式的代码,所以不应当使用而选择更好的替代方案。 +使用逆向的 `goto` 会很快导致意大利面条式的代码,所以不应当使用而选择更好的替代方案。 -**特别注意** 使用标签和 goto 语句是不被鼓励的:它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。 +**特别注意** 使用标签和 `goto` 语句是不被鼓励的:它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。 -一个建议使用 goto 语句的示例会在第 15.1 章的 simple_tcp_server.go 中出现:示例中在发生读取错误时,使用 goto 来跳出无限读取循环并关闭相应的客户端链接。 +一个建议使用 `goto` 语句的示例会在[第 15.1 章](15.1.md) 的 [simple_tcp_server.go](.\examples\chapter_15\simple_tcp_server.go) 中出现:示例中在发生读取错误时,使用 goto 来跳出无限读取循环并关闭相应的客户端链接。 定义但未使用标签会导致编译错误:`label … defined and not used`。 -如果您必须使用 goto,应当只使用正序的标签(标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现定义新变量的语句,否则会导致编译失败。 +如果您必须使用 `goto`,应当只使用正序的标签(标签位于 `goto` 语句之后),但注意标签和 `goto` 语句之间不能出现定义新变量的语句,否则会导致编译失败。 示例 5.15 [goto2.go](examples/chapter_5/got2o.go): @@ -77,7 +77,7 @@ func main() { } ``` -**问题 5.3** 请描述下面 for 循环的输出: +**问题 5.3** 请描述下面 `for` 循环的输出: 1.