From d8213d003be6f1140d6bc0b33a5d4785d07bff0b Mon Sep 17 00:00:00 2001 From: Xargin Date: Tue, 19 Sep 2017 13:01:29 +0800 Subject: [PATCH 01/49] fix typo diff --git a/ch12/ch12-08.md b/ch12/ch12-08.md index 48ab516..08511e1 100644 --- a/ch12/ch12-08.md +++ b/ch12/ch12-08.md @@ -18,7 +18,7 @@ func Print(x interface{}) { } ``` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -reflect.Type和reflect.Value都提供了一个Method方法。每次t.Method(i)调用将一个reflect.Method的实例,对应一个用于描述一个方法的名称和类型的结构体。每次v.Method(i)方法调用都返回一个reflect.Value以表示对应的值(§6.4),也就是一个方法是帮到它的接收者的。使用reflect.Value.Call方法(我们之类没有演示),将可以调用一个Func类型的Value,但是这个例子中只用到了它的类型。 +reflect.Type和reflect.Value都提供了一个Method方法。每次t.Method(i)调用将一个reflect.Method的实例,对应一个用于描述一个方法的名称和类型的结构体。每次v.Method(i)方法调用都返回一个reflect.Value以表示对应的值(§6.4),也就是一个方法是帮到它的接收者的。使用reflect.Value.Call方法(我们这里没有演示),将可以调用一个Func类型的Value,但是这个例子中只用到了它的类型。 这是属于time.Duration和`*strings.Replacer`两个类型的方法: --- ch12/ch12-08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch12/ch12-08.md b/ch12/ch12-08.md index 48ab516..08511e1 100644 --- a/ch12/ch12-08.md +++ b/ch12/ch12-08.md @@ -18,7 +18,7 @@ func Print(x interface{}) { } ``` -reflect.Type和reflect.Value都提供了一个Method方法。每次t.Method(i)调用将一个reflect.Method的实例,对应一个用于描述一个方法的名称和类型的结构体。每次v.Method(i)方法调用都返回一个reflect.Value以表示对应的值(§6.4),也就是一个方法是帮到它的接收者的。使用reflect.Value.Call方法(我们之类没有演示),将可以调用一个Func类型的Value,但是这个例子中只用到了它的类型。 +reflect.Type和reflect.Value都提供了一个Method方法。每次t.Method(i)调用将一个reflect.Method的实例,对应一个用于描述一个方法的名称和类型的结构体。每次v.Method(i)方法调用都返回一个reflect.Value以表示对应的值(§6.4),也就是一个方法是帮到它的接收者的。使用reflect.Value.Call方法(我们这里没有演示),将可以调用一个Func类型的Value,但是这个例子中只用到了它的类型。 这是属于time.Duration和`*strings.Replacer`两个类型的方法: From d54b4d781b190d46d6c8de24932d665b48f9e117 Mon Sep 17 00:00:00 2001 From: Xargin Date: Thu, 26 Oct 2017 16:07:36 +0800 Subject: [PATCH 02/49] =?UTF-8?q?fix=20typo=20diff=20--git=20a/ch0/ch0-01.?= =?UTF-8?q?md=20b/ch0/ch0-01.md=20index=20ecb1c9a..ac8116f=20100644=20---?= =?UTF-8?q?=20a/ch0/ch0-01.md=20+++=20b/ch0/ch0-01.md=20@@=20-14,7=20+14,7?= =?UTF-8?q?=20@@=20Go=E8=AF=AD=E8=A8=80=E7=9A=84=E5=8F=A6=E4=B8=80?= =?UTF-8?q?=E6=94=AF=E7=A5=96=E5=85=88=EF=BC=8C=E5=B8=A6=E6=9D=A5=E4=BA=86?= =?UTF-8?q?Go=E8=AF=AD=E8=A8=80=E5=8C=BA=E5=88=AB=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E7=9A=84=E9=87=8D=E8=A6=81=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接下来,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语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。(译注:在Aelf之后还有一个叫[Limbo][Limbo]的编程语言,Go语言从其中借鉴了很多特性。 具体请参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 ) +在Plan9操作系统中,这些优秀的想法被吸收到了一个叫[Alef][Alef]的编程语言中。Alef试图将Newsqueak语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。(译注:在Alef之后还有一个叫[Limbo][Limbo]的编程语言,Go语言从其中借鉴了很多特性。 具体请参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 ) Go语言的其他的一些特性零散地来自于其他一些编程语言;比如iota语法是从[APL][APL]语言借鉴,词法作用域与嵌套函数来自于[Scheme][Scheme]语言(和其他很多语言)。当然,我们也可以从Go中发现很多创新的设计。比如Go语言的切片为动态数组提供了有效的随机存取的性能,这可能会让人联想到链表的底层的共享机制。还有Go语言新发明的defer语句。 --- ch0/ch0-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch0/ch0-01.md b/ch0/ch0-01.md index ecb1c9a..ac8116f 100644 --- a/ch0/ch0-01.md +++ b/ch0/ch0-01.md @@ -14,7 +14,7 @@ Go语言的另一支祖先,带来了Go语言区别其他语言的重要特性 接下来,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语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。(译注:在Aelf之后还有一个叫[Limbo][Limbo]的编程语言,Go语言从其中借鉴了很多特性。 具体请参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 ) +在Plan9操作系统中,这些优秀的想法被吸收到了一个叫[Alef][Alef]的编程语言中。Alef试图将Newsqueak语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。(译注:在Alef之后还有一个叫[Limbo][Limbo]的编程语言,Go语言从其中借鉴了很多特性。 具体请参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 ) Go语言的其他的一些特性零散地来自于其他一些编程语言;比如iota语法是从[APL][APL]语言借鉴,词法作用域与嵌套函数来自于[Scheme][Scheme]语言(和其他很多语言)。当然,我们也可以从Go中发现很多创新的设计。比如Go语言的切片为动态数组提供了有效的随机存取的性能,这可能会让人联想到链表的底层的共享机制。还有Go语言新发明的defer语句。 From 1d4ee4c460da0dd29a3b8b986e53b178df1f3f91 Mon Sep 17 00:00:00 2001 From: wukoo Date: Sat, 18 Nov 2017 13:10:03 +0800 Subject: [PATCH 03/49] Update ch3-05-3.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit utf8 最长4个字节,最多往前找3个字节,就可以找到当字符的起始字节。英文版里也是说3个字节 --- ch3/ch3-05-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch3/ch3-05-3.md b/ch3/ch3-05-3.md index c91dbb1..683f4d9 100644 --- a/ch3/ch3-05-3.md +++ b/ch3/ch3-05-3.md @@ -9,7 +9,7 @@ UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused) ``` -变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多2个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。 +变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多3个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。 Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数字,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。 From d28d4fb53ab11d1f794689c62a0f4e7e32d20e85 Mon Sep 17 00:00:00 2001 From: JimLee1996 <59061463@qq.com> Date: Tue, 21 Nov 2017 14:32:14 +0800 Subject: [PATCH 04/49] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=B0=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function and arguemnt expressions are evaluated when the staement is executed, butt the actual call is deferred until the function that contains... 原文翻译很有歧义望修改! --- ch5/ch5-08.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ch5/ch5-08.md b/ch5/ch5-08.md index 9c25d1f..1ee8015 100644 --- a/ch5/ch5-08.md +++ b/ch5/ch5-08.md @@ -46,7 +46,7 @@ title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/ resp.Body.close调用了多次,这是为了确保title在所有执行路径下(即使函数运行失败)都关闭了网络连接。随着函数变得复杂,需要处理的错误也变多,维护清理逻辑变得越来越困难。而Go语言独有的defer机制可以让事情变得简单。 -你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。 +你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当执行到该条语句时,函数和参数表达式得到计算,但直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。 defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。在下面的代码中,一条defer语句替代了之前的所有resp.Body.Close @@ -221,4 +221,4 @@ func fetch(url string) (filename string, n int64, err error) { 对resp.Body.Close延迟调用我们已经见过了,在此不做解释。上例中,通过os.Create打开文件进行写入,在关闭文件时,我们没有对f.close采用defer机制,因为这会产生一些微妙的错误。许多文件系统,尤其是NFS,写入文件时发生的错误会被延迟到文件关闭时反馈。如果没有检查文件关闭时的反馈信息,可能会导致数据丢失,而我们还误以为写入操作成功。如果io.Copy和f.close都失败了,我们倾向于将io.Copy的错误信息反馈给调用者,因为它先于f.close发生,更有可能接近问题的本质。 -**练习5.18:**不修改fetch的行为,重写fetch函数,要求使用defer机制关闭文件。 \ No newline at end of file +**练习5.18:**不修改fetch的行为,重写fetch函数,要求使用defer机制关闭文件。 From 3a284d83e37d05c0710f36247918fbbd0ce09402 Mon Sep 17 00:00:00 2001 From: A1014280203 <1014280203@qq.com> Date: Tue, 28 Nov 2017 12:01:30 +0800 Subject: [PATCH 05/49] =?UTF-8?q?=E8=A1=A5=E5=85=85sync.Once=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充了go1.9中,sync.Once的新实现方式带来的不同。 --- ch9/ch9-05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-05.md b/ch9/ch9-05.md index b335e9e..6a6a5cc 100644 --- a/ch9/ch9-05.md +++ b/ch9/ch9-05.md @@ -101,6 +101,6 @@ func Icon(name string) image.Image { } ``` -每一次对Do(loadIcons)的调用都会锁定mutex,并会检查boolean变量。在第一次调用时,boolean变量的值是false,Do会调用loadIcons并会将boolean变量设置为true。随后的调用什么都不会做,但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话,我们能够避免在变量被构建完成之前和其它goroutine共享该变量。 +每一次对Do(loadIcons)的调用都会锁定mutex,并会检查boolean变量(译注:Go1.9中会先判断boolean变量是否为1(true),只有不为1才锁定mutex,不再需要每次都锁定mutex)。在第一次调用时,boolean变量的值是false,Do会调用loadIcons并会将boolean变量设置为true。随后的调用什么都不会做,但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话,我们能够避免在变量被构建完成之前和其它goroutine共享该变量。 **练习 9.2:** 重写2.6.2节中的PopCount的例子,使用sync.Once,只在第一次需要用到的时候进行初始化。(虽然实际上,对PopCount这样很小且高度优化的函数进行同步可能代价没法接受) From 740cc92d15e17cce33809d59b63bb8783d28b9e4 Mon Sep 17 00:00:00 2001 From: MiFalyzz Date: Thu, 30 Nov 2017 11:25:28 +0800 Subject: [PATCH 06/49] Update ch5-07.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit interface{} 而不是 interfac{} --- ch5/ch5-07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-07.md b/ch5/ch5-07.md index 6a68da2..b7d3514 100644 --- a/ch5/ch5-07.md +++ b/ch5/ch5-07.md @@ -51,7 +51,7 @@ linenum, name := 12, "count" errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count" ``` -interfac{}表示函数的最后一个参数可以接收任意类型,我们会在第7章详细介绍。 +interface{}表示函数的最后一个参数可以接收任意类型,我们会在第7章详细介绍。 **练习5.15:** 编写类似sum的可变参数函数max和min。考虑不传参时,max和min该如何处理,再编写至少接收1个参数的版本。 From d280c6c7aa849b11214b9d1807e4661eb9a1e500 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Fri, 1 Dec 2017 00:08:06 +0800 Subject: [PATCH 07/49] Update ch7-13.md --- ch7/ch7-13.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ch7/ch7-13.md b/ch7/ch7-13.md index 77490e5..aab7328 100644 --- a/ch7/ch7-13.md +++ b/ch7/ch7-13.md @@ -48,11 +48,11 @@ switch语句可以简化if-else链,如果这个if-else链对一连串值做相 ```go switch x.(type) { - case nil: // ... - case int, uint: // ... - case bool: // ... - case string: // ... - default: // ... +case nil: // ... +case int, uint: // ... +case bool: // ... +case string: // ... +default: // ... } ``` From ce363dd19ee713f2a3eca90495b545f5164b930c Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Sat, 2 Dec 2017 16:08:06 +0800 Subject: [PATCH 08/49] Update ch8-04-1.md --- ch8/ch8-04-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-04-1.md b/ch8/ch8-04-1.md index 740fa1f..ce961bd 100644 --- a/ch8/ch8-04-1.md +++ b/ch8/ch8-04-1.md @@ -29,7 +29,7 @@ func main() { } ``` -当用户关闭了标准输入,主goroutine中的mustCopy函数调用将返回,然后调用conn.Close()关闭读和写方向的网络连接。关闭网络连接中的写方向的连接将导致server程序收到一个文件(end-of-file)结束的信号。关闭网络连接中读方向的连接将导致后台goroutine的io.Copy函数调用返回一个“read from closed connection”(“从关闭的连接读”)类似的错误,因此我们临时移除了错误日志语句;在练习8.3将会提供一个更好的解决方案。(需要注意的是go语句调用了一个函数字面量,这Go语言中启动goroutine常用的形式。) +当用户关闭了标准输入,主goroutine中的mustCopy函数调用将返回,然后调用conn.Close()关闭读和写方向的网络连接。关闭网络连接中的写方向的连接将导致server程序收到一个文件(end-of-file)结束的信号。关闭网络连接中读方向的连接将导致后台goroutine的io.Copy函数调用返回一个“read from closed connection”(“从关闭的连接读”)类似的错误,因此我们临时移除了错误日志语句;在练习8.3将会提供一个更好的解决方案。(需要注意的是go语句调用了一个函数字面量,这是Go语言中启动goroutine常用的形式。) 在后台goroutine返回之前,它先打印一个日志信息,然后向done对应的channel发送一个值。主goroutine在退出前先等待从done对应的channel接收一个值。因此,总是可以在程序退出前正确输出“done”消息。 From 74e4184c86021c1e982742989e356c0365d1afa8 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Sat, 2 Dec 2017 17:19:21 +0800 Subject: [PATCH 09/49] Update ch8-04-4.md --- ch8/ch8-04-4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-04-4.md b/ch8/ch8-04-4.md index 0c877de..d7bfe9a 100644 --- a/ch8/ch8-04-4.md +++ b/ch8/ch8-04-4.md @@ -51,7 +51,7 @@ fmt.Println(<-ch) // "B" fmt.Println(<-ch) // "C" ``` -在这个例子中,发送和接收操作都发生在同一个goroutine中,但是在真实的程序中它们一般由不同的goroutine执行。Go语言新手有时候会将一个带缓存的channel当作同一个goroutine中的队列使用,虽然语法看似简单,但实际上这是一个错误。Channel和goroutine的调度器机制是紧密相连的,一个发送操作——或许是整个程序——可能会永远阻塞。如果你只是需要一个简单的队列,使用slice就可以了。 +在这个例子中,发送和接收操作都发生在同一个goroutine中,但是在真实的程序中它们一般由不同的goroutine执行。Go语言新手有时候会将一个带缓存的channel当作同一个goroutine中的队列使用,虽然语法看似简单,但实际上这是一个错误。Channel和goroutine的调度器机制是紧密相连的,如果没有其他goroutine从channel接收,发送者——或许是整个程序——将会面临永远阻塞的风险。如果你只是需要一个简单的队列,使用slice就可以了。 下面的例子展示了一个使用了带缓存channel的应用。它并发地向三个镜像站点发出请求,三个镜像站点分散在不同的地理位置。它们分别将收到的响应发送到带缓存channel,最后接收者只接收第一个收到的响应,也就是最快的那个响应。因此mirroredQuery函数可能在另外两个响应慢的镜像站点响应之前就返回了结果。(顺便说一下,多个goroutines并发地向同一个channel发送数据,或从同一个channel接收数据都是常见的用法。) From 620a1c7fd1784ff870b2076c8dc36f5964f11fce Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Mon, 4 Dec 2017 22:56:28 +0800 Subject: [PATCH 10/49] Update SUMMARY.md --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index 798a1f9..f405fbb 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -79,7 +79,7 @@ * [并发的循环](ch8/ch8-05.md) * [示例: 并发的Web爬虫](ch8/ch8-06.md) * [基于select的多路复用](ch8/ch8-07.md) - * [示例: 并发的字典遍历](ch8/ch8-08.md) +  * [示例: 并发的目录遍历](ch8/ch8-08.md) * [并发的退出](ch8/ch8-09.md) * [示例: 聊天服务](ch8/ch8-10.md) * [基于共享变量的并发](ch9/ch9.md) From e1358aac8662be4d430376dac0df692dffc111ca Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 00:20:21 +0800 Subject: [PATCH 11/49] Update ch8-08.md --- ch8/ch8-08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-08.md b/ch8/ch8-08.md index 0ca44bf..e79330d 100644 --- a/ch8/ch8-08.md +++ b/ch8/ch8-08.md @@ -131,7 +131,7 @@ $ ./du2 -v $HOME /usr /bin /etc 213201 files 62.7 GB ``` -然而这个程序还是会花上很长时间才会结束。无法对walkDir做并行化处理没什么别的原因,无非是因为磁盘系统并行限制。下面这个第三个版本的du,会对每一个walkDir的调用创建一个新的goroutine。它使用sync.WaitGroup (§8.5)来对仍旧活跃的walkDir调用进行计数,另一个goroutine会在计数器减为零的时候将fileSizes这个channel关闭。 +然而这个程序还是会花上很长时间才会结束。完全可以并发调用walkDir,从而发挥磁盘系统的并行性能。下面这个第三个版本的du,会对每一个walkDir的调用创建一个新的goroutine。它使用sync.WaitGroup (§8.5)来对仍旧活跃的walkDir调用进行计数,另一个goroutine会在计数器减为零的时候将fileSizes这个channel关闭。 gopl.io/ch8/du3 ```go From 7b3d3a6aad9b4fd438fc78a28af74f2f4601dd05 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 20:59:49 +0800 Subject: [PATCH 12/49] Update ch8-10.md --- ch8/ch8-10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-10.md b/ch8/ch8-10.md index 553a778..4e09c2b 100644 --- a/ch8/ch8-10.md +++ b/ch8/ch8-10.md @@ -114,7 +114,7 @@ You are 127.0.0.1:64216 127.0.0.1:64216 has arrived 当与n个客户端保持聊天session时,这个程序会有2n+2个并发的goroutine,然而这个程序却并不需要显式的锁(§9.2)。clients这个map被限制在了一个独立的goroutine中,broadcaster,所以它不能被并发地访问。多个goroutine共享的变量只有这些channel和net.Conn的实例,两个东西都是并发安全的。我们会在下一章中更多地讲解约束,并发安全以及goroutine中共享变量的含义。 -**练习 8.12:** 使broadcaster能够将arrival事件通知当前所有的客户端。为了达成这个目的,你需要有一个客户端的集合,并且在entering和leaving的channel中记录客户端的名字。 +**练习 8.12:** 使broadcaster能够将arrival事件通知当前所有的客户端。这需要你在clients集合中,以及entering和leaving的channel中记录客户端的名字。 **练习 8.13:** 使聊天服务器能够断开空闲的客户端连接,比如最近五分钟之后没有发送任何消息的那些客户端。提示:可以在其它goroutine中调用conn.Close()来解除Read调用,就像input.Scanner()所做的那样。 From dfefed699c878bedc4814243fb377b4357cd8091 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 22:47:19 +0800 Subject: [PATCH 13/49] Update ch9-01.md --- ch9/ch9-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 557b732..64172fc 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -2,7 +2,7 @@ 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法判断的。当我们没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话,就说明x和y这两个事件是并发的。 -考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的方法和操作都是并发安全的话,那么类型便是并发安全的。 +考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的方法和操作都是并发安全的话,那么该类型便是并发安全的。 在一个程序中有非并发安全的类型的情况下,我们依然可以使这个程序并发安全。确实,并发安全的类型是例外,而不是规则,所以只有当文档中明确地说明了其是并发安全的情况下,你才可以并发地去访问它。我们会避免并发访问大多数的类型,无论是将变量局限在单一的一个goroutine内,还是用互斥条件维持更高级别的不变性,都是为了这个目的。我们会在本章中说明这些术语。 From 75a37c10668fd49cc12d1f447a1984f16596fa30 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 23:20:21 +0800 Subject: [PATCH 14/49] Update ch9-01.md --- ch9/ch9-01.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 557b732..dd963ee 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -40,11 +40,11 @@ go bank.Deposit(100) // B Alice存了$200,然后检查她的余额,同时Bob存了$100。因为A1和A2是和B并发执行的,我们没法预测他们发生的先后顺序。直观地来看的话,我们会认为其执行顺序只有三种可能性:“Alice先”,“Bob先”以及“Alice/Bob/Alice”交错执行。下面的表格会展示经过每一步骤后balance变量的值。引号里的字符串表示余额单。 ``` -Alice first Bob first Alice/Bob/Alice -0 0 0 -A1 200 B 100 A1 200 -A2 "=200" A1 300 B 300 -B 300 A2 "=300" A2 "=300" +Alice first Bob first Alice/Bob/Alice + 0 0 0 + A1 200 B 100 A1 200 + A2 "= 200" A1 300 B 300 + B 300 A2 "= 300" A2 "= 300" ``` 所有情况下最终的余额都是$300。唯一的变数是Alice的余额单是否包含了Bob交易,不过无论怎么着客户都不会在意。 From 4283571cf34b566ed027c78e2066b528f2da9e51 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 00:20:55 +0800 Subject: [PATCH 15/49] Update ch9-01.md --- ch9/ch9-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 557b732..3bfd8f5 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -110,7 +110,7 @@ var icons = map[string]image.Image{ func Icon(name string) image.Image { return icons[name] } ``` -上面的例子里icons变量在包初始化阶段就已经被赋值了,包的初始化是在程序main函数开始执行之前就完成了的。只要初始化完成了,icons就再也不会修改的或者不变量是本来就并发安全的,这种变量不需要进行同步。不过显然我们没法用这种方法,因为update操作是必要的操作,尤其对于银行账户来说。 +上面的例子里icons变量在包初始化阶段就已经被赋值了,包的初始化是在程序main函数开始执行之前就完成了的。只要初始化完成了,icons就再也不会被修改。数据结构如果从不被修改或是不变量则是并发安全的,无需进行同步。不过显然,如果update操作是必要的,我们就没法用这种方法,比如说银行账户。 第二种避免数据竞争的方法是,避免从多个goroutine访问变量。这也是前一章中大多数程序所采用的方法。例如前面的并发web爬虫(§8.6)的main goroutine是唯一一个能够访问seen map的goroutine,而聊天服务器(§8.10)中的broadcaster goroutine是唯一一个能够访问clients map的goroutine。这些变量都被限定在了一个单独的goroutine中。 From c1b75dda196226939251832135b7ca1c0f3fb509 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 14:14:13 +0800 Subject: [PATCH 16/49] Update ch9-02.md --- ch9/ch9-02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-02.md b/ch9/ch9-02.md index e9ad9b6..008a625 100644 --- a/ch9/ch9-02.md +++ b/ch9/ch9-02.md @@ -102,7 +102,7 @@ func Withdraw(amount int) bool { 上面这个例子中,Deposit会调用mu.Lock()第二次去获取互斥锁,但因为mutex已经锁上了,而无法被重入(译注:go里没有重入锁,关于重入锁的概念,请参考java)--也就是说没法对一个已经锁上的mutex来再次上锁--这会导致程序死锁,没法继续执行下去,Withdraw会永远阻塞下去。 -关于Go的互斥量不能重入这一点我们有很充分的理由。互斥量的目的是为了确保共享变量在程序执行时的关键点上能够保证不变性。不变性的其中之一是“没有goroutine访问共享变量”。但实际上这里对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine获得了一个互斥锁时,它会断定这种不变性能够被保持。在其获取并保持锁期间,可能会去更新共享变量,这样不变性只是短暂地被破坏。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一个可以重入的mutex也可以保证没有其它的goroutine在访问共享变量,但这种方式没法保证这些变量额外的不变性。(译注:这段翻译有点晕) +关于Go的mutex不能重入这一点我们有很充分的理由。mutex的目的是确保共享变量在程序执行时的关键点上能够保证不变性。不变性的其中之一是“没有goroutine访问共享变量”,但实际上这里对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine获得了一个互斥锁时,它会断定这种不变性能够被保持。在其获取并保持锁期间,可能会去更新共享变量,这样不变性只是短暂地被破坏。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一个可以重入的mutex也可以保证没有其它的goroutine在访问共享变量,但这种方式没法保证这些变量额外的不变性。(译注:这段翻译有点晕) 一个通用的解决方案是将一个函数分离为多个函数,比如我们把Deposit分离成两个:一个不导出的函数deposit,这个函数假设锁总是会被保持并去做实际的操作,另一个是导出的函数Deposit,这个函数会调用deposit,但在调用前会先去获取锁。同理我们可以将Withdraw也表示成这种形式: From e287bc9f23195e8b847f9e3daf573a72072786e5 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 18:27:50 +0800 Subject: [PATCH 17/49] Update SUMMARY.md --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index f405fbb..7fe80e7 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -87,7 +87,7 @@ * [sync.Mutex互斥锁](ch9/ch9-02.md) * [sync.RWMutex读写锁](ch9/ch9-03.md) * [内存同步](ch9/ch9-04.md) - * [sync.Once初始化](ch9/ch9-05.md) +  * [sync.Once惰性初始化](ch9/ch9-05.md) * [竞争条件检测](ch9/ch9-06.md) * [示例: 并发的非阻塞缓存](ch9/ch9-07.md) * [Goroutines和线程](ch9/ch9-08.md) From 7c0b29af8ee80ad7a5ae5956536b8b6e1867053a Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 18:29:24 +0800 Subject: [PATCH 18/49] Update ch9-05.md --- ch9/ch9-05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-05.md b/ch9/ch9-05.md index 6a6a5cc..75246a0 100644 --- a/ch9/ch9-05.md +++ b/ch9/ch9-05.md @@ -1,4 +1,4 @@ -## 9.5. sync.Once初始化 +## 9.5. sync.Once惰性初始化 如果初始化成本比较大的话,那么将初始化延迟到需要的时候再去做就是一个比较好的选择。如果在程序启动的时候就去做这类初始化的话,会增加程序的启动时间,并且因为执行的时候可能也并不需要这些变量,所以实际上有一些浪费。让我们来看在本章早一些时候的icons变量: From 84d5d2bf5e673acd204d7bb64458e5e598b80c89 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 22:37:52 +0800 Subject: [PATCH 19/49] Update ch10-06.md --- ch10/ch10-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch10/ch10-06.md b/ch10/ch10-06.md index 3c4ceee..5faeba4 100644 --- a/ch10/ch10-06.md +++ b/ch10/ch10-06.md @@ -4,7 +4,7 @@ 当创建一个包,一般要用短小的包名,但也不能太短导致难以理解。标准库中最常用的包有bufio、bytes、flag、fmt、http、io、json、os、sort、sync和time等包。 -它们的名字都简洁明了。例如,不要将一个类似imageutil或ioutilis的通用包命名为util,虽然它看起来很短小。要尽量避免包名使用可能被经常用于局部变量的名字,这样可能导致用户重命名导入包,例如前面看到的path包。 +尽可能让命名有描述性且无歧义。例如,类似imageutil或ioutilis的工具包命名已经足够简洁了,就无须再命名为util了。要尽量避免包名使用可能被经常用于局部变量的名字,这样可能导致用户重命名导入包,例如前面看到的path包。 包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。 From 29140c015fad1ce1b9f477429d6e176ac9ed2300 Mon Sep 17 00:00:00 2001 From: Xargin Date: Mon, 11 Dec 2017 19:12:31 +0800 Subject: [PATCH 20/49] fix order --- ch1/ch1-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch1/ch1-06.md b/ch1/ch1-06.md index 3615887..9ab78a4 100644 --- a/ch1/ch1-06.md +++ b/ch1/ch1-06.md @@ -63,7 +63,7 @@ goroutine是一种函数的并发执行方式,而channel是用来在goroutine main函数中用make函数创建了一个传递string类型参数的channel,对每一个命令行参数,我们都用go这个关键字来创建一个goroutine,并且让函数在这个goroutine异步执行http.Get方法。这个程序里的io.Copy会把响应的Body内容拷贝到ioutil.Discard输出流中(译注:可以把这个变量看作一个垃圾桶,可以向里面写一些不需要的数据),因为我们需要这个方法返回的字节数,但是又不想要其内容。每当请求返回内容时,fetch函数都会往ch这个channel里写入一个字符串,由main函数里的第二个for循环来处理并打印channel里的这个字符串。 -当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine往这个channel里写入、或者接收值,这样两个goroutine才会继续执行channel操作之后的逻辑。在这个例子中,每一个fetch函数在执行时都会往channel里发送一个值(ch <- expression),主函数负责接收这些值(<-ch)。这个程序中我们用main函数来接收所有fetch函数传回的字符串,可以避免在goroutine异步执行还没有完成时main函数提前退出。 +当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine往这个channel里接收或者写入值,这样两个goroutine才会继续执行channel操作之后的逻辑。在这个例子中,每一个fetch函数在执行时都会往channel里发送一个值(ch <- expression),主函数负责接收这些值(<-ch)。这个程序中我们用main函数来接收所有fetch函数传回的字符串,可以避免在goroutine异步执行还没有完成时main函数提前退出。 **练习 1.10:** 找一个数据量比较大的网站,用本小节中的程序调研网站的缓存策略,对每个URL执行两遍请求,查看两次时间是否有较大的差别,并且每次获取到的响应内容是否一致,修改本节中的程序,将响应结果输出,以便于进行对比。 From 214cac409538320d27e449c507ea472d2cf1104e Mon Sep 17 00:00:00 2001 From: Xargin Date: Mon, 11 Dec 2017 19:14:11 +0800 Subject: [PATCH 21/49] uupdate --- ch1/ch1-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch1/ch1-06.md b/ch1/ch1-06.md index 9ab78a4..1e23c4b 100644 --- a/ch1/ch1-06.md +++ b/ch1/ch1-06.md @@ -63,7 +63,7 @@ goroutine是一种函数的并发执行方式,而channel是用来在goroutine main函数中用make函数创建了一个传递string类型参数的channel,对每一个命令行参数,我们都用go这个关键字来创建一个goroutine,并且让函数在这个goroutine异步执行http.Get方法。这个程序里的io.Copy会把响应的Body内容拷贝到ioutil.Discard输出流中(译注:可以把这个变量看作一个垃圾桶,可以向里面写一些不需要的数据),因为我们需要这个方法返回的字节数,但是又不想要其内容。每当请求返回内容时,fetch函数都会往ch这个channel里写入一个字符串,由main函数里的第二个for循环来处理并打印channel里的这个字符串。 -当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine往这个channel里接收或者写入值,这样两个goroutine才会继续执行channel操作之后的逻辑。在这个例子中,每一个fetch函数在执行时都会往channel里发送一个值(ch <- expression),主函数负责接收这些值(<-ch)。这个程序中我们用main函数来接收所有fetch函数传回的字符串,可以避免在goroutine异步执行还没有完成时main函数提前退出。 +当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine从这个channel里接收或者写入值,这样两个goroutine才会继续执行channel操作之后的逻辑。在这个例子中,每一个fetch函数在执行时都会往channel里发送一个值(ch <- expression),主函数负责接收这些值(<-ch)。这个程序中我们用main函数来接收所有fetch函数传回的字符串,可以避免在goroutine异步执行还没有完成时main函数提前退出。 **练习 1.10:** 找一个数据量比较大的网站,用本小节中的程序调研网站的缓存策略,对每个URL执行两遍请求,查看两次时间是否有较大的差别,并且每次获取到的响应内容是否一致,修改本节中的程序,将响应结果输出,以便于进行对比。 From e0bb46ea95389af720f20c2d4b1a6c7fa11e03b3 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Fri, 15 Dec 2017 14:19:27 +0800 Subject: [PATCH 22/49] Update ch12-07.md --- ch12/ch12-07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch12/ch12-07.md b/ch12/ch12-07.md index c0fe40b..157e538 100644 --- a/ch12/ch12-07.md +++ b/ch12/ch12-07.md @@ -1,6 +1,6 @@ ## 12.7. 获取结构体字段标识 -在4.5节我们使用构体成员标签用于设置对应JSON对应的名字。其中json成员标签让我们可以选择成员的名字和抑制零值成员的输出。在本节,我们将看到如果通过反射机制类获取成员标签。 +在4.5节我们使用构体成员标签用于设置对应JSON对应的名字。其中json成员标签让我们可以选择成员的名字和抑制零值成员的输出。在本节,我们将看到如何通过反射机制类获取成员标签。 对于一个web服务,大部分HTTP处理函数要做的第一件事情就是展开请求中的参数到本地变量中。我们定义了一个工具函数,叫params.Unpack,通过使用结构体成员标签机制来让HTTP处理函数解析请求参数更方便。 From b2792e2ea827e4e5a2c4d93d2e162987642a0309 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Fri, 15 Dec 2017 14:22:51 +0800 Subject: [PATCH 23/49] Update ch12-07.md --- ch12/ch12-07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch12/ch12-07.md b/ch12/ch12-07.md index c0fe40b..e85dbcf 100644 --- a/ch12/ch12-07.md +++ b/ch12/ch12-07.md @@ -1,4 +1,4 @@ -## 12.7. 获取结构体字段标识 +## 12.7. 获取结构体字段标签 在4.5节我们使用构体成员标签用于设置对应JSON对应的名字。其中json成员标签让我们可以选择成员的名字和抑制零值成员的输出。在本节,我们将看到如果通过反射机制类获取成员标签。 From fc760171e15c47daf1449d654472c8b89a019e89 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Fri, 15 Dec 2017 14:24:25 +0800 Subject: [PATCH 24/49] Update SUMMARY.md --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index 7fe80e7..3ef9266 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -113,7 +113,7 @@ * [示例: 编码S表达式](ch12/ch12-04.md) * [通过reflect.Value修改值](ch12/ch12-05.md) * [示例: 解码S表达式](ch12/ch12-06.md) - * [获取结构体字段标识](ch12/ch12-07.md) +  * [获取结构体字段标签](ch12/ch12-07.md) * [显示一个类型的方法集](ch12/ch12-08.md) * [几点忠告](ch12/ch12-09.md) * [底层编程](ch13/ch13.md) From 81a0ef33172cb952fabbb76b03f084364bba4423 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Fri, 15 Dec 2017 14:46:04 +0800 Subject: [PATCH 25/49] Update ch12-09.md --- ch12/ch12-09.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch12/ch12-09.md b/ch12/ch12-09.md index 7dceca9..1c6ae4b 100644 --- a/ch12/ch12-09.md +++ b/ch12/ch12-09.md @@ -16,5 +16,5 @@ fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)" 避免使用反射的第二个原因是,即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地为每个导出的类型和其它接受interface{}或reflect.Value类型参数的函数维护说明文档。 -第三个原因,基于反射的代码通常比正常的代码运行速度慢一到两个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以使用反射可能会使程序更加清晰。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。 +第三个原因,基于反射的代码通常比正常的代码运行速度慢一到两个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以当反射能使程序更加清晰的时候可以考虑使用。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。 From 3f8f4a05313207adb70658689cca917d63fc6016 Mon Sep 17 00:00:00 2001 From: wukoo Date: Mon, 1 Jan 2018 16:43:56 +0800 Subject: [PATCH 26/49] Update ch5-04-1.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 翻译错误。 --- ch5/ch5-04-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-04-1.md b/ch5/ch5-04-1.md index 1a5a625..34c00fc 100644 --- a/ch5/ch5-04-1.md +++ b/ch5/ch5-04-1.md @@ -11,7 +11,7 @@ if err != nil{ } ``` -当对html.Parse的调用失败时,findLinks不会直接返回html.Parse的错误,因为缺少两条重要信息:1、错误发生在解析器;2、url已经被解析。这些信息有助于错误的处理,findLinks会构造新的错误信息返回给调用者: +当对html.Parse的调用失败时,findLinks不会直接返回html.Parse的错误,因为缺少两条重要信息:1、发生错误时的解析器(html parser);2、发生错误的url。因此,findLinks构造了一个新的错误信息,既包含了这两项,也包括了底层的解析出错的信息。 ```Go doc, err := html.Parse(resp.Body) From 72b9bfe1732ef2f85465060d0a5a0f7fa0e7878b Mon Sep 17 00:00:00 2001 From: Xargin Date: Wed, 17 Jan 2018 17:45:27 +0800 Subject: [PATCH 27/49] fix --- ch9/ch9-08-4.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ch9/ch9-08-4.md b/ch9/ch9-08-4.md index 97123c6..e29b9ec 100644 --- a/ch9/ch9-08-4.md +++ b/ch9/ch9-08-4.md @@ -2,9 +2,9 @@ 在大多数支持多线程的操作系统和程序语言中,当前的线程都有一个独特的身份(id),并且这个身份信息可以以一个普通值的形式被很容易地获取到,典型的可以是一个integer或者指针值。这种情况下我们做一个抽象化的thread-local storage(线程本地存储,多线程编程中不希望其它线程访问的内容)就很容易,只需要以线程的id作为key的一个map就可以解决问题,每一个线程以其id就能从中获取到值,且和其它线程互不冲突。 -goroutine没有可以被程序员获取到的身份(id)的概念。这一点是设计上故意而为之,由于thread-local storage总是会被滥用。比如说,一个web server是用一种支持tls的语言实现的,而非常普遍的是很多函数会去寻找HTTP请求的信息,这代表它们就是去其存储层(这个存储层有可能是tls)查找的。这就像是那些过分依赖全局变量的程序一样,会导致一种非健康的“距离外行为”,在这种行为下,一个函数的行为可能不是由其自己内部的变量所决定,而是由其所运行在的线程所决定。因此,如果线程本身的身份会改变——比如一些worker线程之类的——那么函数的行为就会变得神秘莫测。 +goroutine没有可以被程序员获取到的身份(id)的概念。这一点是设计上故意而为之,由于thread-local storage总是会被滥用。比如说,一个web server是用一种支持tls的语言实现的,而非常普遍的是很多函数会去寻找HTTP请求的信息,这代表它们就是去其存储层(这个存储层有可能是tls)查找的。这就像是那些过分依赖全局变量的程序一样,会导致一种非健康的“距离外行为”,在这种行为下,一个函数的行为可能并不仅由自己的参数所决定,而是由其所运行在的线程所决定。因此,如果线程本身的身份会改变——比如一些worker线程之类的——那么函数的行为就会变得神秘莫测。 -Go鼓励更为简单的模式,这种模式下参数对函数的影响都是显式的。这样不仅使程序变得更易读,而且会让我们自由地向一些给定的函数分配子任务时不用担心其身份信息影响行为。 +Go鼓励更为简单的模式,这种模式下参数(译注:外部显式参数和内部显式参数。tls 中的内容算是"外部"隐式参数)对函数的影响都是显式的。这样不仅使程序变得更易读,而且会让我们自由地向一些给定的函数分配子任务时不用担心其身份信息影响行为。 你现在应该已经明白了写一个Go程序所需要的所有语言特性信息。在后面两章节中,我们会回顾一些之前的实例和工具,支持我们写出更大规模的程序:如何将一个工程组织成一系列的包,如何获取,构建,测试,性能测试,剖析,写文档,并且将这些包分享出去。 From 701c69dabc1145555cb0d6dc95cf722d3241d07b Mon Sep 17 00:00:00 2001 From: iamybj Date: Thu, 22 Feb 2018 12:55:15 +0800 Subject: [PATCH 28/49] Update preface.md --- preface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preface.md b/preface.md index 37b07d4..7ef0c30 100644 --- a/preface.md +++ b/preface.md @@ -11,12 +11,12 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本 在线预览: +- http://gopl-zh.simple-is-best.tk/ - http://gopl-zh.b0.upaiyun.com/ - http://docs.ruanjiadeng.com/gopl-zh/ - http://shifei.me/gopl-zh/ - http://2goo.info/media/html/gopl-zh-gh-pages/ - http://docs.plhwin.com/gopl-zh/ -- http://gopl-zh.simple-is-best.tk/ - https://docs.hacknode.org/gopl-zh/ {% include "./version.md" %} From 9689a660540f5d7b1ea3e57397765e705c367bbd Mon Sep 17 00:00:00 2001 From: iamybj Date: Thu, 22 Feb 2018 12:56:00 +0800 Subject: [PATCH 29/49] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bcb0e39..b5f034f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本 在线预览: +- http://gopl-zh.simple-is-best.tk/ - http://gopl-zh.b0.upaiyun.com/ - http://docs.ruanjiadeng.com/gopl-zh/ - http://shifei.me/gopl-zh/ From 3587f6a9317a3673f020aa8be4dee614ae7e3ef2 Mon Sep 17 00:00:00 2001 From: iamybj Date: Thu, 22 Feb 2018 12:57:20 +0800 Subject: [PATCH 30/49] Update preface.md --- preface.md | 1 + 1 file changed, 1 insertion(+) diff --git a/preface.md b/preface.md index 7ef0c30..8671dea 100644 --- a/preface.md +++ b/preface.md @@ -18,6 +18,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本 - http://2goo.info/media/html/gopl-zh-gh-pages/ - http://docs.plhwin.com/gopl-zh/ - https://docs.hacknode.org/gopl-zh/ +- http://books.studygolang.com/gopl-zh/ {% include "./version.md" %} From 9eecd0f9913b635bdbf0dcb6328258dc06c5088d Mon Sep 17 00:00:00 2001 From: Xargin Date: Fri, 23 Feb 2018 15:49:02 +0800 Subject: [PATCH 31/49] fix typo diff --git a/ch2/ch2-07.md b/ch2/ch2-07.md index f5e75f4..b74cfdd 100644 --- a/ch2/ch2-07.md +++ b/ch2/ch2-07.md @@ -12,7 +12,7 @@ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量,和包级的变量同名。或者是像2.3.3节的例子那样,你可以将一个函数参数的名字声明为new,虽然内置的new是全局作用域的。但是物极必反,如果滥用不同词法域可重名的特性的话,可能导致程序很难阅读。 -当编译器遇到一个名字引用时,如果它看起来像一个声明,它首先从最内层的词法域向全局的作用域查找。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问: +当编译器遇到一个名字引用时,它会对其定义进行查找,查找过程从最内层的词法域向全局的作用域进行。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问: ```Go func f() {} --- ch2/ch2-07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch2/ch2-07.md b/ch2/ch2-07.md index f5e75f4..b74cfdd 100644 --- a/ch2/ch2-07.md +++ b/ch2/ch2-07.md @@ -12,7 +12,7 @@ 一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量,和包级的变量同名。或者是像2.3.3节的例子那样,你可以将一个函数参数的名字声明为new,虽然内置的new是全局作用域的。但是物极必反,如果滥用不同词法域可重名的特性的话,可能导致程序很难阅读。 -当编译器遇到一个名字引用时,如果它看起来像一个声明,它首先从最内层的词法域向全局的作用域查找。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问: +当编译器遇到一个名字引用时,它会对其定义进行查找,查找过程从最内层的词法域向全局的作用域进行。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问: ```Go func f() {} From be4933599e97164b85d47b838974674429222bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=A2=9E?= <352281674@qq.com> Date: Thu, 1 Mar 2018 13:18:39 +0800 Subject: [PATCH 32/49] =?UTF-8?q?=E6=9B=B4=E6=96=B0gitbook=E7=89=88?= =?UTF-8?q?=E6=9C=AC=EF=BC=8C=E9=81=BF=E5=85=8D=E5=AE=89=E8=A3=85=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- book.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book.json b/book.json index 55fb5c3..8afdde8 100644 --- a/book.json +++ b/book.json @@ -1,5 +1,5 @@ { - "gitbook": "2.x", + "gitbook": "3.x", "title": "Go语言圣经", "description": "中文版", "language": "zh-hans", From a2ea244e714d0e96f16b67710b030e9bc62bc518 Mon Sep 17 00:00:00 2001 From: sherylynn <352281674@qq.com> Date: Thu, 1 Mar 2018 14:13:31 +0800 Subject: [PATCH 33/49] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86windows?= =?UTF-8?q?=E4=B8=8B=E6=9E=84=E5=BB=BA=E7=9A=84=E6=89=B9=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- make.bat | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 make.bat diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..61a62e8 --- /dev/null +++ b/make.bat @@ -0,0 +1,9 @@ +@echo off +cls +setlocal EnableDelayedExpansion +rem gitbook install +rem &^ 批处理运行gitbook会中断命令 所以用&链接,用^处理换行 +go run update_version.go +gitbook build &^ +go run fix-data-revision.go &^ +go run builder.go \ No newline at end of file From 05eaf14e5f8a4bbdd04a394f98f00c58e66bb593 Mon Sep 17 00:00:00 2001 From: seankang Date: Thu, 8 Mar 2018 20:05:41 +0800 Subject: [PATCH 34/49] Fix a typo --- ch4/ch4-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch4/ch4-06.md b/ch4/ch4-06.md index e1c04f3..22655d4 100644 --- a/ch4/ch4-06.md +++ b/ch4/ch4-06.md @@ -21,7 +21,7 @@ Age: {{.CreatedAt | daysAgo}} days {% raw %} -这个模板先打印匹配到的issue总数,然后打印每个issue的编号、创建用户、标题还有存在的时间。对于每一个action,都有一个当前值的概念,对应点操作符,写作“.”。当前值“.”最初被初始化为调用模板时的参数,在当前例子中对应github.IssuesSearchResult类型的变量。模板中`{{.TotalCount}}`对应action将展开为结构体中TotalCount成员以默认的方式打印的值。模板中`{{range .Items}}`和`{{end}}`对应一个循环action,因此它们直接的内容可能会被展开多次,循环每次迭代的当前值对应当前的Items元素的值。 +这个模板先打印匹配到的issue总数,然后打印每个issue的编号、创建用户、标题还有存在的时间。对于每一个action,都有一个当前值的概念,对应点操作符,写作“.”。当前值“.”最初被初始化为调用模板时的参数,在当前例子中对应github.IssuesSearchResult类型的变量。模板中`{{.TotalCount}}`对应action将展开为结构体中TotalCount成员以默认的方式打印的值。模板中`{{range .Items}}`和`{{end}}`对应一个循环action,因此它们之间的内容可能会被展开多次,循环每次迭代的当前值对应当前的Items元素的值。 {% endraw %} From a3473cf0f6832615de5244583814fc49f0ba44e1 Mon Sep 17 00:00:00 2001 From: kangxiaoning Date: Fri, 9 Mar 2018 17:15:27 +0800 Subject: [PATCH 35/49] Fix a typo --- ch5/ch5-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-01.md b/ch5/ch5-01.md index 0b5825c..5db2f17 100644 --- a/ch5/ch5-01.md +++ b/ch5/ch5-01.md @@ -19,7 +19,7 @@ fmt.Println(hypot(3,4)) // "5" ``` x和y是形参名,3和4是调用时的传入的实参,函数返回了一个float64类型的值。 -返回值也可以像形式参数一样被命名。在这种情况下,每个返回值被声明成一个局部变量,并根据该返回值的类型,将其初始化为0。 +返回值也可以像形式参数一样被命名。在这种情况下,每个返回值被声明成一个局部变量,并根据该返回值的类型,将其初始化为该类型的零值。 如果一个函数在声明时,包含返回值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic异常或函数中存在无限循环。 正如hypot一样,如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型。下面2个声明是等价的: From 76161c4b2274e56d05f0b4e9c63f197e10881d32 Mon Sep 17 00:00:00 2001 From: kangxiaoning Date: Fri, 9 Mar 2018 17:20:43 +0800 Subject: [PATCH 36/49] Fix a typo --- ch5/ch5-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-01.md b/ch5/ch5-01.md index 5db2f17..742303e 100644 --- a/ch5/ch5-01.md +++ b/ch5/ch5-01.md @@ -43,7 +43,7 @@ fmt.Printf("%T\n", first) // "func(int, int) int" fmt.Printf("%T\n", zero) // "func(int, int) int" ``` -函数的类型被称为函数的标识符。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型或标识符。形参和返回值的变量名不影响函数标识符,也不影响它们是否可以以省略参数类型的形式表示。 +函数的类型被称为函数的签名。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型或签名。形参和返回值的变量名不影响函数签名,也不影响它们是否可以以省略参数类型的形式表示。 每一次函数调用都必须按照声明顺序为所有参数提供实参(参数值)。在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。 From f619a6ebd4080300b92036feef101a0a4c1d2c12 Mon Sep 17 00:00:00 2001 From: kangxiaoning Date: Fri, 9 Mar 2018 17:38:51 +0800 Subject: [PATCH 37/49] Fix a typo --- ch5/ch5-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-01.md b/ch5/ch5-01.md index 742303e..b643bc0 100644 --- a/ch5/ch5-01.md +++ b/ch5/ch5-01.md @@ -51,7 +51,7 @@ fmt.Printf("%T\n", zero) // "func(int, int) int" 实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的间接引用被修改。 -你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数标识符。 +你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数签名。 ```Go package math From 5ae8b7cdb3524bb2cdda699fbeacf5d6db2f7f0c Mon Sep 17 00:00:00 2001 From: Xargin Date: Wed, 21 Mar 2018 17:37:17 +0800 Subject: [PATCH 38/49] fix typo --- ch8/ch8-04-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-04-1.md b/ch8/ch8-04-1.md index ce961bd..a0eb71a 100644 --- a/ch8/ch8-04-1.md +++ b/ch8/ch8-04-1.md @@ -2,7 +2,7 @@ 一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。 -基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。因为这个原因,无缓存Channels有时候也被称为同步Channels。当通过一个无缓存Channels发送数据时,接收者收到数据发生在唤醒发送者goroutine之前(译注:*happens before*,这是Go语言并发内存模型的一个关键术语!)。 +基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。因为这个原因,无缓存Channels有时候也被称为同步Channels。当通过一个无缓存Channels发送数据时,接收者收到数据发生在再次唤醒唤醒发送者goroutine之前(译注:*happens before*,这是Go语言并发内存模型的一个关键术语!)。 在讨论并发编程时,当我们说x事件在y事件之前发生(*happens before*),我们并不是说x事件在时间上比y时间更早;我们要表达的意思是要保证在此之前的事件都已经完成了,例如在此之前的更新某些变量的操作已经完成,你可以放心依赖这些已完成的事件了。 From 1788b095fd1e4a2eeba9cd95bea10c3668b4d526 Mon Sep 17 00:00:00 2001 From: Cloud Date: Sat, 31 Mar 2018 00:47:24 +0800 Subject: [PATCH 39/49] fix typo --- ch5/ch5-02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-02.md b/ch5/ch5-02.md index be92e4e..c63e6da 100644 --- a/ch5/ch5-02.md +++ b/ch5/ch5-02.md @@ -37,7 +37,7 @@ func Parse(r io.Reader) (*Node, error) main函数解析HTML标准输入,通过递归函数visit获得links(链接),并打印出这些links: -gopl.io/ch5/findlinks1 +gopl.io/ch5/findlinks1 ```Go // Findlinks1 prints the links in an HTML document read from standard input. package main From 686f88c60ec4d24cadfd93aec9f66c0fe373e16c Mon Sep 17 00:00:00 2001 From: Xargin Date: Wed, 16 May 2018 18:40:58 +0800 Subject: [PATCH 40/49] fix typo diff --git a/ch1/ch1-03.md b/ch1/ch1-03.md index 2861424..866573f 100644 --- a/ch1/ch1-03.md +++ b/ch1/ch1-03.md @@ -37,7 +37,7 @@ func main() { MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (译注:从功能和实现上说,`Go`的`map`类似于`Java`语言中的`HashMap`,Python语言中的`dict`,`Lua`语言中的`table`,通常使用`hash`实现。遗憾的是,对于该词的翻译并不统一,数学界术语为`映射`,而计算机界众说纷纭莫衷一是。为了防止对读者造成误解,保留不译。) -每次`dup`读取一行输入,该行被当做`map`,其对应的值递增。`counts[input.Text()]++`语句等价下面两句: +每次`dup`读取一行输入,该行被当做键存入`map`,其对应的值递增。`counts[input.Text()]++`语句等价下面两句: ```go line := input.Text() --- ch1/ch1-03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch1/ch1-03.md b/ch1/ch1-03.md index 2861424..866573f 100644 --- a/ch1/ch1-03.md +++ b/ch1/ch1-03.md @@ -37,7 +37,7 @@ func main() { (译注:从功能和实现上说,`Go`的`map`类似于`Java`语言中的`HashMap`,Python语言中的`dict`,`Lua`语言中的`table`,通常使用`hash`实现。遗憾的是,对于该词的翻译并不统一,数学界术语为`映射`,而计算机界众说纷纭莫衷一是。为了防止对读者造成误解,保留不译。) -每次`dup`读取一行输入,该行被当做`map`,其对应的值递增。`counts[input.Text()]++`语句等价下面两句: +每次`dup`读取一行输入,该行被当做键存入`map`,其对应的值递增。`counts[input.Text()]++`语句等价下面两句: ```go line := input.Text() From fb2f1a1d9bc7588c41cb56699c5522dba87a60c2 Mon Sep 17 00:00:00 2001 From: shanyy Date: Fri, 18 May 2018 17:15:31 +0800 Subject: [PATCH 41/49] Add preview host(most of hosts are broken) --- preface.md | 1 + 1 file changed, 1 insertion(+) diff --git a/preface.md b/preface.md index 8671dea..8f66c26 100644 --- a/preface.md +++ b/preface.md @@ -11,6 +11,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本 在线预览: +- https://gopl-zh.shanyy.xyz - http://gopl-zh.simple-is-best.tk/ - http://gopl-zh.b0.upaiyun.com/ - http://docs.ruanjiadeng.com/gopl-zh/ From 38543934d80cd94630a37eeb8777a9f89cf91a29 Mon Sep 17 00:00:00 2001 From: shanyy Date: Fri, 18 May 2018 17:25:57 +0800 Subject: [PATCH 42/49] Remove broken preview hosts --- preface.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/preface.md b/preface.md index 8f66c26..cac3122 100644 --- a/preface.md +++ b/preface.md @@ -12,12 +12,6 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本 在线预览: - https://gopl-zh.shanyy.xyz -- http://gopl-zh.simple-is-best.tk/ -- http://gopl-zh.b0.upaiyun.com/ -- http://docs.ruanjiadeng.com/gopl-zh/ -- http://shifei.me/gopl-zh/ -- http://2goo.info/media/html/gopl-zh-gh-pages/ -- http://docs.plhwin.com/gopl-zh/ - https://docs.hacknode.org/gopl-zh/ - http://books.studygolang.com/gopl-zh/ From 3d3ebe7235d555fcb9ba34df9d611ac3e68fafbc Mon Sep 17 00:00:00 2001 From: wahaha Date: Tue, 22 May 2018 16:05:42 +0800 Subject: [PATCH 43/49] Update ch5-03.md --- ch5/ch5-03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-03.md b/ch5/ch5-03.md index 18a167f..9b5b32f 100644 --- a/ch5/ch5-03.md +++ b/ch5/ch5-03.md @@ -41,7 +41,7 @@ func findLinks(url string) ([]string, error) { 在findlinks中,有4处return语句,每一处return都返回了一组值。前三处return,将http和html包中的错误信息传递给findlinks的调用者。第一处return直接返回错误信息,其他两处通过fmt.Errorf(§7.8)输出详细的错误信息。如果findlinks成功结束,最后的return语句将一组解析获得的连接返回给用户。 -在finallinks中,我们必须确保resp.Body被关闭,释放网络资源。虽然Go的垃圾回收机制会回收不被使用的内存,但是这不包括操作系统层面的资源,比如打开的文件、网络连接。因此我们必须显式的释放这些资源。 +在findlinks中,我们必须确保resp.Body被关闭,释放网络资源。虽然Go的垃圾回收机制会回收不被使用的内存,但是这不包括操作系统层面的资源,比如打开的文件、网络连接。因此我们必须显式的释放这些资源。 调用多返回值函数时,返回给调用者的是一组值,调用者必须显式的将这些值分配给变量: From 33bf82d477d6a4669962d0fc6ff056886b2b1d0e Mon Sep 17 00:00:00 2001 From: wahaha Date: Sun, 27 May 2018 01:12:05 +0800 Subject: [PATCH 44/49] Update ch7-03.md --- ch7/ch7-03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch7/ch7-03.md b/ch7/ch7-03.md index cf15d45..cf2cad8 100644 --- a/ch7/ch7-03.md +++ b/ch7/ch7-03.md @@ -34,7 +34,7 @@ func (*IntSet) String() string var _ = IntSet{}.String() // compile error: String requires *IntSet receiver ``` -但是我们可以在一个IntSet值上调用这个方法: +但是我们可以在一个IntSet变量上调用这个方法: ```go var s IntSet From e017c23a9a5195df5af1615ca78eba43b2ad5235 Mon Sep 17 00:00:00 2001 From: wahaha Date: Sun, 27 May 2018 01:15:55 +0800 Subject: [PATCH 45/49] Update ch7-03.md --- ch7/ch7-03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch7/ch7-03.md b/ch7/ch7-03.md index cf2cad8..5382826 100644 --- a/ch7/ch7-03.md +++ b/ch7/ch7-03.md @@ -22,7 +22,7 @@ w = rwc // OK: io.ReadWriteCloser has Write method rwc = w // compile error: io.Writer lacks Close method ``` -因为ReadWriter和ReadWriteCloser包含所有Writer的方法,所以任何实现了ReadWriter和ReadWriteCloser的类型必定也实现了Writer接口 +因为ReadWriter和ReadWriteCloser包含有Writer的方法,所以任何实现了ReadWriter和ReadWriteCloser的类型必定也实现了Writer接口 在进一步学习前,必须先解释一个类型持有一个方法的表示当中的细节。回想在6.2章中,对于每一个命名过的具体类型T;它的一些方法的接收者是类型T本身然而另一些则是一个`*T`的指针。还记得在T类型的参数上调用一个`*T`的方法是合法的,只要这个参数是一个变量;编译器隐式的获取了它的地址。但这仅仅是一个语法糖:T类型的值不拥有所有`*T`指针的方法,这样它就可能只实现了更少的接口。 From 0ab755766556a1553d8114e180f3fe3cb08785de Mon Sep 17 00:00:00 2001 From: kimw Date: Sun, 27 May 2018 16:51:15 -0400 Subject: [PATCH 46/49] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=8D=8A=E8=A7=92?= =?UTF-8?q?=E6=A0=87=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结点的内容。注意不要访问`