Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4-5 章 #844

Merged
merged 26 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions eBook/04.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Go 的源文件以 `.go` 为后缀名存储在计算机中,这些文件名均由小写字母组成,如 `scanner.go` 。如果文件名由多个部分组成,则使用下划线 `_` 对它们进行分隔,如 `scanner_test.go` 。文件名不包含空格或其他特殊字符。

一个源文件可以包含任意多行的代码,Go 本身没有对源文件的大小进行限制。

你会发现在 Go 代码中的几乎所有东西都有一个名称或标识符。另外,Go 语言也是区分大小写的,这与 C 家族中的其它语言相同。有效的标识符必须以字母(可以使用任何 UTF-8 编码的字符或 `_`)开头,然后紧跟着 0 个或多个字符或 Unicode 数字,如:X56、group1、_x23、i、өԑ12。

以下是无效的标识符:
Expand Down Expand Up @@ -58,7 +58,7 @@ Go 的源文件以 `.go` 为后缀名存储在计算机中,这些文件名均

之所以刻意地将 Go 代码中的关键字保持的这么少,是为了简化在编译过程第一步中的代码解析。和其它语言一样,关键字不能够作标识符使用。

除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数(第 6.5 节),它们的作用都将在接下来的章节中进行进一步地讲解。
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数([第 6.5 节](06.5.md)),它们的作用都将在接下来的章节中进行进一步地讲解。

<table class="table table-bordered table-striped table-condensed">
<tr>
Expand Down
70 changes: 35 additions & 35 deletions eBook/04.2.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions eBook/04.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 i
- 正确的做法:`const c1 = 2/3`
- 错误的做法:`const c2 = getNumber()` // 引发构建错误: `getNumber() used as value`

**因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()。**
**因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:`len()`。**

数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出:

Expand Down Expand Up @@ -64,7 +64,7 @@ const (
)
```

现在,数字 0、12 分别代表未知性别、女性和男性。这些枚举值可以用于测试某个变量或常量的实际值,比如使用 switch/case 结构 (第 5.3 节).
现在,数字 `0`、`1``2` 分别代表未知性别、女性和男性。这些枚举值可以用于测试某个变量或常量的实际值,比如使用 switch/case 结构([第 5.3 节](.\05.3.md))。

在这个例子中,`iota` 可以被用作枚举值:

Expand Down Expand Up @@ -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** )。

当然,常量之所以为常量就是恒定不变的量,因此我们无法在程序运行过程中修改它的值;如果你在代码中试图修改常量的值则会引发编译错误。

Expand Down
61 changes: 31 additions & 30 deletions eBook/04.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 中,则可以很轻松地将它们都声明为指针类型:

Expand Down Expand Up @@ -36,15 +36,15 @@ var (

这种因式分解关键字的写法一般用于声明全局变量。

当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 falsestring 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。
当一个变量被声明之后,系统自动赋予它该类型的零值:`int``0`,`float32(64)``0.0`,bool 为 `false`,`string` 为空字符串,指针为 `nil`。记住,所有的内存在 Go 中都是经过初始化的。

变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips` 和 `startDate`。

但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写(第 4.2 节:可见性规则)。

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。在第 5 章,我们将会学习到像 if 和 for 这些控制结构,而在这些结构中声明的变量的作用域只在相应的代码块内。一般情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)判断。
在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。在 [第 5 章](05.0.md),我们将会学习到像 `if``for` 这些控制结构,而在这些结构中声明的变量的作用域只在相应的代码块内。一般情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)判断。

尽管变量的标识符必须是唯一的,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。

Expand All @@ -57,7 +57,7 @@ a = 15
b = false
```

一般情况下,当变量a和变量b之间类型相同时,才能进行如`a = b`的赋值。
一般情况下,当变量a和变量b之间类型相同时,才能进行如 `a = b` 的赋值。

声明与赋值(初始化)语句也可以组合起来。

Expand Down Expand Up @@ -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)

Expand All @@ -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 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。

所有像 intfloatbool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
所有像 `int`、`float`、`bool``string` 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:

![](images/4.4.2_fig4.1.jpg?raw=true)
<img src="images/4.4.2_fig4.1.jpg?raw=true" style="zoom:67%;" />

另外,像数组(第 7 章)和结构(第 10 章)这些复合类型也是值类型。
另外,像数组([第 7 章](.\07.0.md))和结构([第 10 章](.\10.0md))这些复合类型也是值类型。

当使用等号 `=` 将一个变量的值赋值给另一个变量时,如:`j = i`,实际上是在内存中将 i 的值进行了拷贝:
当使用等号 `=` 将一个变量的值赋值给另一个变量时,如:`j = i`,实际上是在内存中将 `i` 的值进行了拷贝:

![](images/4.4.2_fig4.2.jpg?raw=true)
<img src="images/4.4.2_fig4.2.jpg?raw=true" style="zoom: 67%;" />

你可以通过 &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)
<img src="images/4.4.2_fig4.3.jpg?raw=true" style="zoom:67%;" />

这个内存地址被称之为指针(你可以从上图中很清晰地看到,第 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)
Expand All @@ -197,17 +198,17 @@ fmt.Print("Hello:", 23)

我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,而这个时候再在 Example 4.4.1 的最后一个声明语句写上 `var` 关键字就显得有些多余了,因此我们可以将它们简写为 `a := 50` 或 `b := false`。

ab 的类型(int 和 bool)将由编译器自动推断。
`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() {
Expand All @@ -218,7 +219,7 @@ func main() {

尝试编译这段代码将得到错误 `a declared and not used`。

此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 `fmt.Println("hello, world", a)` 会移除错误。
此外,单纯地给 `a` 赋值也是不够的,这个值必须被使用,所以使用 `fmt.Println("hello, world", a)` 会移除错误。

但是全局变量是允许声明但不使用。

Expand All @@ -238,13 +239,13 @@ var a, b, c int
a, b, c = 5, 7, "abc"
```

上面这行假设了变量 a,bc 都已经被声明,否则的话应该这样使用:
上面这行假设了变量 `a`,`b``c` 都已经被声明,否则的话应该这样使用:

```go
a, b, c := 5, 7, "abc"
```

右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 `5`, b 的值是 `7`,c 的值是 `"abc"`。
右边的这些值以相同的顺序赋值给左边的变量,所以 `a` 的值是 `5`, `b` 的值是 `7`,`c` 的值是 `"abc"`。

这被称为 **并行** 或 **同时** 赋值。

Expand All @@ -260,9 +261,9 @@ a, b, c := 5, 7, "abc"

## 4.4.5 init 函数

变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。
变量除了可以在全局声明中初始化,也可以在 `init()` 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 `main()` 函数高。

每个源文件可以包含多个 init 函数,同一个源文件中的 init函数会按照从上到下的顺序执行,如果一个包有多个源文件包含 init 函数的话,则官方鼓励但不保证以文件名的顺序调用。初始化总是以单线程并且按照包的依赖关系顺序执行。
每个源文件可以包含多个 `init()` 函数,同一个源文件中的 `init()` 函数会按照从上到下的顺序执行,如果一个包有多个源文件包含 `init()` 函数的话,则官方鼓励但不保证以文件名的顺序调用。初始化总是以单线程并且按照包的依赖关系顺序执行。

一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。

Expand All @@ -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
Expand All @@ -299,7 +300,7 @@ func main() {
}
```

init 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 `backend()`:
`init()` 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 `backend()`:

```go
func init() {
Expand Down
Loading