From 0ab755766556a1553d8114e180f3fe3cb08785de Mon Sep 17 00:00:00 2001 From: kimw Date: Sun, 27 May 2018 16:51:15 -0400 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=8D=8A=E8=A7=92=E6=A0=87?= =?UTF-8?q?=E7=82=B9=E7=AC=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTORS.md | 2 +- appendix/appendix-c-cpoyright.md | 2 +- ch0/ch0-01.md | 2 +- ch0/ch0-02.md | 2 +- ch0/ch0-03.md | 6 +++--- ch1/ch1-01.md | 17 +++++++++-------- ch1/ch1-02.md | 24 ++++++++++++------------ ch1/ch1-04.md | 2 +- ch1/ch1.md | 4 ++-- ch10/ch10-07-3.md | 2 +- ch11/ch11-02-2.md | 2 +- ch12/ch12-02.md | 26 +++++++++++++------------- ch12/ch12-03.md | 2 +- ch12/ch12-08.md | 2 +- ch13/ch13-01.md | 6 +++--- ch13/ch13-04.md | 6 +++--- ch13/ch13.md | 3 +-- ch2/ch2-03-3.md | 2 +- ch3/ch3-01.md | 2 +- ch3/ch3-02.md | 6 +++--- ch3/ch3-03.md | 2 +- ch3/ch3-04.md | 4 ++-- ch5/ch5-01.md | 5 ++--- ch5/ch5-02.md | 4 ++-- ch5/ch5-03.md | 2 +- ch5/ch5-04.md | 5 ++--- ch5/ch5-05.md | 4 ++-- ch5/ch5-06.md | 2 +- ch5/ch5-07.md | 2 +- ch5/ch5-10.md | 2 +- ch6/ch6-05.md | 2 +- ch7/ch7-04.md | 2 +- ch7/ch7-05.md | 2 +- ch7/ch7-06.md | 3 ++- ch7/ch7-07.md | 2 +- ch7/ch7.md | 2 +- ch8/ch8-02.md | 2 +- ch9/ch9-02.md | 2 +- ch9/ch9-06.md | 2 +- 39 files changed, 85 insertions(+), 86 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 06cda0e..ce60a33 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -9,6 +9,6 @@ # 译文授权 -除特别注明外, 本站内容均采用[知识共享-署名(CC-BY) 3.0协议](http://creativecommons.org/licenses/by/3.0/)授权, 代码遵循[Go项目的BSD协议](http://golang.org/LICENSE)授权. +除特别注明外,本站内容均采用[知识共享-署名(CC-BY) 3.0协议](http://creativecommons.org/licenses/by/3.0/)授权,代码遵循[Go项目的BSD协议](http://golang.org/LICENSE)授权。 Creative Commons License diff --git a/appendix/appendix-c-cpoyright.md b/appendix/appendix-c-cpoyright.md index d40ba64..8435dd0 100644 --- a/appendix/appendix-c-cpoyright.md +++ b/appendix/appendix-c-cpoyright.md @@ -1,6 +1,6 @@ ## 附录C:译文授权 -除特别注明外, 本站内容均采用[知识共享-署名(CC-BY) 3.0协议](http://creativecommons.org/licenses/by/3.0/)授权, 代码遵循[Go项目的BSD协议](http://golang.org/LICENSE)授权. +除特别注明外,本站内容均采用[知识共享-署名(CC-BY) 3.0协议](http://creativecommons.org/licenses/by/3.0/)授权,代码遵循[Go项目的BSD协议](http://golang.org/LICENSE)授权。 Creative Commons License diff --git a/ch0/ch0-01.md b/ch0/ch0-01.md index ac8116f..8629d1a 100644 --- a/ch0/ch0-01.md +++ b/ch0/ch0-01.md @@ -12,7 +12,7 @@ Go语言有时候被描述为“C类似语言”,或者是“21世纪的C语 Go语言的另一支祖先,带来了Go语言区别其他语言的重要特性,灵感来自于贝尔实验室的[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)于1978年发表的鲜为外界所知的关于并发研究的基础文献 *顺序通信进程* ( *[communicating sequential processes][CSP]* ,缩写为[CSP][CSP]。在[CSP][CSP]中,程序是一组中间没有共享状态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。不过[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)的[CSP][CSP]只是一个用于描述并发性基本概念的描述语言,并不是一个可以编写可执行程序的通用编程语言。 -接下来,Rob Pike和其他人开始不断尝试将[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)引入实际的编程语言中。他们第一次尝试引入[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)特性的编程语言叫[Squeak](http://doc.cat-v.org/bell_labs/squeak/)(老鼠间交流的语言),是一个提供鼠标和键盘事件处理的编程语言,它的管道是静态创建的。然后是改进版的[Newsqueak](http://doc.cat-v.org/bell_labs/squeak/)语言,提供了类似C语言语句和表达式的语法和类似[Pascal][Pascal]语言的推导语法。Newsqueak是一个带垃圾回收的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言中管道是动态创建的,属于第一类值, 可以保存到变量中。 +接下来,Rob Pike和其他人开始不断尝试将[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)引入实际的编程语言中。他们第一次尝试引入[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)特性的编程语言叫[Squeak](http://doc.cat-v.org/bell_labs/squeak/)(老鼠间交流的语言),是一个提供鼠标和键盘事件处理的编程语言,它的管道是静态创建的。然后是改进版的[Newsqueak](http://doc.cat-v.org/bell_labs/squeak/)语言,提供了类似C语言语句和表达式的语法和类似[Pascal][Pascal]语言的推导语法。Newsqueak是一个带垃圾回收的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言中管道是动态创建的,属于第一类值,可以保存到变量中。 在Plan9操作系统中,这些优秀的想法被吸收到了一个叫[Alef][Alef]的编程语言中。Alef试图将Newsqueak语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。(译注:在Alef之后还有一个叫[Limbo][Limbo]的编程语言,Go语言从其中借鉴了很多特性。 具体请参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 ) diff --git a/ch0/ch0-02.md b/ch0/ch0-02.md index 2cd1566..dc9f496 100644 --- a/ch0/ch0-02.md +++ b/ch0/ch0-02.md @@ -12,4 +12,4 @@ Go语言有足够的类型系统以避免动态语言中那些粗心的类型错 Go语言鼓励当代计算机系统设计的原则,特别是局部的重要性。它的内置数据类型和大多数的准库数据结构都经过精心设计而避免显式的初始化或隐式的构造函数,因为很少的内存分配和内存初始化代码被隐藏在库代码中了。Go语言的聚合类型(结构体和数组)可以直接操作它们的元素,只需要更少的存储空间、更少的内存写操作,而且指针操作比其他间接操作的语言也更有效率。由于现代计算机是一个并行的机器,Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小,因此,创建一个goroutine的代价很小,创建百万级的goroutine完全是可行的。 -Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口,包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。库和工具使用了大量的约定来减少额外的配置和解释,从而最终简化程序的逻辑,而且,每个Go程序结构都是如此的相似,因此,Go程序也很容易学习。使用Go语言自带工具构建Go语言项目只需要使用文件名和标识符名称, 一个偶尔的特殊注释来确定所有的库、可执行文件、测试、基准测试、例子、以及特定于平台的变量、项目的文档等;Go语言源代码本身就包含了构建规范。 +Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口,包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。库和工具使用了大量的约定来减少额外的配置和解释,从而最终简化程序的逻辑,而且,每个Go程序结构都是如此的相似,因此,Go程序也很容易学习。使用Go语言自带工具构建Go语言项目只需要使用文件名和标识符名称,一个偶尔的特殊注释来确定所有的库、可执行文件、测试、基准测试、例子、以及特定于平台的变量、项目的文档等;Go语言源代码本身就包含了构建规范。 diff --git a/ch0/ch0-03.md b/ch0/ch0-03.md index ff5b69d..22df950 100644 --- a/ch0/ch0-03.md +++ b/ch0/ch0-03.md @@ -8,7 +8,7 @@ 第一章到第五章是基础部分,主流命令式编程语言这部分都类似。个别之处,Go语言有自己特色的语法和风格,但是大多数程序员能很快适应。其余章节是Go语言特有的:方法、接口、并发、包、测试和反射等语言特性。 -Go语言的面向对象机制与一般语言不同。它没有类层次结构,甚至可以说没有类;仅仅通过组合(而不是继承)简单的对象来构建复杂的对象。方法不仅可以定义在结构体上, 而且, 可以定义在任何用户自定义的类型上;并且, 具体类型和抽象类型(接口)之间的关系是隐式的,所以很多类型的设计者可能并不知道该类型到底实现了哪些接口。方法在第六章讨论,接口在第七章讨论。 +Go语言的面向对象机制与一般语言不同。它没有类层次结构,甚至可以说没有类;仅仅通过组合(而不是继承)简单的对象来构建复杂的对象。方法不仅可以定义在结构体上,而且,可以定义在任何用户自定义的类型上;并且,具体类型和抽象类型(接口)之间的关系是隐式的,所以很多类型的设计者可能并不知道该类型到底实现了哪些接口。方法在第六章讨论,接口在第七章讨论。 第八章讨论了基于顺序通信进程(CSP)概念的并发编程,使用goroutines和channels处理并发编程。第九章则讨论了传统的基于共享变量的并发编程。 @@ -16,7 +16,7 @@ Go语言的面向对象机制与一般语言不同。它没有类层次结构, 第十一章讨论了单元测试,Go语言的工具和标准库中集成了轻量级的测试功能,避免了强大但复杂的测试框架。测试库提供了一些基本构件,必要时可以用来构建复杂的测试构件。 -第十二章讨论了反射,一种程序在运行期间审视自己的能力。反射是一个强大的编程工具,不过要谨慎地使用;这一章利用反射机制实现一些重要的Go语言库函数, 展示了反射的强大用法。第十三章解释了底层编程的细节,在必要时,可以使用unsafe包绕过Go语言安全的类型系统。 +第十二章讨论了反射,一种程序在运行期间审视自己的能力。反射是一个强大的编程工具,不过要谨慎地使用;这一章利用反射机制实现一些重要的Go语言库函数,展示了反射的强大用法。第十三章解释了底层编程的细节,在必要时,可以使用unsafe包绕过Go语言安全的类型系统。 每一章都有一些练习题,你可以用来测试你对Go的理解,你也可以探讨书中这些例子的扩展和替代。 @@ -38,4 +38,4 @@ $ go version go version go1.5 linux/amd64 ``` -如果使用其他的操作系统, 请参考 https://golang.org/doc/install 提供的说明安装。 +如果使用其他的操作系统,请参考 https://golang.org/doc/install 提供的说明安装。 diff --git a/ch1/ch1-01.md b/ch1/ch1-01.md index 190a104..aa2cead 100644 --- a/ch1/ch1-01.md +++ b/ch1/ch1-01.md @@ -1,6 +1,6 @@ ## 1.1. Hello, World -我们以现已成为传统的“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语言一些核心理念。 gopl.io/ch1/helloworld ```go @@ -40,7 +40,7 @@ $ ./helloworld Hello, 世界 ``` -本书中, 所有的示例代码上都有一行标记,利用这些标记, 可以从[gopl.io](http://gopl.io)网站上本书源码仓库里获取代码: +本书中所有示例代码上都有一行标记,利用这些标记可以从[gopl.io](http://gopl.io)网站上本书源码仓库里获取代码: ``` gopl.io/ch1/helloworld @@ -48,25 +48,26 @@ 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语言的代码通过**包**(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成, 目录定义包的作用。每个源文件都以一条`package`声明语句开始,这个例子里就是`package main`, 表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。 +来讨论下程序本身。Go语言的代码通过**包**(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。每个源文件都以一条`package`声明语句开始,这个例子里就是`package main`,表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。 Go的标准库提供了100多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如`fmt`包,就含有格式化输出、接收输入的函数。`Println`是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。 -`main`包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在`main`里的`main` *函数* 也很特殊,它是整个程序执行时的入口(译注:C系语言差不多都这样)。`main`函数所做的事情就是程序做的。当然了,`main`函数一般调用其它包里的函数完成很多工作, 比如, `fmt.Println`。 +`main`包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在`main`里的`main` *函数* 也很特殊,它是整个程序执行时的入口(译注:C系语言差不多都这样)。`main`函数所做的事情就是程序做的。当然了,`main`函数一般调用其它包里的函数完成很多工作(如:`fmt.Println`)。 必须告诉编译器源文件需要哪些包,这就是跟随在`package`声明后面的`import`声明扮演的角色。hello world例子只用到了一个包,大多数程序需要导入多个包。 必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包(译注:Go语言编译过程没有警告信息,争议特性之一)。 -`import`声明必须跟在文件的`package`声明之后。随后,则是组成程序的函数、变量、常量、类型的声明语句(分别由关键字`func`, `var`, `const`, `type`定义)。这些内容的声明顺序并不重要(译注:最好还是定一下规范)。这个例子的程序已经尽可能短了,只声明了一个函数, 其中只调用了一个其他函数。为了节省篇幅,有些时候, 示例程序会省略`package`和`import`声明,但是,这些声明在源代码里有,并且必须得有才能编译。 +`import`声明必须跟在文件的`package`声明之后。随后,则是组成程序的函数、变量、常量、类型的声明语句(分别由关键字`func`、`var`、`const`、`type`定义)。这些内容的声明顺序并不重要(译注:最好还是定一下规范)。这个例子的程序已经尽可能短了,只声明了一个函数,其中只调用了一个其他函数。为了节省篇幅,有些时候示例程序会省略`package`和`import`声明,但是,这些声明在源代码里有,并且必须得有才能编译。 一个函数的声明由`func`关键字、函数名、参数列表、返回值列表(这个例子里的`main`函数参数列表和返回值都是空的)以及包含在大括号里的函数体组成。第五章进一步考察函数。 -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`声明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装: ``` $ go get golang.org/x/tools/cmd/goimports ``` diff --git a/ch1/ch1-02.md b/ch1/ch1-02.md index b8d7307..50a169d 100644 --- a/ch1/ch1-02.md +++ b/ch1/ch1-02.md @@ -1,14 +1,14 @@ ## 1.2. 命令行参数 -大多数的程序都是处理输入,产生输出;这也正是“计算”的定义。但是, 程序如何获取要处理的输入数据呢?一些程序生成自己的数据,但通常情况下,输入来自于程序外部:文件、网络连接、其它程序的输出、敲键盘的用户、命令行参数或其它类似输入源。下面几个例子会讨论其中几个输入源,首先是命令行参数。 +大多数的程序都是处理输入,产生输出;这也正是“计算”的定义。但是,程序如何获取要处理的输入数据呢?一些程序生成自己的数据,但通常情况下,输入来自于程序外部:文件、网络连接、其它程序的输出、敲键盘的用户、命令行参数或其它类似输入源。下面几个例子会讨论其中几个输入源,首先是命令行参数。 `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工具格式化时按照字母顺序对包名排序。(示例有多个版本时,我们会对示例编号,这样可以明确当前正在讨论的是哪个。) gopl.io/ch1/echo1 ```go @@ -46,7 +46,7 @@ sep + os.Args[i] s += sep + os.Args[i] ``` -是一条*赋值语句*, 将s的旧值跟sep与os.Args[i]连接后赋值回s,等价于: +是一条*赋值语句*,将s的旧值跟sep与os.Args[i]连接后赋值回s,等价于: ```go s = s + sep + os.Args[i] @@ -56,7 +56,7 @@ s = s + sep + os.Args[i] 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`也非法。 @@ -68,7 +68,7 @@ for initialization; condition; post { } ``` -for循环三个部分不需括号包围。大括号强制要求, 左大括号必须和*post*语句在同一行。 +for循环三个部分不需括号包围。大括号强制要求,左大括号必须和*post*语句在同一行。 *initialization*语句是可选的,在循环开始前执行。*initalization*如果存在,必须是一条*简单语句*(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。`condition`是一个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为`true`则执行循环体语句。`post`语句在循环体执行结束后执行,之后再次对`condition`求值。`condition`值为`false`时,循环结束。 @@ -90,9 +90,9 @@ for { } ``` -这就变成一个无限循环,尽管如此,还可以用其他方式终止循环, 如一条`break`或`return`语句。 +这就变成一个无限循环,尽管如此,还可以用其他方式终止循环,如一条`break`或`return`语句。 -`for`循环的另一种形式, 在某种数据类型的区间(range)上遍历,如字符串或切片。`echo`的第二版本展示了这种形式: +`for`循环的另一种形式,在某种数据类型的区间(range)上遍历,如字符串或切片。`echo`的第二版本展示了这种形式: gopl.io/ch1/echo2 ```go @@ -114,9 +114,9 @@ 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`,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等价: @@ -129,7 +129,7 @@ var s string = "" 用哪种不用哪种,为什么呢?第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。 -如前文所述,每次循环迭代字符串s的内容都会更新。`+=`连接原字符串、空格和下个参数,产生新字符串, 并把它赋值给`s`。`s`原来的内容已经不再使用,将在适当时机对它进行垃圾回收。 +如前文所述,每次循环迭代字符串s的内容都会更新。`+=`连接原字符串、空格和下个参数,产生新字符串,并把它赋值给`s`。`s`原来的内容已经不再使用,将在适当时机对它进行垃圾回收。 如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用`strings`包的`Join`函数: diff --git a/ch1/ch1-04.md b/ch1/ch1-04.md index 6c21718..cefd2f2 100644 --- a/ch1/ch1-04.md +++ b/ch1/ch1-04.md @@ -80,7 +80,7 @@ gif.GIF是一个struct类型(参考4.4节)。struct是一组值或者叫字 lissajous函数内部有两层嵌套的for循环。外层循环会循环64次,每一次都会生成一个单独的动画帧。它生成了一个包含两种颜色的201*201大小的图片,白色和黑色。所有像素点都会被默认设置为其零值(也就是调色板palette里的第0个值),这里我们设置的是白色。每次外层循环都会生成一张新图片,并将一些像素设置为黑色。其结果会append到之前结果之后。这里我们用到了append(参考4.2.1)内置函数,将结果append到anim中的帧列表末尾,并设置一个默认的80ms的延迟值。循环结束后所有的延迟值被编码进了GIF图片中,并将结果写入到输出流。out这个变量是io.Writer类型,这个类型支持把输出结果写到很多目标,很快我们就可以看到例子。 -内层循环设置两个偏振值。x轴偏振使用sin函数。y轴偏振也是正弦波,但其相对x轴的偏振是一个0-3的随机值,初始偏振值是一个零值,随着动画的每一帧逐渐增加。循环会一直跑到x轴完成五次完整的循环。每一步它都会调用SetColorIndex来为(x, y)点来染黑色。 +内层循环设置两个偏振值。x轴偏振使用sin函数。y轴偏振也是正弦波,但其相对x轴的偏振是一个0-3的随机值,初始偏振值是一个零值,随着动画的每一帧逐渐增加。循环会一直跑到x轴完成五次完整的循环。每一步它都会调用SetColorIndex来为(x,y)点来染黑色。 main函数调用lissajous函数,用它来向标准输出流打印信息,所以下面这个命令会像图1.1中产生一个GIF动画。 diff --git a/ch1/ch1.md b/ch1/ch1.md index c0ee46e..1796f28 100644 --- a/ch1/ch1.md +++ b/ch1/ch1.md @@ -1,5 +1,5 @@ # 第一章 入门 -本章介绍Go语言的基础组件。本章提供了足够的信息和示例程序,希望可以帮你尽快入门, 写出有用的程序。本章和之后章节的示例程序都针对你可能遇到的现实案例。先了解几个Go程序,涉及的主题从简单的文件处理、图像处理到互联网客户端和服务端并发。当然,第一章不会解释细枝末节,但用这些程序来学习一门新语言还是很有效的。 +本章介绍Go语言的基础组件。本章提供了足够的信息和示例程序,希望可以帮你尽快入门,写出有用的程序。本章和之后章节的示例程序都针对你可能遇到的现实案例。先了解几个Go程序,涉及的主题从简单的文件处理、图像处理到互联网客户端和服务端并发。当然,第一章不会解释细枝末节,但用这些程序来学习一门新语言还是很有效的。 -学习一门新语言时,会有一种自然的倾向, 按照自己熟悉的语言的套路写新语言程序。学习Go语言的过程中,请警惕这种想法,尽量别这么做。我们会演示怎么写好Go语言程序,所以,请使用本书的代码作为你自己写程序时的指南。 +学习一门新语言时,会有一种自然的倾向,按照自己熟悉的语言的套路写新语言程序。学习Go语言的过程中,请警惕这种想法,尽量别这么做。我们会演示怎么写好Go语言程序,所以,请使用本书的代码作为你自己写程序时的指南。 diff --git a/ch10/ch10-07-3.md b/ch10/ch10-07-3.md index 0c3cfb0..24e61ad 100644 --- a/ch10/ch10-07-3.md +++ b/ch10/ch10-07-3.md @@ -4,7 +4,7 @@ 由于每个目录只包含一个包,因此每个对应可执行程序或者叫Unix术语中的命令的包,会要求放到一个独立的目录中。这些目录有时候会放在名叫cmd目录的子目录下面,例如用于提供Go文档服务的golang.org/x/tools/cmd/godoc命令就是放在cmd子目录(§10.7.4)。 -每个包可以由它们的导入路径指定,就像前面看到的那样,或者用一个相对目录的路径名指定,相对路径必须以`.`或`..`开头。如果没有指定参数,那么默认指定为当前目录对应的包。 下面的命令用于构建同一个包, 虽然它们的写法各不相同: +每个包可以由它们的导入路径指定,就像前面看到的那样,或者用一个相对目录的路径名指定,相对路径必须以`.`或`..`开头。如果没有指定参数,那么默认指定为当前目录对应的包。下面的命令用于构建同一个包,虽然它们的写法各不相同: ``` $ cd $GOPATH/src/gopl.io/ch1/helloworld diff --git a/ch11/ch11-02-2.md b/ch11/ch11-02-2.md index 6ddddb0..7f4e7b2 100644 --- a/ch11/ch11-02-2.md +++ b/ch11/ch11-02-2.md @@ -41,7 +41,7 @@ func echo(newline bool, sep string, args []string) error { } ``` -在测试中我们可以用各种参数和标志调用echo函数,然后检测它的输出是否正确, 我们通过增加参数来减少echo函数对全局变量的依赖。我们还增加了一个全局名为out的变量来替代直接使用os.Stdout,这样测试代码可以根据需要将out修改为不同的对象以便于检查。下面就是echo_test.go文件中的测试代码: +在测试中我们可以用各种参数和标志调用echo函数,然后检测它的输出是否正确,我们通过增加参数来减少echo函数对全局变量的依赖。我们还增加了一个全局名为out的变量来替代直接使用os.Stdout,这样测试代码可以根据需要将out修改为不同的对象以便于检查。下面就是echo_test.go文件中的测试代码: ```Go package main diff --git a/ch12/ch12-02.md b/ch12/ch12-02.md index caf7159..a956db3 100644 --- a/ch12/ch12-02.md +++ b/ch12/ch12-02.md @@ -1,8 +1,8 @@ -## 12.2. reflect.Type和reflect.Value +## 12.2. reflect.Type 和 reflect.Value -反射是由 reflect 包提供的。 它定义了两个重要的类型, Type 和 Value. 一个 Type 表示一个Go类型. 它是一个接口, 有许多方法来区分类型以及检查它们的组成部分, 例如一个结构体的成员或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息(§7.5), 也正是这个实体标识了接口值的动态类型. +反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。一个 Type 表示一个Go类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。唯一能反映 reflect.Type 实现的是接口的类型描述信息(§7.5),也正是这个实体标识了接口值的动态类型。 -函数 reflect.TypeOf 接受任意的 interface{} 类型, 并以reflect.Type形式返回其动态类型: +函数 reflect.TypeOf 接受任意的 interface{} 类型,并以 reflect.Type 形式返回其动态类型: ```Go t := reflect.TypeOf(3) // a reflect.Type @@ -10,22 +10,22 @@ fmt.Println(t.String()) // "int" fmt.Println(t) // "int" ``` -其中 TypeOf(3) 调用将值 3 传给 interface{} 参数. 回到 7.5节 的将一个具体的值转为接口类型会有一个隐式的接口转换操作, 它会创建一个包含两个信息的接口值: 操作数的动态类型(这里是int)和它的动态的值(这里是3). +其中 TypeOf(3) 调用将值 3 传给 interface{} 参数。回到 7.5节 的将一个具体的值转为接口类型会有一个隐式的接口转换操作,它会创建一个包含两个信息的接口值:操作数的动态类型(这里是 int)和它的动态的值(这里是 3)。 -因为 reflect.TypeOf 返回的是一个动态类型的接口值, 它总是返回具体的类型. 因此, 下面的代码将打印 "*os.File" 而不是 "io.Writer". 稍后, 我们将看到能够表达接口类型的 reflect.Type. +因为 reflect.TypeOf 返回的是一个动态类型的接口值,它总是返回具体的类型。因此,下面的代码将打印 "*os.File" 而不是 "io.Writer"。稍后,我们将看到能够表达接口类型的 reflect.Type。 ```Go var w io.Writer = os.Stdout fmt.Println(reflect.TypeOf(w)) // "*os.File" ``` -要注意的是 reflect.Type 接口是满足 fmt.Stringer 接口的. 因为打印一个接口的动态类型对于调试和日志是有帮助的, fmt.Printf 提供了一个缩写 %T 参数, 内部使用 reflect.TypeOf 来输出: +要注意的是 reflect.Type 接口是满足 fmt.Stringer 接口的。因为打印一个接口的动态类型对于调试和日志是有帮助的, fmt.Printf 提供了一个缩写 %T 参数,内部使用 reflect.TypeOf 来输出: ```Go fmt.Printf("%T\n", 3) // "int" ``` -reflect 包中另一个重要的类型是 Value. 一个 reflect.Value 可以装载任意类型的值. 函数 reflect.ValueOf 接受任意的 interface{} 类型, 并返回一个装载着其动态值的 reflect.Value. 和 reflect.TypeOf 类似, reflect.ValueOf 返回的结果也是具体的类型, 但是 reflect.Value 也可以持有一个接口值. +reflect 包中另一个重要的类型是 Value。一个 reflect.Value 可以装载任意类型的值。函数 reflect.ValueOf 接受任意的 interface{} 类型,并返回一个装载着其动态值的 reflect.Value。和 reflect.TypeOf 类似,reflect.ValueOf 返回的结果也是具体的类型,但是 reflect.Value 也可以持有一个接口值。 ```Go v := reflect.ValueOf(3) // a reflect.Value @@ -34,16 +34,16 @@ fmt.Printf("%v\n", v) // "3" fmt.Println(v.String()) // NOTE: "" ``` -和 reflect.Type 类似, reflect.Value 也满足 fmt.Stringer 接口, 但是除非 Value 持有的是字符串, 否则 String 方法只返回其类型. 而使用 fmt 包的 %v 标志参数会对 reflect.Values 特殊处理. +和 reflect.Type 类似,reflect.Value 也满足 fmt.Stringer 接口,但是除非 Value 持有的是字符串,否则 String 方法只返回其类型。而使用 fmt 包的 %v 标志参数会对 reflect.Values 特殊处理。 -对 Value 调用 Type 方法将返回具体类型所对应的 reflect.Type: +对 Value 调用 Type 方法将返回具体类型所对应的 reflect.Type: ```Go t := v.Type() // a reflect.Type fmt.Println(t.String()) // "int" ``` -reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法. 它返回一个 interface{} 类型,装载着与 reflect.Value 相同的具体值: +reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法。它返回一个 interface{} 类型,装载着与 reflect.Value 相同的具体值: ```Go v := reflect.ValueOf(3) // a reflect.Value @@ -52,9 +52,9 @@ i := x.(int) // an int fmt.Printf("%d\n", i) // "3" ``` -reflect.Value 和 interface{} 都能装载任意的值. 所不同的是, 一个空的接口隐藏了值内部的表示方式和所有方法, 因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样), 内部值我们没法访问. 相比之下, 一个 Value 则有很多方法来检查其内容, 无论它的具体类型是什么. 让我们再次尝试实现我们的格式化函数 format.Any. +reflect.Value 和 interface{} 都能装载任意的值。所不同的是,一个空的接口隐藏了值内部的表示方式和所有方法,因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样),内部值我们没法访问。相比之下,一个 Value 则有很多方法来检查其内容,无论它的具体类型是什么。让我们再次尝试实现我们的格式化函数 format.Any。 -我们使用 reflect.Value 的 Kind 方法来替代之前的类型 switch. 虽然还是有无穷多的类型, 但是它们的kinds类型却是有限的: Bool, String 和 所有数字类型的基础类型; Array 和 Struct 对应的聚合类型; Chan, Func, Ptr, Slice, 和 Map 对应的引用类型; interface 类型; 还有表示空值的 Invalid 类型. (空的 reflect.Value 的 kind 即为 Invalid.) +我们使用 reflect.Value 的 Kind 方法来替代之前的类型 switch。虽然还是有无穷多的类型,但是它们的 kinds 类型却是有限的:Bool、String 和 所有数字类型的基础类型;Array 和 Struct 对应的聚合类型;Chan、Func、Ptr、Slice 和 Map 对应的引用类型;interface 类型;还有表示空值的 Invalid 类型。(空的 reflect.Value 的 kind 即为 Invalid。) gopl.io/ch12/format ```Go @@ -95,7 +95,7 @@ func formatAtom(v reflect.Value) string { } ``` -到目前为止, 我们的函数将每个值视作一个不可分割没有内部结构的物品, 因此它叫 formatAtom. 对于聚合类型(结构体和数组)和接口,只是打印值的类型, 对于引用类型(channels, functions, pointers, slices, 和 maps), 打印类型和十六进制的引用地址. 虽然还不够理想, 但是依然是一个重大的进步, 并且 Kind 只关心底层表示, format.Any 也支持具名类型. 例如: +到目前为止,我们的函数将每个值视作一个不可分割没有内部结构的物品,因此它叫 formatAtom。对于聚合类型(结构体和数组)和接口,只是打印值的类型,对于引用类型(channels、functions、pointers、slices 和 maps),打印类型和十六进制的引用地址。虽然还不够理想,但是依然是一个重大的进步,并且 Kind 只关心底层表示,format.Any 也支持具名类型。例如: ```Go var x int64 = 1 diff --git a/ch12/ch12-03.md b/ch12/ch12-03.md index cdf38a3..0fb6610 100644 --- a/ch12/ch12-03.md +++ b/ch12/ch12-03.md @@ -157,7 +157,7 @@ Display("os.Stderr", os.Stderr) // (*(*os.Stderr).file).nepipe = 0 ``` -可以看出,反射能够访问到结构体中未导出的成员。需要当心的是这个例子的输出在不同操作系统上可能是不同的,并且随着标准库的发展也可能导致结果不同。(这也是将这些成员定义为私有成员的原因之一!)我们甚至可以用Display函数来显示reflect.Value 的内部构造(在这里设置为`*os.File`的类型描述体)。`Display("rV", reflect.ValueOf(os.Stderr))`调用的输出如下,当然不同环境得到的结果可能有差异: +可以看出,反射能够访问到结构体中未导出的成员。需要当心的是这个例子的输出在不同操作系统上可能是不同的,并且随着标准库的发展也可能导致结果不同。(这也是将这些成员定义为私有成员的原因之一!)我们甚至可以用Display函数来显示reflect.Value 的内部构造(在这里设置为`*os.File`的类型描述体)。`Display("rV", reflect.ValueOf(os.Stderr))`调用的输出如下,当然不同环境得到的结果可能有差异: ```Go Display rV (reflect.Value): diff --git a/ch12/ch12-08.md b/ch12/ch12-08.md index 08511e1..34d6f66 100644 --- a/ch12/ch12-08.md +++ b/ch12/ch12-08.md @@ -37,4 +37,4 @@ methods.Print(new(strings.Replacer)) // type *strings.Replacer // func (*strings.Replacer) Replace(string) string // func (*strings.Replacer) WriteString(io.Writer, string) (int, error) -```` +``` diff --git a/ch13/ch13-01.md b/ch13/ch13-01.md index 08fdc6a..002a625 100644 --- a/ch13/ch13-01.md +++ b/ch13/ch13-01.md @@ -38,11 +38,11 @@ struct{ bool; int16; float64 } // 2 words 3words 关于内存地址对齐算法的细节超出了本书的范围,也不是每一个结构体都需要担心这个问题,不过有效的包装可以使数据结构更加紧凑(译注:未来的Go语言编译器应该会默认优化结构体的顺序,当然应该也能够指定具体的内存布局,相同讨论请参考 [Issue10014](https://github.com/golang/go/issues/10014) ),内存使用率和性能都可能会受益。 -`unsafe.Alignof` 函数返回对应参数的类型需要对齐的倍数. 和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量. 通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小. +`unsafe.Alignof` 函数返回对应参数的类型需要对齐的倍数。和 Sizeof 类似, Alignof 也是返回一个常量表达式,对应一个常量。通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节),其它的类型对齐到机器字大小。 -`unsafe.Offsetof` 函数的参数必须是一个字段 `x.f`, 然后返回 `f` 字段相对于 `x` 起始地址的偏移量, 包括可能的空洞. +`unsafe.Offsetof` 函数的参数必须是一个字段 `x.f`,然后返回 `f` 字段相对于 `x` 起始地址的偏移量,包括可能的空洞。 -图 13.1 显示了一个结构体变量 x 以及其在32位和64位机器上的典型的内存. 灰色区域是空洞. +图 13.1 显示了一个结构体变量 x 以及其在32位和64位机器上的典型的内存。灰色区域是空洞。 ```Go var x struct { diff --git a/ch13/ch13-04.md b/ch13/ch13-04.md index 123f148..98490de 100644 --- a/ch13/ch13-04.md +++ b/ch13/ch13-04.md @@ -2,7 +2,7 @@ Go程序可能会遇到要访问C语言的某些硬件驱动函数的场景,或者是从一个C++语言实现的嵌入式数据库查询记录的场景,或者是使用Fortran语言实现的一些线性代数库的场景。C语言作为一个通用语言,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现(译者:Go语言需要也应该拥抱这些巨大的代码遗产)。 -在本节中,我们将构建一个简易的数据压缩程序,使用了一个Go语言自带的叫cgo的用于支援C语言函数调用的工具。这类工具一般被称为 *foreign-function interfaces* (简称ffi), 并且在类似工具中cgo也不是唯一的。SWIG( http://swig.org )是另一个类似的且被广泛使用的工具,SWIG提供了很多复杂特性以支援C++的特性,但SWIG并不是我们要讨论的主题。 +在本节中,我们将构建一个简易的数据压缩程序,使用了一个Go语言自带的叫cgo的用于支援C语言函数调用的工具。这类工具一般被称为 *foreign-function interfaces* (简称ffi),并且在类似工具中cgo也不是唯一的。SWIG(http://swig.org)是另一个类似的且被广泛使用的工具,SWIG提供了很多复杂特性以支援C++的特性,但SWIG并不是我们要讨论的主题。 在标准库的`compress/...`子包有很多流行的压缩算法的编码和解码实现,包括流行的LZW压缩算法(Unix的compress命令用的算法)和DEFLATE压缩算法(GNU gzip命令用的算法)。这些包的API的细节虽然有些差异,但是它们都提供了针对 io.Writer类型输出的压缩接口和提供了针对io.Reader类型输入的解压缩接口。例如: @@ -37,7 +37,7 @@ bzip2压缩算法,是基于优雅的Burrows-Wheeler变换算法,运行速度 // pointers to Go variables. ``` -要使用libbzip2,我们需要先构建一个bz_stream结构体,用于保持输入和输出缓存。然后有三个函数:BZ2_bzCompressInit用于初始化缓存,BZ2_bzCompress用于将输入缓存的数据压缩到输出缓存,BZ2_bzCompressEnd用于释放不需要的缓存。(目前不要担心包的具体结构, 这个例子的目的就是演示各个部分如何组合在一起的。) +要使用libbzip2,我们需要先构建一个bz_stream结构体,用于保持输入和输出缓存。然后有三个函数:BZ2_bzCompressInit用于初始化缓存,BZ2_bzCompress用于将输入缓存的数据压缩到输出缓存,BZ2_bzCompressEnd用于释放不需要的缓存。(目前不要担心包的具体结构,这个例子的目的就是演示各个部分如何组合在一起的。) 我们可以在Go代码中直接调用BZ2_bzCompressInit和BZ2_bzCompressEnd,但是对于BZ2_bzCompress,我们将定义一个C语言的包装函数,用它完成真正的工作。下面是C代码,对应一个独立的文件。 @@ -204,7 +204,7 @@ $ ./bzipper < /usr/share/dict/words | bunzip2 | sha256sum 126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed - ``` -我们演示了如何将一个C语言库链接到Go语言程序。相反, 将Go编译为静态库然后链接到C程序,或者将Go程序编译为动态库然后在C程序中动态加载也都是可行的(译注:在Go1.5中,Windows系统的Go语言实现并不支持生成C语言动态库或静态库的特性。不过好消息是,目前已经有人在尝试解决这个问题,具体请访问 [Issue11058](https://github.com/golang/go/issues/11058) )。这里我们只展示的cgo很小的一些方面,更多的关于内存管理、指针、回调函数、中断信号处理、字符串、errno处理、终结器,以及goroutines和系统线程的关系等,有很多细节可以讨论。特别是如何将Go语言的指针传入C函数的规则也是异常复杂的(译注:简单来说,要传入C函数的Go指针指向的数据本身不能包含指针或其他引用类型;并且C函数在返回后不能继续持有Go指针;并且在C函数返回之前,Go指针是被锁定的,不能导致对应指针数据被移动或栈的调整),部分的原因在13.2节有讨论到,但是在Go1.5中还没有被明确(译注:Go1.6将会明确cgo中的指针使用规则)。如果要进一步阅读,可以从 https://golang.org/cmd/cgo 开始。 +我们演示了如何将一个C语言库链接到Go语言程序。相反,将Go编译为静态库然后链接到C程序,或者将Go程序编译为动态库然后在C程序中动态加载也都是可行的(译注:在Go1.5中,Windows系统的Go语言实现并不支持生成C语言动态库或静态库的特性。不过好消息是,目前已经有人在尝试解决这个问题,具体请访问 [Issue11058](https://github.com/golang/go/issues/11058) )。这里我们只展示的cgo很小的一些方面,更多的关于内存管理、指针、回调函数、中断信号处理、字符串、errno处理、终结器,以及goroutines和系统线程的关系等,有很多细节可以讨论。特别是如何将Go语言的指针传入C函数的规则也是异常复杂的(译注:简单来说,要传入C函数的Go指针指向的数据本身不能包含指针或其他引用类型;并且C函数在返回后不能继续持有Go指针;并且在C函数返回之前,Go指针是被锁定的,不能导致对应指针数据被移动或栈的调整),部分的原因在13.2节有讨论到,但是在Go1.5中还没有被明确(译注:Go1.6将会明确cgo中的指针使用规则)。如果要进一步阅读,可以从 https://golang.org/cmd/cgo 开始。 **练习 13.3:** 使用sync.Mutex以保证bzip2.writer在多个goroutines中被并发调用是安全的。 diff --git a/ch13/ch13.md b/ch13/ch13.md index 3ab92a5..e1a2ce1 100644 --- a/ch13/ch13.md +++ b/ch13/ch13.md @@ -16,5 +16,4 @@ Go语言的实现刻意隐藏了很多底层细节。我们无法知道一个结 要注意的是,unsafe包是一个采用特殊方式实现的包。虽然它可以和普通包一样的导入和使用,但它实际上是由编译器实现的。它提供了一些访问语言内部特性的方法,特别是内存布局相关的细节。将这些特性封装到一个独立的包中,是为在极少数情况下需要使用的时候,同时引起人们的注意(译注:因为看包的名字就知道使用unsafe包是不安全的)。此外,有一些环境因为安全的因素可能限制这个包的使用。 -不过unsafe包被广泛地用于比较低级的包, 例如runtime、os、syscall还有net包等,因为它们需要和操作系统密切配合,但是对于普通的程序一般是不需要使用unsafe包的。 - +不过unsafe包被广泛地用于比较低级的包,例如runtime、os、syscall还有net包等,因为它们需要和操作系统密切配合,但是对于普通的程序一般是不需要使用unsafe包的。 diff --git a/ch2/ch2-03-3.md b/ch2/ch2-03-3.md index ba4ec2c..13f9621 100644 --- a/ch2/ch2-03-3.md +++ b/ch2/ch2-03-3.md @@ -32,7 +32,7 @@ q := new(int) fmt.Println(p == q) // "false" ``` -当然也可能有特殊情况:如果两个类型都是空的,也就是说类型的大小是0,例如`struct{}`和 `[0]int`, 有可能有相同的地址(依赖具体的语言实现)(译注:请谨慎使用大小为0的类型,因为如果类型的大小为0的话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看`runtime.SetFinalizer`函数相关文档)。 +当然也可能有特殊情况:如果两个类型都是空的,也就是说类型的大小是0,例如`struct{}`和`[0]int`,有可能有相同的地址(依赖具体的语言实现)(译注:请谨慎使用大小为0的类型,因为如果类型的大小为0的话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看`runtime.SetFinalizer`函数相关文档)。 new函数使用通常相对比较少,因为对于结构体来说,直接用字面量语法创建新变量的方法会更灵活(§4.4.1)。 diff --git a/ch3/ch3-01.md b/ch3/ch3-01.md index 4de8deb..a95ea7b 100644 --- a/ch3/ch3-01.md +++ b/ch3/ch3-01.md @@ -73,7 +73,7 @@ Go语言还提供了以下的bit位操作运算符,前面4个操作运算符 >> 右移 ``` -位操作运算符`^`作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符`&^`用于按位置零(AND NOT):如果对应y中bit位为1的话, 表达式`z = x &^ y`结果z的对应的bit位为0,否则z对应的bit位等于x相应的bit位的值。 +位操作运算符`^`作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符`&^`用于按位置零(AND NOT):如果对应y中bit位为1的话,表达式`z = x &^ y`结果z的对应的bit位为0,否则z对应的bit位等于x相应的bit位的值。 下面的代码演示了如何使用位操作解释uint8类型值的8个独立的bit位。它使用了Printf函数的%b参数打印二进制格式的数字;其中%08b中08表示打印至少8个字符宽度,不足的前缀部分用0填充。 diff --git a/ch3/ch3-02.md b/ch3/ch3-02.md index d7ab416..f2952b6 100644 --- a/ch3/ch3-02.md +++ b/ch3/ch3-02.md @@ -135,15 +135,15 @@ func f(x, y float64) float64 { 要注意的是corner函数返回了两个结果,分别对应每个网格顶点的坐标参数。 -要解释这个程序是如何工作的需要一些基本的几何学知识,但是我们可以跳过几何学原理,因为程序的重点是演示浮点数运算。程序的本质是三个不同的坐标系中映射关系,如图3.2所示。第一个是100x100的二维网格,对应整数坐标(i,j),从远处的(0, 0)位置开始。我们从远处向前面绘制,因此远处先绘制的多边形有可能被前面后绘制的多边形覆盖。 +要解释这个程序是如何工作的需要一些基本的几何学知识,但是我们可以跳过几何学原理,因为程序的重点是演示浮点数运算。程序的本质是三个不同的坐标系中映射关系,如图3.2所示。第一个是100x100的二维网格,对应整数坐标(i,j),从远处的(0,0)位置开始。我们从远处向前面绘制,因此远处先绘制的多边形有可能被前面后绘制的多边形覆盖。 第二个坐标系是一个三维的网格浮点坐标(x,y,z),其中x和y是i和j的线性函数,通过平移转换为网格单元的中心,然后用xyrange系数缩放。高度z是函数f(x,y)的值。 -第三个坐标系是一个二维的画布,起点(0,0)在左上角。画布中点的坐标用(sx, sy)表示。我们使用等角投影将三维点 +第三个坐标系是一个二维的画布,起点(0,0)在左上角。画布中点的坐标用(sx,sy)表示。我们使用等角投影将三维点(x,y,z)投影到二维的画布中。 ![](../images/ch3-02.png) -(x,y,z)投影到二维的画布中。画布中从远处到右边的点对应较大的x值和较大的y值。并且画布中x和y值越大,则对应的z值越小。x和y的垂直和水平缩放系数来自30度角的正弦和余弦值。z的缩放系数0.4,是一个任意选择的参数。 +画布中从远处到右边的点对应较大的x值和较大的y值。并且画布中x和y值越大,则对应的z值越小。x和y的垂直和水平缩放系数来自30度角的正弦和余弦值。z的缩放系数0.4,是一个任意选择的参数。 对于二维网格中的每一个网格单元,main函数计算单元的四个顶点在画布中对应多边形ABCD的顶点,其中B对应(i,j)顶点位置,A、C和D是其它相邻的顶点,然后输出SVG的绘制指令。 diff --git a/ch3/ch3-03.md b/ch3/ch3-03.md index f2b46e3..bc38e8e 100644 --- a/ch3/ch3-03.md +++ b/ch3/ch3-03.md @@ -93,4 +93,4 @@ func mandelbrot(z complex128) color.Color { **练习 3.8:** 通过提高精度来生成更多级别的分形。使用四种不同精度类型的数字实现相同的分形:complex64、complex128、big.Float和big.Rat。(后面两种类型在math/big包声明。Float是有指定限精度的浮点数;Rat是无限精度的有理数。)它们间的性能和内存使用对比如何?当渲染图可见时缩放的级别是多少? -**练习 3.9:** 编写一个web服务器,用于给客户端生成分形的图像。运行客户端通过HTTP参数指定x,y和zoom参数。 +**练习 3.9:** 编写一个web服务器,用于给客户端生成分形的图像。运行客户端通过HTTP参数指定x、y和zoom参数。 diff --git a/ch3/ch3-04.md b/ch3/ch3-04.md index ec1737d..e00ccb1 100644 --- a/ch3/ch3-04.md +++ b/ch3/ch3-04.md @@ -29,7 +29,7 @@ if b { } ``` -如果需要经常做类似的转换, 包装成一个函数会更方便: +如果需要经常做类似的转换,包装成一个函数会更方便: ```Go // btoi returns 1 if b is true and 0 if false. @@ -41,7 +41,7 @@ func btoi(b bool) int { } ``` -数字到布尔型的逆转换则非常简单, 不过为了保持对称, 我们也可以包装一个函数: +数字到布尔型的逆转换则非常简单,不过为了保持对称,我们也可以包装一个函数: ```Go // itob reports whether i is non-zero. diff --git a/ch5/ch5-01.md b/ch5/ch5-01.md index b643bc0..9853f47 100644 --- a/ch5/ch5-01.md +++ b/ch5/ch5-01.md @@ -8,8 +8,7 @@ func name(parameter-list) (result-list) { } ``` -形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部变量,其值由参数调用者提供。返回值列表描述了函数返回值的变量名以及类型。如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。 -在hypot函数中, +形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部变量,其值由参数调用者提供。返回值列表描述了函数返回值的变量名以及类型。如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。在hypot函数中: ```Go func hypot(x, y float64) float64 { @@ -18,7 +17,7 @@ func hypot(x, y float64) float64 { fmt.Println(hypot(3,4)) // "5" ``` -x和y是形参名,3和4是调用时的传入的实参,函数返回了一个float64类型的值。 +x和y是形参名,3和4是调用时的传入的实参,函数返回了一个float64类型的值。 返回值也可以像形式参数一样被命名。在这种情况下,每个返回值被声明成一个局部变量,并根据该返回值的类型,将其初始化为该类型的零值。 如果一个函数在声明时,包含返回值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic异常或函数中存在无限循环。 diff --git a/ch5/ch5-02.md b/ch5/ch5-02.md index c63e6da..4ec9a3d 100644 --- a/ch5/ch5-02.md +++ b/ch5/ch5-02.md @@ -4,7 +4,7 @@ 下文的示例代码使用了非标准包 golang.org/x/net/html ,解析HTML。golang.org/x/... 目录下存储了一些由Go团队设计、维护,对网络编程、国际化文件处理、移动平台、图像处理、加密解密、开发者工具提供支持的扩展包。未将这些扩展包加入到标准库原因有二,一是部分包仍在开发中,二是对大多数Go语言的开发者而言,扩展包提供的功能很少被使用。 -例子中调用golang.org/x/net/html的部分api如下所示。html.Parse函数读入一组bytes解析后,返回html.Node类型的HTML页面树状结构根节点。HTML拥有很多类型的结点如text(文本),commnets(注释)类型,在下面的例子中,我们 只关注< name key='value' >形式的结点。 +例子中调用golang.org/x/net/html的部分api如下所示。html.Parse函数读入一组bytes解析后,返回html.Node类型的HTML页面树状结构根节点。HTML拥有很多类型的结点如text(文本)、commnets(注释)类型,在下面的例子中,我们 只关注< name key='value' >形式的结点。 golang.org/x/net/html ```Go @@ -157,6 +157,6 @@ $ ./fetch https://golang.org | ./outline **练习 5.2:** 编写函数,记录在HTML树中出现的同名元素的次数。 -**练习 5.3:** 编写函数输出所有text结点的内容。注意不要访问`