Co-authored-by: Joe Chen <jc@unknwon.io>
This commit is contained in:
Haigang Zhou
2022-05-09 21:42:14 +08:00
committed by GitHub
parent 82026084b3
commit f5dae8f559
16 changed files with 273 additions and 267 deletions

View File

@@ -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"
```
但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 constvar 和 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 buildgo 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``printlnprint("ABC")``println("ABC")``println(i)`(带一个变量 i)。
单纯地打印一个字符串或变量甚至可以使用预定义的方法来实现,如:`print``printlnprint("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()`,而不是使用下划线来分割多个名称。
## 链接