Merge branch 'master' of github.com:chai2010/book-gopl-zh

This commit is contained in:
Xargin 2016-08-10 23:16:56 +08:00
commit 676d2c6cb9
16 changed files with 21 additions and 21 deletions

View File

@ -17,6 +17,8 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本
- http://2goo.info/media/html/gopl-zh-gh-pages/
- http://docs.plhwin.com/gopl-zh/
**注意,在线预览不是最新版,最新以仓库里的内容为准**
### 从源文件构建

View File

@ -1,6 +1,6 @@
## Go语言起源
编程语言的演化跟生物物种的演化类似,一个成功的编程语言的后代一般都会继承它们祖先的优点;当然有时多种语言杂合也可能会产生令人惊讶的特性;还有一些激进的新特性可能并没有先例。我们可以通过观察编程语言和软硬件环境是如何相互促进、相互影响的演化过程而学到很多
编程语言的演化跟生物物种的演化类似,一个成功的编程语言的后代一般都会继承它们祖先的优点;当然有时多种语言杂合也可能会产生令人惊讶的特性;还有一些激进的新特性可能并没有先例。通过观察这些影响,我们可以学到为什么一门语言是这样子的,它已经适应了怎样的环境
下图展示了有哪些早期的编程语言对Go语言的设计产生了重要影响。

View File

@ -10,6 +10,6 @@ Go项目包括编程语言本身附带了相关的工具和标准库最后
Go语言有足够的类型系统以避免动态语言中那些粗心的类型错误但是Go语言的类型系统相比传统的强类型语言又要简洁很多。虽然有时候这会导致一个“无类型”的抽象类型概念但是Go语言程序员并不需要像C++或Haskell程序员那样纠结于具体类型的安全属性。在实践中Go语言简洁的类型系统给程序员带来了更多的安全性和更好的运行时性能。
Go语言鼓励当代计算机系统设计的原则特别是局部的重要性。它的内置数据类型和大多数的准库数据结构都经过精心设计而避免显式的初始化或隐式的构造函数因为很少的内存分配和内存初始化代码被隐藏在库代码中了。Go语言的聚合类型结构体和数组可以直接操作它们的元素只需要更少的存储空间、更少的内存分配而且指针操作比其他间接操作的语言也更有效率。由于现代计算机是一个并行的机器Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小因此创建一个goroutine的代价很小创建百万级的goroutine完全是可行的。
Go语言鼓励当代计算机系统设计的原则特别是局部的重要性。它的内置数据类型和大多数的准库数据结构都经过精心设计而避免显式的初始化或隐式的构造函数因为很少的内存分配和内存初始化代码被隐藏在库代码中了。Go语言的聚合类型结构体和数组可以直接操作它们的元素只需要更少的存储空间、更少的内存写操作而且指针操作比其他间接操作的语言也更有效率。由于现代计算机是一个并行的机器Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小因此创建一个goroutine的代价很小创建百万级的goroutine完全是可行的。
Go语言的标准库通常被称为语言自带的电池提供了清晰的构建模块和公共接口包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等并支持许多标准化的文件格式和编解码协议。库和工具使用了大量的约定来减少额外的配置和解释从而最终简化程序的逻辑而且每个Go程序结构都是如此的相似因此Go程序也很容易学习。使用Go语言自带工具构建Go语言项目只需要使用文件名和标识符名称, 一个偶尔的特殊注释来确定所有的库、可执行文件、测试、基准测试、例子、以及特定于平台的变量、项目的文档等Go语言源代码本身就包含了构建规范。

View File

@ -18,7 +18,7 @@ Go语言的面向对象机制与一般语言不同。它没有类层次结构
第十二章讨论了反射一种程序在运行期间审视自己的能力。反射是一个强大的编程工具不过要谨慎地使用这一章利用反射机制实现一些重要的Go语言库函数, 展示了反射的强大用法。第十三章解释了底层编程的细节在必要时可以使用unsafe包绕过Go语言安全的类型系统。
部分章节的后面有练习题根据对Go语言的理解修改书中的例子来探索Go语言的用法
每一章都有一些练习题你可以用来测试你对Go的理解你也可以探讨书中这些例子的扩展和替代
书中所有的代码都可以从 http://gopl.io 上的Git仓库下载。go get命令根据每个例子的导入路径智能地获取、构建并安装。只需要选择一个目录作为工作空间然后将GOPATH环境变量设置为该路径。

View File

@ -54,7 +54,7 @@ Go的标准库提供了100多个包以支持常见功能如输入、输出
`main`包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在`main`里的`main` *函数* 也很特殊它是整个程序执行时的入口译注C系语言差不多都这样。`main`函数所做的事情就是程序做的。当然了,`main`函数一般调用其它包里的函数完成很多工作, 比如, `fmt.Println`
必须告诉编译器源文件需要哪些包,这就是`import`声明以及随后的`package`声明扮演的角色。hello world例子只用到了一个包大多数程序需要导入多个包。
必须告诉编译器源文件需要哪些包,这就是跟随在`package`声明后面的`import`声明扮演的角色。hello world例子只用到了一个包大多数程序需要导入多个包。
必须恰当导入需要的包缺少了必要的包或者导入了不需要的包程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包译注Go语言编译过程没有警告信息争议特性之一

View File

@ -56,7 +56,7 @@ counts[line] = counts[line] + 1
input := bufio.NewScanner(os.Stdin)
```
该变量从程序的标准输入中读取内容。每次调用`input.Scan()`,即读入下一行,并移除行末的换行符;读取的内容可以调用`input.Text()`得到。`Scan`函数在读到一行时返回`true`在无输入时返回`false`。
该变量从程序的标准输入中读取内容。每次调用`input.Scan()`,即读入下一行,并移除行末的换行符;读取的内容可以调用`input.Text()`得到。`Scan`函数在读到一行时返回`true`不再有输入时返回`false`。
类似于C或其它语言里的`printf`函数,`fmt.Printf`函数对一些表达式产生格式化输出。该函数的首个参数是个格式字符串指定后续参数被如何格式化。各个参数的格式取决于“转换字符”conversion character形式为百分号后跟一个字母。举个例子`%d`表示以十进制形式打印一个整型操作数,而`%s`则表示把字符串型操作数的值展开。

View File

@ -1,7 +1,7 @@
## 11.1. go test
go test命令是一个按照一定的约定和组织的测试代码的驱动程序。在包目录内所有以_test.go为后缀名的源文件并不是go build构建包的一部分它们是go test测试的一部分。
go test命令是一个按照一定的约定和组织来测试代码的程序。在包目录内,所有以`_test.go`为后缀名的源文件在执行go build时不会被构建成包的一部分它们是go test测试的一部分。
\*_test.go文件中有三种类型的函数测试函数、基准测试函数、示例函数。一个测试函数是以Test为函数名前缀的函数用于测试程序的一些逻辑行为是否正确go test命令会调用这些测试函数并报告测试结果是PASS或FAIL。基准测试函数是以Benchmark为函数名前缀的函数它们用于衡量一些函数的性能go test命令会多次运行基准函数以计算一个平均的执行时间。示例函数是以Example为函数名前缀的函数提供一个由编译器保证正确性的示例文档。我们将在11.2节讨论测试函数的所有细节,在11.4节讨论基准测试函数的细节然后在11.6节讨论示例函数的细节。
`*_test.go`文件中,有三种类型的函数:测试函数、基准测试(benchmark)函数、示例函数。一个测试函数是以Test为函数名前缀的函数用于测试程序的一些逻辑行为是否正确go test命令会调用这些测试函数并报告测试结果是PASS或FAIL。基准测试函数是以Benchmark为函数名前缀的函数它们用于衡量一些函数的性能go test命令会多次运行基准函数以计算一个平均的执行时间。示例函数是以Example为函数名前缀的函数提供一个由编译器保证正确性的示例文档。我们将在11.2节讨论测试函数的所有细节,在11.4节讨论基准测试函数的细节然后在11.6节讨论示例函数的细节。
go test命令会遍历所有的\*_test.go文件中符合上述命名规则的函数然后生成一个临时的main包用于调用相应的测试函数然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
go test命令会遍历所有的`*_test.go`文件中符合上述命名规则的函数生成一个临时的main包用于调用相应的测试函数接着构建并运行、报告测试结果,最后清理测试中生成的临时文件。

View File

@ -27,7 +27,7 @@ continue for import return var
panic recover
```
这些内部预先定义的名字并不是关键字,你可以定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。
这些内部预先定义的名字并不是关键字,你可以定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。
如果一个名字是在函数内部定义那么它的就只在函数内部有效。如果是在函数外部定义那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的译注必须是在函数外部定义的包级名字包级函数名本身也是包级名字那么它将是导出的也就是说可以被外部的包访问例如fmt包的Printf函数就是导出的可以在fmt包外部访问。包本身的名字一般总是用小写字母。

View File

@ -18,7 +18,7 @@ fmt.Println(x) // "2"
变量有时候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必须能接受`&`取地址操作。
任何类型的指针的零值都是nil。如果`p != nil`测试为真那么p是指向某个有效变量。指针之间也是可以进行相等测试的只有当它们指向同一个变量或全部是nil时才相等。
任何类型的指针的零值都是nil。如果p指向某个有效变量那么`p != nil`测试为真。指针之间也是可以进行相等测试的只有当它们指向同一个变量或全部是nil时才相等。
```Go
var x, y int

View File

@ -6,7 +6,7 @@ var声明语句可以创建一个特定类型的变量然后给变量附加
var 变量名字 类型 = 表达式
```
其中“*类型*”或“*= 表达式*”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0布尔类型变量对应的零值是false字符串类型对应的零值是空字符串接口或引用类型包括slice、map、chan和函数变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
其中“*类型*”或“*= 表达式*”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0布尔类型变量对应的零值是false字符串类型对应的零值是空字符串接口或引用类型包括slice、指针、map、chan和函数变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
零值初始化机制可以确保每个声明的变量总是有一个良好定义的值因此在Go语言中不存在未初始化的变量。这个特性可以简化很多代码而且可以在没有增加额外工作的前提下确保边界条件下的合理行为。例如
@ -39,6 +39,3 @@ var f, err = os.Open(name) // os.Open returns a file and an error
{% include "./ch2-03-3.md" %}
{% include "./ch2-03-4.md" %}

View File

@ -8,7 +8,7 @@ Go语言中的包和其他语言的库或模块的概念类似目的都是为
为了演示包基本的用法先假设我们的温度转换软件已经很流行我们希望到Go语言社区也能使用这个包。我们该如何做呢
让我们创建一个名为gopl.io/ch2/tempconv的包这是前面例子的一个改进版本。我们约定我们的例子都是以章节顺序来编号的,这样的路径更容易阅读)包代码存储在两个源文件中,用来演示如何在一个源文件声明然后在其他的源文件访问;虽然在现实中,这样小的包一般只需要一个文件。
让我们创建一个名为gopl.io/ch2/tempconv的包这是前面例子的一个改进版本。这里我们没有按照惯例按顺序对例子进行编号,因此包路径看起来更像一个真实的包)包代码存储在两个源文件中,用来演示如何在一个源文件声明然后在其他的源文件访问;虽然在现实中,这样小的包一般只需要一个文件。
我们把变量的声明、对应的常量还有方法都放到tempconv.go源文件中

View File

@ -16,7 +16,7 @@ func writeHeader(w io.Writer, contentType string) error {
因为Write方法需要传入一个byte切片而我们希望写入的值是一个字符串所以我们需要使用[]byte(...)进行转换。这个转换分配内存并且做一个拷贝但是这个拷贝在转换后几乎立马就被丢弃掉。让我们假装这是一个web服务器的核心部分并且我们的性能分析表示这个内存分配使服务器的速度变慢。这里我们可以避免掉内存分配么
这个io.Writer接口告诉我们关于w持有的具体类型的唯一东西就是可以向它写入字节切片。如果我们回顾net/http包中的内幕我们知道在这个程序中的w变量持有的动态类型也有一个允许字符串高效写入的WriteString方法这个方法会避免去分配一个时的拷贝。这可能像在黑夜中射击一样但是许多满足io.Writer接口的重要类型同时也有WriteString方法包括\*bytes.Buffer\*os.File和\*bufio.Writer。
这个io.Writer接口告诉我们关于w持有的具体类型的唯一东西就是可以向它写入字节切片。如果我们回顾net/http包中的内幕我们知道在这个程序中的w变量持有的动态类型也有一个允许字符串高效写入的WriteString方法这个方法会避免去分配一个时的拷贝。这可能像在黑夜中射击一样但是许多满足io.Writer接口的重要类型同时也有WriteString方法包括\*bytes.Buffer\*os.File和\*bufio.Writer。
我们不能对任意io.Writer类型的变量w假设它也拥有WriteString方法。但是我们可以定义一个只有这个方法的新接口并且使用类型断言来检测是否w的动态类型满足这个新接口。

View File

@ -1,6 +1,6 @@
## 7.13. 类型开关
接口被以两种不同的方式使用。在第一个方式中以io.Readerio.Writerfmt.Stringersort.Interfacehttp.Handler和error为典型一个接口的方法表达了实现这个接口的具体类型间的相性,但是隐藏了代表的细节和这些具体类型本身的操作。重点在于方法上,而不是具体的类型上。
接口被以两种不同的方式使用。在第一个方式中以io.Readerio.Writerfmt.Stringersort.Interfacehttp.Handler和error为典型一个接口的方法表达了实现这个接口的具体类型间的相性,但是隐藏了代表的细节和这些具体类型本身的操作。重点在于方法上,而不是具体的类型上。
第二个方式利用一个接口值可以持有各种具体类型值的能力并且将这个接口认为是这些类型的union联合。类型断言用来动态地区别这些类型并且对每一种情况都不一样。在这个方式中重点在于具体的类型满足这个接口而不是在于接口的方法如果它确实有一些的话并且没有任何的信息隐藏。我们将以这种方式使用的接口描述为discriminated unions可辨识联合

View File

@ -6,4 +6,4 @@
因为在Go语言中只有当两个或更多的类型实现一个接口时才使用接口它们必定会从任意特定的实现细节中抽象出来。结果就是有更少和更简单方法经常和io.Writer或 fmt.Stringer一样只有一个的更小的接口。当新的类型出现时小的接口更容易满足。对于接口设计的一个好的标准就是 ask only for what you need只考虑你需要的东西
我们完成了对methods和接口的学习过程。Go语言良好的支持面向对象风格的编程不是说你仅仅只能使用它。不是任何事物都需要被当做成一个对象独立的函数有它们自己的用处未封装的数据类型也是这样。同时观察到这两个在本书的前五章的例子中没有调用超过两打方法像input.Scan与之相反的是普遍的函数调用如fmt.Printf。
我们完成了对methods和接口的学习过程。Go语言良好的支持面向对象风格的编程不是说你仅仅只能使用它。不是任何事物都需要被当做成一个对象独立的函数有它们自己的用处未封装的数据类型也是这样。同时观察到这两个在本书的前五章的例子中没有调用超过两打方法像input.Scan与之相反的是普遍的函数调用如fmt.Printf。

View File

@ -58,7 +58,7 @@ https://golang.org/blog/
最初的错误信息是一个让人莫名的DNS查找失败即使这个域名是完全可靠的。而随后的错误信息揭示了原因这个程序一次性创建了太多网络连接超过了每一个进程的打开文件数限制既而导致了在调用net.Dial像DNS查找失败这样的问题。
这个程序实在是太他妈并行了。无穷无尽地并行化并不是什么好事情因为不管怎么说你的系统总是会有一个些限制因素比如CPU核心数会限制你的计算负载比如你的硬盘转轴和磁头数限制了你的本地磁盘IO操作频率比如你的网络带宽限制了你的下载速度上限或者是你的一个web服务的服务容量上限等等。为了解决这个问题我们可以限制并发程序所使用的资源来使之适应自己的运行环境。对于我们的例子来说最简单的方法就是限制对links.Extract在同一时间最多不会有超过n次调用这里的n是fd的limit-20一般情况下。这一个夜店里限制客人数目是一个道理,只有当有客人离开时,才会允许新的客人进入店内(译注:作者你个老流氓)。
这个程序实在是太他妈并行了。无穷无尽地并行化并不是什么好事情因为不管怎么说你的系统总是会有一个些限制因素比如CPU核心数会限制你的计算负载比如你的硬盘转轴和磁头数限制了你的本地磁盘IO操作频率比如你的网络带宽限制了你的下载速度上限或者是你的一个web服务的服务容量上限等等。为了解决这个问题我们可以限制并发程序所使用的资源来使之适应自己的运行环境。对于我们的例子来说最简单的方法就是限制对links.Extract在同一时间最多不会有超过n次调用这里的n是fd的limit-20一般情况下。这一个夜店里限制客人数目是一个道理,只有当有客人离开时,才会允许新的客人进入店内(译注:作者你个老流氓)。
我们可以用一个有容量限制的buffered channel来控制并发这类似于操作系统里的计数信号量概念。从概念上讲channel里的n个空槽代表n个可以处理内容的token(通行证)从channel里接收一个值会释放其中的一个token并且生成一个新的空槽位。这样保证了在没有接收介入时最多有n个发送操作。(这里可能我们拿channel里填充的槽来做token更直观一些不过还是这样吧~)。由于channel里的元素类型并不重要我们用一个零值的struct{}来作为其元素。

View File

@ -163,12 +163,13 @@ type Memo struct {
// Get is concurrency-safe.
func (memo *Memo) Get(key string) (value interface{}, err error) {
res, ok := memo.cache[key] if!ok{
res, ok := memo.cache[key]
if !ok {
res.value, res.err = memo.f(key)
memo.cache[key] = res
memo.mu.Lock()
res, ok := memo.cache[key]
if !ok {
if !ok {
res.value, res.err = memo.f(key)
memo.cache[key] = res
}