Compare commits

...

8 Commits

Author SHA1 Message Date
chai2010
eb1938e3aa
Merge pull request #106 from iGmainC/master
优化排版
2022-08-09 20:22:56 +08:00
iGmainC
ff6a845acf
style(ch1-01): 修改排版 2022-08-09 19:25:20 +08:00
iGmainC
4a4774f251
style(ch1-02): 修改排版 2022-08-09 19:23:34 +08:00
iGmainC
58165d4f80
style(ch1-01.md): 优化排版 2022-08-09 00:04:39 +08:00
iGmainC
2f61241be4
Update book.yml
18.04将要被弃用
2022-08-08 23:55:49 +08:00
iGmainC
99929b676b
style(ch1-03): 优化排版 2022-08-08 13:06:43 +08:00
igmainc
84bd2c3927 style(ch1-02): 优化排版
优化 ch1-02 排版,在汉字和英文间加入空格
2022-08-07 23:18:00 +08:00
igmainc
39d018c5e6 style(ch1-01): 优化排版
优化 ch1-01 排版,在汉字和英文间加入空格
2022-08-07 23:13:53 +08:00
4 changed files with 91 additions and 96 deletions

View File

@ -13,7 +13,7 @@ on:
- closed - closed
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-18.04 runs-on: ubuntu-22.04
steps: steps:
- name: Git checkout - name: Git checkout
uses: actions/checkout@v2 uses: actions/checkout@v2

View File

@ -3,6 +3,7 @@
我们以现已成为传统的“hello world”案例来开始吧这个例子首次出现于 1978 年出版的 C 语言圣经 [《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)(译注:本书作者之一 Brian W. Kernighan 也是《The C Programming Language》一书的作者。C 语言是直接影响 Go 语言设计的语言之一。这个例子体现了 Go 语言一些核心理念。 我们以现已成为传统的“hello world”案例来开始吧这个例子首次出现于 1978 年出版的 C 语言圣经 [《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)(译注:本书作者之一 Brian W. Kernighan 也是《The C Programming Language》一书的作者。C 语言是直接影响 Go 语言设计的语言之一。这个例子体现了 Go 语言一些核心理念。
<u><i>gopl.io/ch1/helloworld</i></u> <u><i>gopl.io/ch1/helloworld</i></u>
```go ```go
package main package main
@ -13,27 +14,27 @@ func main() {
} }
``` ```
Go是一门编译型语言Go语言的工具链将源代码及其依赖转换成计算机的机器指令译注静态编译。Go语言提供的工具都通过一个单独的命令`go`调用,`go`命令有一系列子命令。最简单的一个子命令就是run。这个命令编译一个或多个以.go结尾的源文件链接库文件并运行最终生成的可执行文件。本书使用$表示命令行提示符。) Go 是一门编译型语言Go 语言的工具链将源代码及其依赖转换成计算机的机器指令译注静态编译。Go 语言提供的工具都通过一个单独的命令 `go` 调用,`go` 命令有一系列子命令。最简单的一个子命令就是 `run`。这个命令编译一个或多个以。`.go` 结尾的源文件,链接库文件,并运行最终生成的可执行文件。(本书使用$表示命令行提示符。)
``` ```bash
$ go run helloworld.go $ go run helloworld.go
``` ```
毫无意外,这个命令会输出: 毫无意外,这个命令会输出:
``` ```text
Hello, 世界 Hello, 世界
``` ```
Go 语言原生支持 Unicode它可以处理全世界任何语言的文本。 Go 语言原生支持 Unicode它可以处理全世界任何语言的文本。
如果不只是一次性实验你肯定希望能够编译这个程序保存编译结果以备将来之用。可以用build子命令 如果不只是一次性实验,你肯定希望能够编译这个程序,保存编译结果以备将来之用。可以用 `build` 子命令:
``` ```shell
$ go build helloworld.go $ go build helloworld.go
``` ```
这个命令生成一个名为helloworld的可执行的二进制文件译注Windows系统下生成的可执行文件是helloworld.exe增加了.exe后缀名之后你可以随时运行它译注在Windows系统下在命令行直接输入helloworld.exe命令运行不需任何处理译注因为静态编译所以不用担心在系统库更新的时候冲突幸福感满满 这个命令生成一个名为 `helloworld` 的可执行的二进制文件译注Windows 系统下生成的可执行文件是 `helloworld.exe`,增加了 `.exe` 后缀名),之后你可以随时运行它(译注:在 Windows 系统下在命令行直接输入 `helloworld.exe` 命令运行),不需任何处理(译注:因为静态编译,所以不用担心在系统库更新的时候冲突,幸福感满满)。
``` ```
$ ./helloworld $ ./helloworld
@ -42,19 +43,19 @@ Hello, 世界
本书中所有示例代码上都有一行标记,利用这些标记可以从 [gopl.io](http://gopl.io) 网站上本书源码仓库里获取代码: 本书中所有示例代码上都有一行标记,利用这些标记可以从 [gopl.io](http://gopl.io) 网站上本书源码仓库里获取代码:
``` ```text
gopl.io/ch1/helloworld gopl.io/ch1/helloworld
``` ```
执行 `go get gopl.io/ch1/helloworld` 命令就会从网上获取代码并放到对应目录中需要先安装Git或Hg之类的版本管理工具并将对应的命令添加到PATH环境变量中。序言已经提及需要先设置好GOPATH环境变量下载的代码会放在`$GOPATH/src/gopl.io/ch1/helloworld`目录。2.6和10.7节有这方面更详细的介绍。 执行 `go get gopl.io/ch1/helloworld` 命令,就会从网上获取代码,并放到对应目录中(需要先安装 Git Hg 之类的版本管理工具,并将对应的命令添加到 `PATH` 环境变量中。序言已经提及,需要先设置好 `GOPATH` 环境变量,下载的代码会放在 `$GOPATH/src/gopl.io/ch1/helloworld` 目录)。[2.6](../ch2/ch2-06.md) [10.7 ](../ch10/ch10-07.md)有这方面更详细的介绍。
来讨论下程序本身。Go语言的代码通过**包**package组织包类似于其它语言里的库libraries或者模块modules。一个包由位于单个目录下的一个或多个.go源代码文件组成目录定义包的作用。每个源文件都以一条`package`声明语句开始,这个例子里就是`package main`表示该文件属于哪个包紧跟着一系列导入import的包之后是存储在这个文件里的程序语句。 来讨论下程序本身。Go 语言的代码通过**包**package组织包类似于其它语言里的库libraries或者模块modules。一个包由位于单个目录下的一个或多个 `.go` 源代码文件组成,目录定义包的作用。每个源文件都以一条 `package` 声明语句开始,这个例子里就是 `package main`表示该文件属于哪个包紧跟着一系列导入import的包之后是存储在这个文件里的程序语句。
Go 的标准库提供了 100 多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如 `fmt` 包,就含有格式化输出、接收输入的函数。`Println` 是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。 Go 的标准库提供了 100 多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如 `fmt` 包,就含有格式化输出、接收输入的函数。`Println` 是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。
`main` 包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在 `main` 里的 `main` *函数*也很特殊它是整个程序执行时的入口译注C 系语言差不多都这样)。`main` 函数所做的事情就是程序做的。当然了,`main` 函数一般调用其它包里的函数完成很多工作(如:`fmt.Println`)。 `main` 包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在 `main` 里的 `main` *函数*也很特殊它是整个程序执行时的入口译注C 系语言差不多都这样)。`main` 函数所做的事情就是程序做的。当然了,`main` 函数一般调用其它包里的函数完成很多工作(如:`fmt.Println`)。
必须告诉编译器源文件需要哪些包,这就是跟随在`package`声明后面的`import`声明扮演的角色。hello world例子只用到了一个包大多数程序需要导入多个包。 必须告诉编译器源文件需要哪些包,这就是跟随在 `package` 声明后面的 `import` 声明扮演的角色。`hello world` 例子只用到了一个包,大多数程序需要导入多个包。
必须恰当导入需要的包缺少了必要的包或者导入了不需要的包程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包译注Go 语言编译过程没有警告信息,争议特性之一)。 必须恰当导入需要的包缺少了必要的包或者导入了不需要的包程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包译注Go 语言编译过程没有警告信息,争议特性之一)。
@ -64,12 +65,12 @@ Go的标准库提供了100多个包以支持常见功能如输入、输出
Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字 `break`、`continue`、`fallthrough`或 `return` 中的一个、运算符和分隔符 `++`、`--`、`)`、`]` 或 `}` 中的一个)。举个例子,函数的左括号 `{` 必须和 `func` 函数声明在同一行上,且位于末尾,不能独占一行,而在表达式 `x+y` 中,可在 `+` 后换行,不能在 `+` 前换行(译注:以+结尾的话不会被插入分号分隔符,但是以 x 结尾的话则会被分号分隔符,从而导致编译错误)。 Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字 `break`、`continue`、`fallthrough`或 `return` 中的一个、运算符和分隔符 `++`、`--`、`)`、`]` 或 `}` 中的一个)。举个例子,函数的左括号 `{` 必须和 `func` 函数声明在同一行上,且位于末尾,不能独占一行,而在表达式 `x+y` 中,可在 `+` 后换行,不能在 `+` 前换行(译注:以+结尾的话不会被插入分号分隔符,但是以 x 结尾的话则会被分号分隔符,从而导致编译错误)。
Go语言在代码格式上采取了很强硬的态度。`gofmt`工具把代码格式化为标准格式译注这个格式化工具没有任何可以调整代码格式的参数Go语言就是这么任性并且`go`工具中的`fmt`子命令会对指定包,否则默认为当前目录中所有.go源文件应用`gofmt`命令。本书中的所有代码都被gofmt过。你也应该养成格式化自己的代码的习惯。以法令方式规定标准的代码格式可以避免无尽的无意义的琐碎争执译注也导致了Go语言的TIOBE排名较低因为缺少撕逼的话题。更重要的是这样可以做多种自动源码转换如果放任Go语言代码格式这些转换就不大可能了。 Go 语言在代码格式上采取了很强硬的态度。`gofmt`工具把代码格式化为标准格式译注这个格式化工具没有任何可以调整代码格式的参数Go 语言就是这么任性),并且 `go` 工具中的 `fmt` 子命令会对指定包,否则默认为当前目录中所有。go 源文件应用 `gofmt` 命令。本书中的所有代码都被 gofmt 过。你也应该养成格式化自己的代码的习惯。以法令方式规定标准的代码格式可以避免无尽的无意义的琐碎争执(译注:也导致了 Go 语言的 TIOBE 排名较低,因为缺少撕逼的话题)。更重要的是,这样可以做多种自动源码转换,如果放任 Go 语言代码格式,这些转换就不大可能了。
很多文本编辑器都可以配置为保存文件时自动执行`gofmt`,这样你的源代码总会被恰当地格式化。还有个相关的工具`goimports`,可以根据代码需要,自动地添加或删除`import`声明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装: 很多文本编辑器都可以配置为保存文件时自动执行 `gofmt`,这样你的源代码总会被恰当地格式化。还有个相关的工具`goimports`,可以根据代码需要,自动地添加或删除 `import` 声明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装:
``` ```shell
$ go get golang.org/x/tools/cmd/goimports $ go get golang.org/x/tools/cmd/goimports
``` ```
对于大多数用户来说下载、编译包、运行测试用例、察看Go语言的文档等等常用功能都可以用go的工具完成。10.7节详细介绍这些知识。 对于大多数用户来说,下载、编译包、运行测试用例、察看 Go 语言的文档等等常用功能都可以用 go 的工具完成。[10.7 ](../ch10/ch10-07.md)详细介绍这些知识。

View File

@ -2,15 +2,16 @@
大多数的程序都是处理输入,产生输出;这也正是“计算”的定义。但是,程序如何获取要处理的输入数据呢?一些程序生成自己的数据,但通常情况下,输入来自于程序外部:文件、网络连接、其它程序的输出、敲键盘的用户、命令行参数或其它类似输入源。下面几个例子会讨论其中几个输入源,首先是命令行参数。 大多数的程序都是处理输入,产生输出;这也正是“计算”的定义。但是,程序如何获取要处理的输入数据呢?一些程序生成自己的数据,但通常情况下,输入来自于程序外部:文件、网络连接、其它程序的输出、敲键盘的用户、命令行参数或其它类似输入源。下面几个例子会讨论其中几个输入源,首先是命令行参数。
`os`包以跨平台的方式提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取os包外部使用os.Args访问该变量。 `os` 包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从 `os` 包的 `Args` 变量获取;`os` 包外部使用 `os.Args` 访问该变量。
os.Args变量是一个字符串string的*切片*slice译注slice和Python语言中的切片类似是一个简版的动态数组切片是Go语言的基础概念稍后详细介绍。现在先把切片s当作数组元素序列序列的长度动态变化`s[i]`访问单个元素,用`s[m:n]`获取子序列译注和python里的语法差不多。序列的元素数目为len(s)。和大多数编程语言类似区间索引时Go语言里也采用左闭右开形式区间包括第一个索引元素不包括最后一个因为这样可以简化逻辑。译注比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3]不包含最后一个元素。比如s[m:n]这个切片0 m n len(s)包含n-m个元素。 `os.Args` 变量是一个字符串string *切片*slice译注slice Python 语言中的切片类似,是一个简版的动态数组),切片是 Go 语言的基础概念,稍后详细介绍。现在先把切片 `s` 当作数组元素序列,序列的长度动态变化,用 `s[i]` 访问单个元素,用 `s[m:n]` 获取子序列(译注:和 Python 里的语法差不多)。序列的元素数目为 `len(s)`。和大多数编程语言类似区间索引时Go 语言里也采用左闭右开形式,即,区间包括第一个索引元素,不包括最后一个,因为这样可以简化逻辑。(译注:比如 `a=[1,2,3,4,5]`, `a[0:3]=[1,2,3]`,不包含最后一个元素)。比如 `s[m:n]` 这个切片,`0≤m≤n≤len(s)`,包含 `n-m` 个元素。
os.Args的第一个元素os.Args[0]是命令本身的名字其它的元素则是程序启动时传给它的参数。s[m:n]形式的切片表达式产生从第m个元素到第n-1个元素的切片下个例子用到的元素包含在os.Args[1:len(os.Args)]切片中。如果省略切片表达式的m或n会默认传入0或len(s)因此前面的切片可以简写成os.Args[1:]。 `os.Args` 的第一个元素:`os.Args[0]`,是命令本身的名字;其它的元素则是程序启动时传给它的参数。`s[m:n]` 形式的切片表达式,产生从第 `m` 个元素到第 `n-1` 个元素的切片,下个例子用到的元素包含在 `os.Args[1:len(os.Args)]` 切片中。如果省略切片表达式的 `m` `n`,会默认传入 `0` `len(s)`,因此前面的切片可以简写成 `os.Args[1:]`
下面是Unix里echo命令的一份实现echo把它的命令行参数打印成一行。程序导入了两个包用括号把它们括起来写成列表形式而没有分开写成独立的`import`声明。两种形式都合法列表形式习惯上用得多。包导入顺序并不重要gofmt工具格式化时按照字母顺序对包名排序。示例有多个版本时我们会对示例编号这样可以明确当前正在讨论的是哪个。 下面是 Unix `echo` 命令的一份实现,`echo` 把它的命令行参数打印成一行。程序导入了两个包,用括号把它们括起来写成列表形式,而没有分开写成独立的 `import` 声明。两种形式都合法,列表形式习惯上用得多。包导入顺序并不重要;`gofmt` 工具格式化时按照字母顺序对包名排序。(示例有多个版本时,我们会对示例编号,这样可以明确当前正在讨论的是哪个。)
<u><i>gopl.io/ch1/echo1</i></u> <u><i>gopl.io/ch1/echo1</i></u>
```go ```go
// Echo1 prints its command-line arguments. // Echo1 prints its command-line arguments.
package main package main
@ -30,37 +31,21 @@ func main() {
} }
``` ```
注释语句以`//`开头。对于程序员来说,//之后到行末之间所有的内容都是注释,被编译器忽略。按照惯例,我们在每个包的包声明前添加注释;对于`main package`,注释包含一句或几句话,从整体角度对程序做个描述。 注释语句以 `//` 开头。对于程序员来说,`//` 之后到行末之间所有的内容都是注释,被编译器忽略。按照惯例,我们在每个包的包声明前添加注释;对于 `main package`,注释包含一句或几句话,从整体角度对程序做个描述。
var声明定义了两个string类型的变量s和sep。变量会在声明时直接初始化。如果变量没有显式初始化则被隐式地赋予其类型的*零值*zero value数值类型是0字符串类型是空字符串""。这个例子里声明把s和sep隐式地初始化成空字符串。第2章再来详细地讲解变量和声明。 `var` 声明定义了两个 `string` 类型的变量 `s` `sep`。变量会在声明时直接初始化。如果变量没有显式初始化,则被隐式地赋予其类型的 *零值*zero value数值类型是 `0`,字符串类型是空字符串 `""`。这个例子里,声明把 `s` `sep` 隐式地初始化成空字符串。第 2 章再来详细地讲解变量和声明。
对数值类型Go语言提供了常规的数值和逻辑运算符。而对string类型`+`运算符连接字符串译注和C++或者js是一样的。所以表达式 对数值类型Go 语言提供了常规的数值和逻辑运算符。而对 `string` 类型,`+` 运算符连接字符串(译注:和 C++ 或者 JavaScript 是一样的)。所以表达式:`sep + os.Args[i]` 表示连接字符串 `sep``os.Args`。程序中使用的语句:`s+=sep+os.Args[i]` 是一条 *赋值语句*,将 `s` 的旧值跟 `sep``os.Args[i]` 连接后赋值回 `s`,等价于:`s=s+sep+os.Args[i]`。
```go
sep + os.Args[i]
```
表示连接字符串sep和os.Args。程序中使用的语句
```go
s += sep + os.Args[i]
```
是一条*赋值语句*将s的旧值跟sep与os.Args[i]连接后赋值回s等价于
```go
s = s + sep + os.Args[i]
```
运算符 `+=` 是赋值运算符assignment operator每种数值运算符或逻辑运算符`+``*`,都有对应的赋值运算符。 运算符 `+=` 是赋值运算符assignment operator每种数值运算符或逻辑运算符`+``*`,都有对应的赋值运算符。
echo程序可以每循环一次输出一个参数这个版本却是不断地把新文本追加到末尾来构造字符串。字符串s开始为空即值为""每次循环会添加一些文本第一次迭代之后还会再插入一个空格因此循环结束时每个参数中间都有一个空格。这是一种二次加工quadratic process当参数数量庞大时开销很大但是对于echo这种情形不大可能出现。本章会介绍echo的若干改进版下一章解决低效问题。 `echo` 程序可以每循环一次输出一个参数,这个版本却是不断地把新文本追加到末尾来构造字符串。字符串 `s` 开始为空,即值为 `""`每次循环会添加一些文本第一次迭代之后还会再插入一个空格因此循环结束时每个参数中间都有一个空格。这是一种二次加工quadratic process当参数数量庞大时开销很大但是对于 `echo`,这种情形不大可能出现。本章会介绍 `echo` 的若干改进版,下一章解决低效问题。
循环索引变量i在for循环的第一部分中定义。符号`:=`是*短变量声明*short variable declaration的一部分这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。下一章有这方面更多说明。 循环索引变量 `i``for` 循环的第一部分中定义。符号 `:=`*短变量声明*short variable declaration的一部分这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。下一章有这方面更多说明。
自增语句`i++`给`i`加1这和`i += 1`以及`i = i + 1`都是等价的。对应的还有`i--`给`i`减1。它们是语句而不像C系的其它语言那样是表达式。所以`j = i++`非法,而且++和--都只能放在变量名后面,因此`--i`也非法。 自增语句 `i++``i``1`;这和 `i+=1` 以及 `i=i+1` 都是等价的。对应的还有 `i--``i``1`。它们是语句,而不像 C 系的其它语言那样是表达式。所以 `j=i++` 非法,而且 `++``--` 都只能放在变量名后面,因此 `--i` 也非法。
Go语言只有for循环这一种循环语句。for循环有多种形式其中一种如下所示 Go 语言只有 `for` 循环这一种循环语句。`for` 循环有多种形式,其中一种如下所示:
```go ```go
for initialization; condition; post { for initialization; condition; post {
@ -68,9 +53,9 @@ for initialization; condition; post {
} }
``` ```
for循环三个部分不需括号包围。大括号强制要求左大括号必须和*post*语句在同一行。 `for` 循环三个部分不需括号包围。大括号强制要求,左大括号必须和 *`post`* 语句在同一行。
*initialization*语句是可选的,在循环开始前执行。*initalization*如果存在,必须是一条*简单语句*simple statement短变量声明、自增语句、赋值语句或函数调用。`condition`是一个布尔表达式boolean expression其值在每次循环迭代开始时计算。如果为`true`则执行循环体语句。`post`语句在循环体执行结束后执行,之后再次对`condition`求值。`condition`值为`false`时,循环结束。 *`initialization`* 语句是可选的,在循环开始前执行。*`initalization`* 如果存在,必须是一条 *简单语句*simple statement短变量声明、自增语句、赋值语句或函数调用。`condition` 是一个布尔表达式boolean expression其值在每次循环迭代开始时计算。如果为 `true` 则执行循环体语句。`post` 语句在循环体执行结束后执行,之后再次对 `condition` 求值。`condition` 值为 `false` 时,循环结束。
for 循环的这三个部分每个都可以省略,如果省略 `initialization``post`,分号也可以省略: for 循环的这三个部分每个都可以省略,如果省略 `initialization``post`,分号也可以省略:
@ -95,6 +80,7 @@ for {
`for` 循环的另一种形式在某种数据类型的区间range上遍历如字符串或切片。`echo` 的第二版本展示了这种形式: `for` 循环的另一种形式在某种数据类型的区间range上遍历如字符串或切片。`echo` 的第二版本展示了这种形式:
<u><i>gopl.io/ch1/echo2</i></u> <u><i>gopl.io/ch1/echo2</i></u>
```go ```go
// Echo2 prints its command-line arguments. // Echo2 prints its command-line arguments.
package main package main
@ -116,7 +102,7 @@ func main() {
每次循环迭代,`range` 产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但 `range` 的语法要求,要处理元素,必须处理索引。一种思路是把索引赋值给一个临时变量(如 `temp`)然后忽略它的值,但 Go 语言不允许使用无用的局部变量local variables因为这会导致编译错误。 每次循环迭代,`range` 产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但 `range` 的语法要求,要处理元素,必须处理索引。一种思路是把索引赋值给一个临时变量(如 `temp`)然后忽略它的值,但 Go 语言不允许使用无用的局部变量local variables因为这会导致编译错误。
Go语言中这种情况的解决方法是用`空标识符`blank identifier即`_`也就是下划线。空标识符可用于在任何语法需要变量名但程序逻辑不需要的时候在循环里丢弃不需要的循环索引并保留元素值。大多数的Go程序员都会像上面这样使用`range`和`_`写`echo`程序因为隐式地而非显式地索引os.Args容易写对。 Go 语言中这种情况的解决方法是用 *空标识符*blank identifier`_`(也就是下划线)。空标识符可用于在任何语法需要变量名但程序逻辑不需要的时候(如:在循环里)丢弃不需要的循环索引,并保留元素值。大多数的 Go 程序员都会像上面这样使用 `range` `_` `echo` 程序,因为隐式地而非显式地索引 `os.Args`,容易写对。
`echo` 的这个版本使用一条短变量声明来声明并初始化 `s``seps`,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等价: `echo` 的这个版本使用一条短变量声明来声明并初始化 `s``seps`,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等价:
@ -127,13 +113,14 @@ var s = ""
var s string = "" var s string = ""
``` ```
用哪种不用哪种,为什么呢?第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。 用哪种不用哪种,为什么呢?第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为 `""`。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。
如前文所述每次循环迭代字符串s的内容都会更新。`+=`连接原字符串、空格和下个参数,产生新字符串,并把它赋值给`s`。`s`原来的内容已经不再使用,将在适当时机对它进行垃圾回收。 如前文所述,每次循环迭代字符串 `s` 的内容都会更新。`+=` 连接原字符串、空格和下个参数,产生新字符串,并把它赋值给 `s`。`s` 原来的内容已经不再使用,将在适当时机对它进行垃圾回收。
如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用 `strings` 包的 `Join` 函数: 如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用 `strings` 包的 `Join` 函数:
<u><i>gopl.io/ch1/echo3</i></u> <u><i>gopl.io/ch1/echo3</i></u>
```go ```go
func main() { func main() {
fmt.Println(strings.Join(os.Args[1:], " ")) fmt.Println(strings.Join(os.Args[1:], " "))
@ -148,8 +135,10 @@ fmt.Println(os.Args[1:])
这条语句的输出结果跟 `strings.Join` 得到的结果很像,只是被放到了一对方括号里。切片都会被打印成这种格式。 这条语句的输出结果跟 `strings.Join` 得到的结果很像,只是被放到了一对方括号里。切片都会被打印成这种格式。
---------
**练习 1.1** 修改 `echo` 程序,使其能够打印 `os.Args[0]`,即被执行命令本身的名字。 **练习 1.1** 修改 `echo` 程序,使其能够打印 `os.Args[0]`,即被执行命令本身的名字。
**练习 1.2** 修改 `echo` 程序,使其打印每个参数的索引和值,每个一行。 **练习 1.2** 修改 `echo` 程序,使其打印每个参数的索引和值,每个一行。
**练习 1.3** 做实验测量潜在低效的版本和使用了`strings.Join`的版本的运行时间差异。1.6节讲解了部分`time`包11.4节展示了如何写标准测试程序,以得到系统性的性能评测。) **练习 1.3** 做实验测量潜在低效的版本和使用了 `strings.Join` 的版本的运行时间差异。([1.6 ](./ch1-06.md)讲解了部分 `time` 包,[11.4 ](../ch11/ch11-04.md)展示了如何写标准测试程序,以得到系统性的性能评测。)

View File

@ -5,6 +5,7 @@
`dup` 的第一个版本打印标准输入中多次出现的行,以重复次数开头。该程序将引入 `if` 语句,`map` 数据类型以及 `bufio` 包。 `dup` 的第一个版本打印标准输入中多次出现的行,以重复次数开头。该程序将引入 `if` 语句,`map` 数据类型以及 `bufio` 包。
<u><i>gopl.io/ch1/dup1</i></u> <u><i>gopl.io/ch1/dup1</i></u>
```go ```go
// Dup1 prints the text of each line that appears more than // Dup1 prints the text of each line that appears more than
// once in the standard input, preceded by its count. // once in the standard input, preceded by its count.
@ -35,7 +36,7 @@ func main() {
**map** 存储了键/值key/value的集合对集合元素提供常数时间的存、取或测试操作。键可以是任意类型只要其值能用 `==` 运算符比较,最常见的例子是字符串;值则可以是任意类型。这个例子中的键是字符串,值是整数。内置函数 `make` 创建空 `map`此外它还有别的作用。4.3 节讨论 `map` **map** 存储了键/值key/value的集合对集合元素提供常数时间的存、取或测试操作。键可以是任意类型只要其值能用 `==` 运算符比较,最常见的例子是字符串;值则可以是任意类型。这个例子中的键是字符串,值是整数。内置函数 `make` 创建空 `map`此外它还有别的作用。4.3 节讨论 `map`
(译注:从功能和实现上说,`Go`的`map`类似于`Java`语言中的`HashMap`Python语言中的`dict``Lua`语言中的`table`,通常使用`hash`实现。遗憾的是,对于该词的翻译并不统一,数学界术语为`映射`,而计算机界众说纷纭莫衷一是。为了防止对读者造成误解,保留不译。) (译注:从功能和实现上说,`Go` `map` 类似于 `Java` 语言中的 `HashMap`Python 语言中的 `dict``Lua` 语言中的 `table`,通常使用 `hash` 实现。遗憾的是,对于该词的翻译并不统一,数学界术语为*映射*,而计算机界众说纷纭莫衷一是。为了防止对读者造成误解,保留不译。)
每次 `dup` 读取一行输入,该行被当做键存入 `map`,其对应的值递增。`counts[input.Text()]++` 语句等价下面两句: 每次 `dup` 读取一行输入,该行被当做键存入 `map`,其对应的值递增。`counts[input.Text()]++` 语句等价下面两句:
@ -44,15 +45,15 @@ line := input.Text()
counts[line] = counts[line] + 1 counts[line] = counts[line] + 1
``` ```
`map`中不含某个键时不用担心,首次读到新行时,等号右边的表达式`counts[line]`的值将被计算为其类型的零值,对于`int`即0。 `map` 中不含某个键时不用担心,首次读到新行时,等号右边的表达式 `counts[line]` 的值将被计算为其类型的零值,对于 `int` `0`
为了打印结果,我们使用了基于`range`的循环,并在`counts`这个`map`上迭代。跟之前类似,每次迭代得到两个结果,键和其在`map`中对应的值。`map`的迭代顺序并不确定从实践来看该顺序随机每次运行都会变化。这种设计是有意为之的因为能防止程序依赖特定遍历顺序而这是无法保证的。译注具体可以参见这里http://stackoverflow.com/questions/11853396/google-go-lang-assignment-order 为了打印结果,我们使用了基于 `range` 的循环,并在 `counts` 这个 `map` 上迭代。跟之前类似,每次迭代得到两个结果,键和其在 `map` 中对应的值。`map` 的迭代顺序并不确定,从实践来看,该顺序随机,每次运行都会变化。这种设计是有意为之的,因为能防止程序依赖特定遍历顺序,而这是无法保证的。(译注:具体可以参见这里[https://stackoverflow.com/questions/11853396/google-go-lang-assignment-order](https://stackoverflow.com/questions/11853396/google-go-lang-assignment-order)
继续来看 `bufio` 包,它使处理输入和输出方便又高效。`Scanner` 类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。 继续来看 `bufio` 包,它使处理输入和输出方便又高效。`Scanner` 类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。
程序使用短变量声明创建 `bufio.Scanner` 类型的变量 `input` 程序使用短变量声明创建 `bufio.Scanner` 类型的变量 `input`
``` ```go
input := bufio.NewScanner(os.Stdin) input := bufio.NewScanner(os.Stdin)
``` ```
@ -62,7 +63,7 @@ input := bufio.NewScanner(os.Stdin)
`Printf` 有一大堆这种转换Go程序员称之为*动词verb*。下面的表格虽然远不是完整的规范,但展示了可用的很多特性: `Printf` 有一大堆这种转换Go程序员称之为*动词verb*。下面的表格虽然远不是完整的规范,但展示了可用的很多特性:
``` ```text
%d 十进制整数 %d 十进制整数
%x, %o, %b 十六进制,八进制,二进制整数。 %x, %o, %b 十六进制,八进制,二进制整数。
%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00 %f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00
@ -80,6 +81,7 @@ input := bufio.NewScanner(os.Stdin)
很多程序要么从标准输入中读取数据,如上面的例子所示,要么从一系列具名文件中读取数据。`dup` 程序的下个版本读取标准输入或是使用 `os.Open` 打开各个具名文件,并操作它们。 很多程序要么从标准输入中读取数据,如上面的例子所示,要么从一系列具名文件中读取数据。`dup` 程序的下个版本读取标准输入或是使用 `os.Open` 打开各个具名文件,并操作它们。
<u><i>gopl.io/ch1/dup2</i></u> <u><i>gopl.io/ch1/dup2</i></u>
```go ```go
// Dup2 prints the count and text of lines that appear more than once // Dup2 prints the count and text of lines that appear more than once
// in the input. It reads from stdin or from a list of named files. // in the input. It reads from stdin or from a list of named files.
@ -123,9 +125,9 @@ func countLines(f *os.File, counts map[string]int) {
} }
``` ```
`os.Open`函数返回两个值。第一个值是被打开的文件(`*os.File`),其后被`Scanner`读取。 `os.Open` 函数返回两个值。第一个值是被打开的文件`*os.File`),其后被 `Scanner` 读取。
`os.Open`返回的第二个值是内置`error`类型的值。如果`err`等于内置值`nil`译注相当于其它语言里的NULL那么文件被成功打开。读取文件直到文件结束然后调用`Close`关闭该文件,并释放占用的所有资源。相反的话,如果`err`的值不是`nil`,说明打开文件时出错了。这种情况下,错误值描述了所遇到的问题。我们的错误处理非常简单,只是使用`Fprintf`与表示任意类型默认格式值的动词`%v`,向标准错误流打印一条信息,然后`dup`继续处理下一个文件;`continue`语句直接跳到`for`循环的下个迭代开始执行。 `os.Open` 返回的第二个值是内置 `error` 类型的值。如果 `err` 等于内置值`nil`(译注:相当于其它语言里的 `NULL`),那么文件被成功打开。读取文件,直到文件结束,然后调用 `Close` 关闭该文件,并释放占用的所有资源。相反的话,如果 `err` 的值不是 `nil`,说明打开文件时出错了。这种情况下,错误值描述了所遇到的问题。我们的错误处理非常简单,只是使用 `Fprintf` 与表示任意类型默认格式值的动词 `%v`,向标准错误流打印一条信息,然后 `dup` 继续处理下一个文件;`continue` 语句直接跳到 `for` 循环的下个迭代开始执行。
为了使示例代码保持合理的大小,本书开始的一些示例有意简化了错误处理,显而易见的是,应该检查 `os.Open` 返回的错误值,然而,使用 `input.Scan` 读取文件过程中不大可能出现错误因此我们忽略了错误处理。我们会在跳过错误检查的地方做说明。5.4 节中深入介绍错误处理。 为了使示例代码保持合理的大小,本书开始的一些示例有意简化了错误处理,显而易见的是,应该检查 `os.Open` 返回的错误值,然而,使用 `input.Scan` 读取文件过程中不大可能出现错误因此我们忽略了错误处理。我们会在跳过错误检查的地方做说明。5.4 节中深入介绍错误处理。
@ -138,6 +140,7 @@ func countLines(f *os.File, counts map[string]int) {
我们略微简化了 `dup3`。首先,由于 `ReadFile` 函数需要文件名作为参数,因此只读指定文件,不读标准输入。其次,由于行计数代码只在一处用到,故将其移回 `main` 函数。 我们略微简化了 `dup3`。首先,由于 `ReadFile` 函数需要文件名作为参数,因此只读指定文件,不读标准输入。其次,由于行计数代码只在一处用到,故将其移回 `main` 函数。
<u><i>gopl.io/ch1/dup3</i></u> <u><i>gopl.io/ch1/dup3</i></u>
```go ```go
package main package main
@ -172,4 +175,6 @@ func main() {
实现上,`bufio.Scanner`、`ioutil.ReadFile` 和 `ioutil.WriteFile` 都使用 `*os.File``Read``Write` 方法但是大多数程序员很少需要直接调用那些低级lower-level函数。高级higher-level函数`bufio``io/ioutil` 包中所提供的那些,用起来要容易点。 实现上,`bufio.Scanner`、`ioutil.ReadFile` 和 `ioutil.WriteFile` 都使用 `*os.File``Read``Write` 方法但是大多数程序员很少需要直接调用那些低级lower-level函数。高级higher-level函数`bufio``io/ioutil` 包中所提供的那些,用起来要容易点。
-----
**练习 1.4** 修改 `dup2`,出现重复的行时打印文件名称。 **练习 1.4** 修改 `dup2`,出现重复的行时打印文件名称。