From 2b37b23285f4fb63cde7361bcaba9b46caac548a Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 15 Feb 2016 11:06:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E5=88=B0=E7=AE=80=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTORS.md | 8 +- README.md | 38 +++--- SUMMARY.md | 190 ++++++++++++++-------------- appendix/appendix-a-errata.md | 2 +- appendix/appendix-b-author.md | 6 +- appendix/appendix-c-cpoyright.md | 4 +- appendix/appendix-d-translations.md | 24 ++-- appendix/appendix-z-index.md | 4 +- appendix/appendix.md | 6 +- ch0/ch0-01.md | 18 +-- ch0/ch0-02.md | 16 +-- ch0/ch0-03.md | 38 +++--- ch0/ch0-04.md | 12 +- ch0/ch0-05.md | 14 +- ch1/ch1-01.md | 66 +++++----- ch1/ch1-02.md | 66 +++++----- ch1/ch1-03.md | 70 +++++----- ch1/ch1-04.md | 26 ++-- ch1/ch1-05.md | 20 +-- ch1/ch1-06.md | 16 +-- ch1/ch1-07.md | 38 +++--- ch1/ch1-08.md | 32 ++--- ch1/ch1.md | 6 +- ch10/ch10-01.md | 10 +- ch10/ch10-02.md | 8 +- ch10/ch10-03.md | 14 +- ch10/ch10-04.md | 14 +- ch10/ch10-05.md | 22 ++-- ch10/ch10-06.md | 22 ++-- ch10/ch10-07-1.md | 12 +- ch10/ch10-07-2.md | 20 +-- ch10/ch10-07-3.md | 32 ++--- ch10/ch10-07-4.md | 26 ++-- ch10/ch10-07-5.md | 8 +- ch10/ch10-07-6.md | 26 ++-- ch10/ch10-07.md | 8 +- ch10/ch10.md | 6 +- ch11/ch11-01.md | 6 +- ch11/ch11-02-1.md | 16 +-- ch11/ch11-02-2.md | 18 +-- ch11/ch11-02-3.md | 22 ++-- ch11/ch11-02-4.md | 28 ++-- ch11/ch11-02-5.md | 16 +-- ch11/ch11-02-6.md | 8 +- ch11/ch11-02.md | 48 +++---- ch11/ch11-03.md | 30 ++--- ch11/ch11-04.md | 40 +++--- ch11/ch11-05.md | 34 ++--- ch11/ch11-06.md | 14 +- ch11/ch11.md | 14 +- ch12/ch12-01.md | 12 +- ch12/ch12-02.md | 24 ++-- ch12/ch12-03.md | 54 ++++---- ch12/ch12-04.md | 36 +++--- ch12/ch12-05.md | 30 ++--- ch12/ch12-06.md | 32 ++--- ch12/ch12-07.md | 24 ++-- ch12/ch12-08.md | 8 +- ch12/ch12-09.md | 16 +-- ch12/ch12.md | 4 +- ch13/ch13-01.md | 50 ++++---- ch13/ch13-02.md | 28 ++-- ch13/ch13-03.md | 20 +-- ch13/ch13-04.md | 46 +++---- ch13/ch13-05.md | 12 +- ch13/ch13.md | 20 +-- ch2/ch2-01.md | 20 +-- ch2/ch2-02.md | 12 +- ch2/ch2-03-1.md | 26 ++-- ch2/ch2-03-2.md | 32 ++--- ch2/ch2-03-3.md | 22 ++-- ch2/ch2-03-4.md | 24 ++-- ch2/ch2-03.md | 18 +-- ch2/ch2-04-1.md | 36 +++--- ch2/ch2-04-2.md | 14 +- ch2/ch2-04.md | 22 ++-- ch2/ch2-05.md | 48 +++---- ch2/ch2-06-1.md | 18 +-- ch2/ch2-06-2.md | 30 ++--- ch2/ch2-06.md | 24 ++-- ch2/ch2-07.md | 50 ++++---- ch2/ch2.md | 6 +- ch3/ch3-01.md | 84 ++++++------ ch3/ch3-02.md | 50 ++++---- ch3/ch3-03.md | 26 ++-- ch3/ch3-04.md | 16 +-- ch3/ch3-05-1.md | 30 ++--- ch3/ch3-05-2.md | 8 +- ch3/ch3-05-3.md | 50 ++++---- ch3/ch3-05-4.md | 44 +++---- ch3/ch3-05-5.md | 16 +-- ch3/ch3-05.md | 26 ++-- ch3/ch3-06-1.md | 16 +-- ch3/ch3-06-2.md | 30 ++--- ch3/ch3-06.md | 18 +-- ch3/ch3.md | 6 +- ch4/ch4-01.md | 42 +++--- ch4/ch4-02-1.md | 32 ++--- ch4/ch4-02-2.md | 32 ++--- ch4/ch4-02.md | 40 +++--- ch4/ch4-03.md | 78 ++++++------ ch4/ch4-04-1.md | 26 ++-- ch4/ch4-04-2.md | 6 +- ch4/ch4-04-3.md | 32 ++--- ch4/ch4-04.md | 32 ++--- ch4/ch4-05.md | 50 ++++---- ch4/ch4-06.md | 40 +++--- ch4/ch4.md | 6 +- ch5/ch5-01.md | 28 ++-- ch5/ch5-02.md | 36 +++--- ch5/ch5-03.md | 32 ++--- ch5/ch5-04-1.md | 40 +++--- ch5/ch5-04-2.md | 8 +- ch5/ch5-04.md | 24 ++-- ch5/ch5-05.md | 38 +++--- ch5/ch5-06-1.md | 16 +-- ch5/ch5-06.md | 56 ++++---- ch5/ch5-07.md | 22 ++-- ch5/ch5-08.md | 42 +++--- ch5/ch5-09.md | 32 ++--- ch5/ch5-10.md | 26 ++-- ch5/ch5.md | 6 +- ch6/ch6-01.md | 30 ++--- ch6/ch6-02-1.md | 14 +- ch6/ch6-02.md | 38 +++--- ch6/ch6-03.md | 30 ++--- ch6/ch6-04.md | 24 ++-- ch6/ch6-05.md | 28 ++-- ch6/ch6-06.md | 36 +++--- ch6/ch6.md | 10 +- ch7/ch7-01.md | 32 ++--- ch7/ch7-02.md | 18 +-- ch7/ch7-03.md | 48 +++---- ch7/ch7-04.md | 24 ++-- ch7/ch7-05-1.md | 16 +-- ch7/ch7-05.md | 44 +++---- ch7/ch7-06.md | 56 ++++---- ch7/ch7-07.md | 48 +++---- ch7/ch7-08.md | 16 +-- ch7/ch7-09.md | 52 ++++---- ch7/ch7-10.md | 20 +-- ch7/ch7-11.md | 18 +-- ch7/ch7-12.md | 24 ++-- ch7/ch7-13.md | 28 ++-- ch7/ch7-14.md | 20 +-- ch7/ch7-15.md | 10 +- ch7/ch7.md | 6 +- ch8/ch8-01.md | 14 +- ch8/ch8-02.md | 28 ++-- ch8/ch8-03.md | 22 ++-- ch8/ch8-04-1.md | 20 +-- ch8/ch8-04-2.md | 22 ++-- ch8/ch8-04-3.md | 16 +-- ch8/ch8-04-4.md | 38 +++--- ch8/ch8-04.md | 18 +-- ch8/ch8-05.md | 40 +++--- ch8/ch8-06.md | 40 +++--- ch8/ch8-07.md | 34 ++--- ch8/ch8-08.md | 26 ++-- ch8/ch8-09.md | 28 ++-- ch8/ch8-10.md | 26 ++-- ch8/ch8.md | 6 +- ch9/ch9-01.md | 58 ++++----- ch9/ch9-02.md | 34 ++--- ch9/ch9-03.md | 14 +- ch9/ch9-04.md | 22 ++-- ch9/ch9-05.md | 22 ++-- ch9/ch9-06.md | 12 +- ch9/ch9-07.md | 64 +++++----- ch9/ch9-08-1.md | 8 +- ch9/ch9-08-2.md | 10 +- ch9/ch9-08-3.md | 8 +- ch9/ch9-08-4.md | 10 +- ch9/ch9-08.md | 4 +- ch9/ch9.md | 6 +- links.md | 10 +- preface.md | 60 ++++----- 177 files changed, 2356 insertions(+), 2356 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0da21a4a..06cda0e1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,14 +1,14 @@ -# 貢獻者 +# 贡献者 -譯者 | 章節 +译者 | 章节 -------------------------------------- | ------------------------- `chai2010 ` | 前言/第2~4章/第10~13章 `Xargin ` | 第1章/第6章/第8~9章 `CrazySssst` | 第5章 `foreversmart ` | 第7章 -# 譯文授權 +# 译文授权 -除特别註明外, 本站內容均采用[知識共享-署名(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/README.md b/README.md index a965e5e1..0f95956c 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,37 @@ -# Go語言聖經(中文版) +# Go语言圣经(中文版) -Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本,僅供學習交流之用。 +Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本,仅供学习交流之用。 [![](cover_middle.jpg)](http://golang-china.github.io/gopl-zh) -- 在線版本:http://golang-china.github.io/gopl-zh -- 離線版本:http://github.com/golang-china/gopl-zh/archive/gh-pages.zip -- 項目主頁:http://github.com/golang-china/gopl-zh -- 原版官網:http://gopl.io +- 在线版本:http://golang-china.github.io/gopl-zh +- 离线版本:http://github.com/golang-china/gopl-zh/archive/gh-pages.zip +- 项目主页:http://github.com/golang-china/gopl-zh +- 原版官网:http://gopl.io -### 從源文件構建 +### 从源文件构建 -先安裝NodeJS和GitBook命令行工具(`npm install gitbook-cli -g`命令)。 +先安装NodeJS和GitBook命令行工具(`npm install gitbook-cli -g`命令)。 -1. 運行`go get github.com/golang-china/gopl-zh`,獲取 [源文件](https://github.com/golang-china/gopl-zh/archive/master.zip)。 -2. 切換到 `gopl-zh` 目録,運行 `gitbook install`,安裝GitBook插件。 -3. 運行`make`,生成`_book`目録。 -4. 打開`_book/index.html`文件。 +1. 运行`go get github.com/golang-china/gopl-zh`,获取 [源文件](https://github.com/golang-china/gopl-zh/archive/master.zip)。 +2. 切换到 `gopl-zh` 目录,运行 `gitbook install`,安装GitBook插件。 +3. 运行`make`,生成`_book`目录。 +4. 打开`_book/index.html`文件。 -### 簡體/繁體轉換 +### 简体/繁体转换 -切片到 `gopl-zh` 目録: +切片到 `gopl-zh` 目录: -- `make zh2tw` 或 `go run zh2tw.go . "\.md$" zh2tw`,轉繁體。 -- `make tw2zh` 或 `go run zh2tw.go . "\.md$" tw2zh`,轉簡體。 +- `make zh2tw` 或 `go run zh2tw.go . "\.md$" zh2tw`,转繁体。 +- `make tw2zh` 或 `go run zh2tw.go . "\.md$" tw2zh`,转简体。 -# 版權聲明 +# 版权声明 Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International LicenseCreative Commons License -嚴禁任何商業行爲使用或引用該文檔的全部或部分內容! +严禁任何商业行为使用或引用该文档的全部或部分内容! -歡迎大家提供建議! +欢迎大家提供建议! diff --git a/SUMMARY.md b/SUMMARY.md index 9324f9ba..798a1f9e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,129 +1,129 @@ # Summary * [前言](preface.md) - * [Go語言起源](ch0/ch0-01.md) - * [Go語言項目](ch0/ch0-02.md) - * [本書的組織](ch0/ch0-03.md) + * [Go语言起源](ch0/ch0-01.md) + * [Go语言项目](ch0/ch0-02.md) + * [本书的组织](ch0/ch0-03.md) * [更多的信息](ch0/ch0-04.md) - * [致謝](ch0/ch0-05.md) -* [入門](ch1/ch1.md) + * [致谢](ch0/ch0-05.md) +* [入门](ch1/ch1.md) * [Hello, World](ch1/ch1-01.md) - * [命令行參數](ch1/ch1-02.md) - * [査找重複的行](ch1/ch1-03.md) - * [GIF動畵](ch1/ch1-04.md) - * [獲取URL](ch1/ch1-05.md) - * [併發獲取多個URL](ch1/ch1-06.md) - * [Web服務](ch1/ch1-07.md) - * [本章要點](ch1/ch1-08.md) -* [程序結構](ch2/ch2.md) + * [命令行参数](ch1/ch1-02.md) + * [查找重复的行](ch1/ch1-03.md) + * [GIF动画](ch1/ch1-04.md) + * [获取URL](ch1/ch1-05.md) + * [并发获取多个URL](ch1/ch1-06.md) + * [Web服务](ch1/ch1-07.md) + * [本章要点](ch1/ch1-08.md) +* [程序结构](ch2/ch2.md) * [命名](ch2/ch2-01.md) - * [聲明](ch2/ch2-02.md) - * [變量](ch2/ch2-03.md) - * [賦值](ch2/ch2-04.md) - * [類型](ch2/ch2-05.md) + * [声明](ch2/ch2-02.md) + * [变量](ch2/ch2-03.md) + * [赋值](ch2/ch2-04.md) + * [类型](ch2/ch2-05.md) * [包和文件](ch2/ch2-06.md) * [作用域](ch2/ch2-07.md) -* [基礎數據類型](ch3/ch3.md) +* [基础数据类型](ch3/ch3.md) * [整型](ch3/ch3-01.md) - * [浮點數](ch3/ch3-02.md) - * [複數](ch3/ch3-03.md) - * [布爾型](ch3/ch3-04.md) + * [浮点数](ch3/ch3-02.md) + * [复数](ch3/ch3-03.md) + * [布尔型](ch3/ch3-04.md) * [字符串](ch3/ch3-05.md) * [常量](ch3/ch3-06.md) -* [複合數據類型](ch4/ch4.md) - * [數組](ch4/ch4-01.md) +* [复合数据类型](ch4/ch4.md) + * [数组](ch4/ch4-01.md) * [Slice](ch4/ch4-02.md) * [Map](ch4/ch4-03.md) - * [結構體](ch4/ch4-04.md) + * [结构体](ch4/ch4-04.md) * [JSON](ch4/ch4-05.md) - * [文本和HTML模闆](ch4/ch4-06.md) -* [函數](ch5/ch5.md) - * [函數聲明](ch5/ch5-01.md) - * [遞歸](ch5/ch5-02.md) - * [多返迴值](ch5/ch5-03.md) - * [錯誤](ch5/ch5-04.md) - * [函數值](ch5/ch5-05.md) - * [匿名函數](ch5/ch5-06.md) - * [可變參數](ch5/ch5-07.md) - * [Deferred函數](ch5/ch5-08.md) - * [Panic異常](ch5/ch5-09.md) - * [Recover捕獲異常](ch5/ch5-10.md) + * [文本和HTML模板](ch4/ch4-06.md) +* [函数](ch5/ch5.md) + * [函数声明](ch5/ch5-01.md) + * [递归](ch5/ch5-02.md) + * [多返回值](ch5/ch5-03.md) + * [错误](ch5/ch5-04.md) + * [函数值](ch5/ch5-05.md) + * [匿名函数](ch5/ch5-06.md) + * [可变参数](ch5/ch5-07.md) + * [Deferred函数](ch5/ch5-08.md) + * [Panic异常](ch5/ch5-09.md) + * [Recover捕获异常](ch5/ch5-10.md) * [方法](ch6/ch6.md) - * [方法聲明](ch6/ch6-01.md) - * [基於指針對象的方法](ch6/ch6-02.md) - * [通過嵌入結構體來擴展類型](ch6/ch6-03.md) - * [方法值和方法表達式](ch6/ch6-04.md) - * [示例: Bit數組](ch6/ch6-05.md) - * [封裝](ch6/ch6-06.md) + * [方法声明](ch6/ch6-01.md) + * [基于指针对象的方法](ch6/ch6-02.md) + * [通过嵌入结构体来扩展类型](ch6/ch6-03.md) + * [方法值和方法表达式](ch6/ch6-04.md) + * [示例: Bit数组](ch6/ch6-05.md) + * [封装](ch6/ch6-06.md) * [接口](ch7/ch7.md) - * [接口是合約](ch7/ch7-01.md) - * [接口類型](ch7/ch7-02.md) - * [實現接口的條件](ch7/ch7-03.md) + * [接口是合约](ch7/ch7-01.md) + * [接口类型](ch7/ch7-02.md) + * [实现接口的条件](ch7/ch7-03.md) * [flag.Value接口](ch7/ch7-04.md) * [接口值](ch7/ch7-05.md) * [sort.Interface接口](ch7/ch7-06.md) * [http.Handler接口](ch7/ch7-07.md) * [error接口](ch7/ch7-08.md) - * [示例: 表達式求值](ch7/ch7-09.md) - * [類型斷言](ch7/ch7-10.md) - * [基於類型斷言識别錯誤類型](ch7/ch7-11.md) - * [通過類型斷言査詢接口](ch7/ch7-12.md) - * [類型分支](ch7/ch7-13.md) - * [示例: 基於標記的XML解碼](ch7/ch7-14.md) - * [補充幾點](ch7/ch7-15.md) + * [示例: 表达式求值](ch7/ch7-09.md) + * [类型断言](ch7/ch7-10.md) + * [基于类型断言识别错误类型](ch7/ch7-11.md) + * [通过类型断言查询接口](ch7/ch7-12.md) + * [类型分支](ch7/ch7-13.md) + * [示例: 基于标记的XML解码](ch7/ch7-14.md) + * [补充几点](ch7/ch7-15.md) * [Goroutines和Channels](ch8/ch8.md) * [Goroutines](ch8/ch8-01.md) - * [示例: 併發的Clock服務](ch8/ch8-02.md) - * [示例: 併發的Echo服務](ch8/ch8-03.md) + * [示例: 并发的Clock服务](ch8/ch8-02.md) + * [示例: 并发的Echo服务](ch8/ch8-03.md) * [Channels](ch8/ch8-04.md) - * [併發的循環](ch8/ch8-05.md) - * [示例: 併發的Web爬蟲](ch8/ch8-06.md) - * [基於select的多路複用](ch8/ch8-07.md) - * [示例: 併發的字典遍歷](ch8/ch8-08.md) - * [併發的退出](ch8/ch8-09.md) - * [示例: 聊天服務](ch8/ch8-10.md) -* [基於共享變量的併發](ch9/ch9.md) - * [競爭條件](ch9/ch9-01.md) - * [sync.Mutex互斥鎖](ch9/ch9-02.md) - * [sync.RWMutex讀寫鎖](ch9/ch9-03.md) - * [內存同步](ch9/ch9-04.md) + * [并发的循环](ch8/ch8-05.md) + * [示例: 并发的Web爬虫](ch8/ch8-06.md) + * [基于select的多路复用](ch8/ch8-07.md) + * [示例: 并发的字典遍历](ch8/ch8-08.md) + * [并发的退出](ch8/ch8-09.md) + * [示例: 聊天服务](ch8/ch8-10.md) +* [基于共享变量的并发](ch9/ch9.md) + * [竞争条件](ch9/ch9-01.md) + * [sync.Mutex互斥锁](ch9/ch9-02.md) + * [sync.RWMutex读写锁](ch9/ch9-03.md) + * [内存同步](ch9/ch9-04.md) * [sync.Once初始化](ch9/ch9-05.md) - * [競爭條件檢測](ch9/ch9-06.md) - * [示例: 併發的非阻塞緩存](ch9/ch9-07.md) - * [Goroutines和線程](ch9/ch9-08.md) + * [竞争条件检测](ch9/ch9-06.md) + * [示例: 并发的非阻塞缓存](ch9/ch9-07.md) + * [Goroutines和线程](ch9/ch9-08.md) * [包和工具](ch10/ch10.md) - * [包簡介](ch10/ch10-01.md) - * [導入路徑](ch10/ch10-02.md) - * [包聲明](ch10/ch10-03.md) - * [導入聲明](ch10/ch10-04.md) - * [包的匿名導入](ch10/ch10-05.md) + * [包简介](ch10/ch10-01.md) + * [导入路径](ch10/ch10-02.md) + * [包声明](ch10/ch10-03.md) + * [导入声明](ch10/ch10-04.md) + * [包的匿名导入](ch10/ch10-05.md) * [包和命名](ch10/ch10-06.md) * [工具](ch10/ch10-07.md) -* [測試](ch11/ch11.md) +* [测试](ch11/ch11.md) * [go test](ch11/ch11-01.md) - * [測試函數](ch11/ch11-02.md) - * [測試覆蓋率](ch11/ch11-03.md) - * [基準測試](ch11/ch11-04.md) + * [测试函数](ch11/ch11-02.md) + * [测试覆盖率](ch11/ch11-03.md) + * [基准测试](ch11/ch11-04.md) * [剖析](ch11/ch11-05.md) - * [示例函數](ch11/ch11-06.md) + * [示例函数](ch11/ch11-06.md) * [反射](ch12/ch12.md) - * [爲何需要反射?](ch12/ch12-01.md) + * [为何需要反射?](ch12/ch12-01.md) * [reflect.Type和reflect.Value](ch12/ch12-02.md) - * [Display遞歸打印](ch12/ch12-03.md) - * [示例: 編碼S表達式](ch12/ch12-04.md) - * [通過reflect.Value脩改值](ch12/ch12-05.md) - * [示例: 解碼S表達式](ch12/ch12-06.md) - * [獲取結構體字段標識](ch12/ch12-07.md) - * [顯示一個類型的方法集](ch12/ch12-08.md) - * [幾點忠告](ch12/ch12-09.md) -* [底層編程](ch13/ch13.md) + * [Display递归打印](ch12/ch12-03.md) + * [示例: 编码S表达式](ch12/ch12-04.md) + * [通过reflect.Value修改值](ch12/ch12-05.md) + * [示例: 解码S表达式](ch12/ch12-06.md) + * [获取结构体字段标识](ch12/ch12-07.md) + * [显示一个类型的方法集](ch12/ch12-08.md) + * [几点忠告](ch12/ch12-09.md) +* [底层编程](ch13/ch13.md) * [unsafe.Sizeof, Alignof 和 Offsetof](ch13/ch13-01.md) * [unsafe.Pointer](ch13/ch13-02.md) - * [示例: 深度相等判斷](ch13/ch13-03.md) - * [通過cgo調用C代碼](ch13/ch13-04.md) - * [幾點忠告](ch13/ch13-05.md) -* [附録](appendix/appendix.md) - * [附録A:原文勘誤](appendix/appendix-a-errata.md) - * [附録B:作者譯者](appendix/appendix-b-author.md) - * [附録C:譯文授權](appendix/appendix-c-cpoyright.md) - * [附録D:其它語言](appendix/appendix-d-translations.md) + * [示例: 深度相等判断](ch13/ch13-03.md) + * [通过cgo调用C代码](ch13/ch13-04.md) + * [几点忠告](ch13/ch13-05.md) +* [附录](appendix/appendix.md) + * [附录A:原文勘误](appendix/appendix-a-errata.md) + * [附录B:作者译者](appendix/appendix-b-author.md) + * [附录C:译文授权](appendix/appendix-c-cpoyright.md) + * [附录D:其它语言](appendix/appendix-d-translations.md) diff --git a/appendix/appendix-a-errata.md b/appendix/appendix-a-errata.md index 6dcf86ee..c88012c2 100644 --- a/appendix/appendix-a-errata.md +++ b/appendix/appendix-a-errata.md @@ -1,4 +1,4 @@ -## 附録A:[原文勘誤](http://www.gopl.io/errata.html) +## 附录A:[原文勘误](http://www.gopl.io/errata.html) **p.9, ¶2:** for "can compared", read "can be compared". (Thanks to Antonio Macías Ojeda, 2015-10-22. Corrected in the second printing.) diff --git a/appendix/appendix-b-author.md b/appendix/appendix-b-author.md index 2823d2ed..3eb768b2 100644 --- a/appendix/appendix-b-author.md +++ b/appendix/appendix-b-author.md @@ -1,4 +1,4 @@ -## 附録B:作者/譯者 +## 附录B:作者/译者 ### 英文作者 @@ -7,9 +7,9 @@ ------- -### 中文譯者 +### 中文译者 -中文譯者 | 章節 +中文译者 | 章节 -------------------------------------- | ------------------------- `chai2010 ` | 前言/第2~4章/第10~13章 `Xargin ` | 第1章/第6章/第8~9章 diff --git a/appendix/appendix-c-cpoyright.md b/appendix/appendix-c-cpoyright.md index 4af0aefc..d40ba64d 100644 --- a/appendix/appendix-c-cpoyright.md +++ b/appendix/appendix-c-cpoyright.md @@ -1,6 +1,6 @@ -## 附録C:譯文授權 +## 附录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/appendix/appendix-d-translations.md b/appendix/appendix-d-translations.md index d23880d5..16fdb139 100644 --- a/appendix/appendix-d-translations.md +++ b/appendix/appendix-d-translations.md @@ -1,20 +1,20 @@ -## 附録D:其它語言 +## 附录D:其它语言 -下表是 [The Go Programming Language](http://www.gopl.io/) 其它語言版本: +下表是 [The Go Programming Language](http://www.gopl.io/) 其它语言版本: -語言 | 鏈接 | 時間 | 譯者 | ISBN +语言 | 链接 | 时间 | 译者 | ISBN ---- | ---- | ---- | ---- | ---- -中文 | [《Go語言聖經》][gopl-zh] | 2016/2/1 | [chai2010][chai2010], [Xargin][Xargin], [CrazySssst][CrazySssst], [foreversmart][foreversmart] | ? -韓語 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ? -俄語 | [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ? -波蘭語 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ? -日語 | [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ? -葡萄牙語 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ? -中文簡體 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ? -中文繁體 | [Gotop Information (Taiwan)](http://www.gotop.com.tw/) | 2017 | ? | ? +中文 | [《Go语言圣经》][gopl-zh] | 2016/2/1 | [chai2010][chai2010], [Xargin][Xargin], [CrazySssst][CrazySssst], [foreversmart][foreversmart] | ? +韩语 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ? +俄语 | [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ? +波兰语 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ? +日语 | [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ? +葡萄牙语 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ? +中文简体 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ? +中文繁体 | [Gotop Information (Taiwan)](http://www.gotop.com.tw/) | 2017 | ? | ? -[gopl-zh]: http://golang-china.github.io/gopl-zh/ "《Go語言聖經》" +[gopl-zh]: http://golang-china.github.io/gopl-zh/ "《Go语言圣经》" [chai2010]: https://github.com/chai2010 [Xargin]: https://github.com/cch123 diff --git a/appendix/appendix-z-index.md b/appendix/appendix-z-index.md index 980ee3b3..449c618e 100644 --- a/appendix/appendix-z-index.md +++ b/appendix/appendix-z-index.md @@ -1,6 +1,6 @@ # 索引 - @@ -85,7 +85,7 @@ TODO ### P400 - diff --git a/appendix/appendix.md b/appendix/appendix.md index c9f7301e..62d3128a 100644 --- a/appendix/appendix.md +++ b/appendix/appendix.md @@ -1,6 +1,6 @@ -# 附録 +# 附录 -英文原版併沒有包含附録部分,隻有一個索引部分。中文版增加附録部分主要用於收録一些和本書相關的內容,比如英文原版的勘誤(有些讀者可能會對照中文和英文原閲讀)、英文作者和中文譯者、譯文授權等內容。以後還可能會考慮增加一些習題解答相關的內容。 +英文原版并没有包含附录部分,只有一个索引部分。中文版增加附录部分主要用于收录一些和本书相关的内容,比如英文原版的勘误(有些读者可能会对照中文和英文原阅读)、英文作者和中文译者、译文授权等内容。以后还可能会考虑增加一些习题解答相关的内容。 -需要特别説明的是,中文版附録併沒有包含英文原版的索引信息。因爲英文原版的索引信息主要是記録每個索引所在的英文頁面位置,而中文版是以GitBook方式組織的html網頁形式,將英文頁面位置轉爲章節位置可能會更合理,不過這個會涉及到繁瑣的手工操作。如果大家有更好的建議,請告知我們。 +需要特别说明的是,中文版附录并没有包含英文原版的索引信息。因为英文原版的索引信息主要是记录每个索引所在的英文页面位置,而中文版是以GitBook方式组织的html网页形式,将英文页面位置转为章节位置可能会更合理,不过这个会涉及到繁琐的手工操作。如果大家有更好的建议,请告知我们。 diff --git a/ch0/ch0-01.md b/ch0/ch0-01.md index 0a66eb5a..475dbc0f 100644 --- a/ch0/ch0-01.md +++ b/ch0/ch0-01.md @@ -1,21 +1,21 @@ -## Go語言起源 +## Go语言起源 -編程語言的演化就像生物物種的演化類似,一個成功的編程語言的後代一般都會繼承它們祖先的優點;當然有時多種語言雜合也可能會産生令人驚訝的特性;還有一些激進的新特性可能併沒有先例。我們可以通過觀察編程語言和軟硬件環境是如何相互促進、相互影響的演化過程而學到很多。 +编程语言的演化就像生物物种的演化类似,一个成功的编程语言的后代一般都会继承它们祖先的优点;当然有时多种语言杂合也可能会产生令人惊讶的特性;还有一些激进的新特性可能并没有先例。我们可以通过观察编程语言和软硬件环境是如何相互促进、相互影响的演化过程而学到很多。 -下圖展示了有哪些早期的編程語言對Go語言的設計産生了重要影響。 +下图展示了有哪些早期的编程语言对Go语言的设计产生了重要影响。 ![](../images/ch0-01.png) -Go語言有時候被描述爲“C類似語言”,或者是“21世紀的C語言”。Go從C語言繼承了相似的表達式語法、控製流結構、基礎數據類型、調用參數傳值、指針等很多思想,還有C語言一直所看中的編譯後機器碼的運行效率以及和現有操作繫統的無縫適配。 +Go语言有时候被描述为“C类似语言”,或者是“21世纪的C语言”。Go从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。 -但是在Go語言的家族樹中還有其它的祖先。其中一個有影響力的分支來自[Niklaus Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)所設計的[Pascal][Pascal]語言。然後[Modula-2][Modula-2]語言激發了包的概念。然後[Oberon][Oberon]語言摒棄了模塊接口文件和模塊實現文件之間的區别。第二代的[Oberon-2][Oberon-2]語言直接影響了包的導入和聲明的語法,還有[Oberon][Oberon]語言的面向對象特性所提供的方法的聲明語法等。 +但是在Go语言的家族树中还有其它的祖先。其中一个有影响力的分支来自[Niklaus Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)所设计的[Pascal][Pascal]语言。然后[Modula-2][Modula-2]语言激发了包的概念。然后[Oberon][Oberon]语言摒弃了模块接口文件和模块实现文件之间的区别。第二代的[Oberon-2][Oberon-2]语言直接影响了包的导入和声明的语法,还有[Oberon][Oberon]语言的面向对象特性所提供的方法的声明语法等。 -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]隻是一個用於描述併發性基本概念的描述語言,併不是一個可以編寫可執行程序的通用編程語言。 +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語言改造爲繫統編程語言,但是因爲缺少垃圾迴收機製而導致併發編程很痛苦。(譯註:在Aelf之後還有一個叫[Limbo][Limbo]的編程語言,Go語言從其中借鑒了很多特性。 具體請參考Pike的講稿:http://talks.golang.org/2012/concurrency.slide#9 ) +在Plan9操作系统中,这些优秀的想法被吸收到了一个叫[Alef][Alef]的编程语言中。Alef试图将Newsqueak语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。(译注:在Aelf之后还有一个叫[Limbo][Limbo]的编程语言,Go语言从其中借鉴了很多特性。 具体请参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 ) -Go語言的其他的一些特性零散地來自於其他一些編程語言;比如iota語法是從[APL][APL]語言借鑒,詞法作用域與嵌套函數來自於[Scheme][Scheme]語言(和其他很多語言)。當然,我們也可以從Go中發現很多創新的設計。比如Go語言的切片爲動態數組提供了有效的隨機存取的性能,這可能會讓人聯想到鏈表的底層的共享機製。還有Go語言新發明的defer語句。 +Go语言的其他的一些特性零散地来自于其他一些编程语言;比如iota语法是从[APL][APL]语言借鉴,词法作用域与嵌套函数来自于[Scheme][Scheme]语言(和其他很多语言)。当然,我们也可以从Go中发现很多创新的设计。比如Go语言的切片为动态数组提供了有效的随机存取的性能,这可能会让人联想到链表的底层的共享机制。还有Go语言新发明的defer语句。 {% include "../links.md" %} diff --git a/ch0/ch0-02.md b/ch0/ch0-02.md index 716710ad..197b072e 100644 --- a/ch0/ch0-02.md +++ b/ch0/ch0-02.md @@ -1,16 +1,16 @@ -## Go語言項目 +## Go语言项目 -所有的編程語言都反映了語言設計者對編程哲學的反思,通常包括之前的語言所暴露的一些不足地方的改進。Go項目是在Google公司維護超級複雜的幾個軟件繫統遇到的一些問題的反思(但是這類問題絶不是Google公司所特有的)。 +所有的编程语言都反映了语言设计者对编程哲学的反思,通常包括之前的语言所暴露的一些不足地方的改进。Go项目是在Google公司维护超级复杂的几个软件系统遇到的一些问题的反思(但是这类问题绝不是Google公司所特有的)。 -正如[Rob Pike](http://genius.cat-v.org/rob-pike/)所説,“軟件的複雜性是乘法級相關的”,通過增加一個部分的複雜性來脩複問題通常將慢慢地增加其他部分的複雜性。通過增加功能和選項和配置是脩複問題的最快的途徑,但是這很容易讓人忘記簡潔的內涵,卽使從長遠來看,簡潔依然是好軟件的關鍵因素。 +正如[Rob Pike](http://genius.cat-v.org/rob-pike/)所说,“软件的复杂性是乘法级相关的”,通过增加一个部分的复杂性来修复问题通常将慢慢地增加其他部分的复杂性。通过增加功能和选项和配置是修复问题的最快的途径,但是这很容易让人忘记简洁的内涵,即使从长远来看,简洁依然是好软件的关键因素。 -簡潔的設計需要在工作開始的時候舍棄不必要的想法,併且在軟件的生命週期內嚴格區别好的改變或壞的改變。通過足夠的努力,一個好的改變可以在不破壞原有完整概念的前提下保持自適應,正如[Fred Brooks](http://www.cs.unc.edu/~brooks/)所説的“概念完整性”;而一個壞的改變則不能達到這個效果,它們僅僅是通過膚淺的和簡單的妥協來破壞原有設計的一致性。隻有通過簡潔的設計,才能讓一個繫統保持穩定、安全和持續的進化。 +简洁的设计需要在工作开始的时候舍弃不必要的想法,并且在软件的生命周期内严格区别好的改变或坏的改变。通过足够的努力,一个好的改变可以在不破坏原有完整概念的前提下保持自适应,正如[Fred Brooks](http://www.cs.unc.edu/~brooks/)所说的“概念完整性”;而一个坏的改变则不能达到这个效果,它们仅仅是通过肤浅的和简单的妥协来破坏原有设计的一致性。只有通过简洁的设计,才能让一个系统保持稳定、安全和持续的进化。 -Go項目包括編程語言本身,附帶了相關的工具和標準庫,最後但併非代表不重要的,關於簡潔編程哲學的宣言。就事後諸葛的角度來看,Go語言的這些地方都做的還不錯:擁有自動垃圾迴收、一個包繫統、函數作爲一等公民、詞法作用域、繫統調用接口、隻讀的UTF8字符串等。但是Go語言本身隻有很少的特性,也不太可能添加太多的特性。例如,它沒有隱式的數值轉換,沒有構造函數和析構函數,沒有運算符重載,沒有默認參數,也沒有繼承,沒有泛型,沒有異常,沒有宏,沒有函數脩飾,更沒有線程局部存儲。但是語言本身是成熟和穩定的,而且承諾保證向後兼容:用之前的Go語言編寫程序可以用新版本的Go語言編譯器和標準庫直接構建而不需要脩改代碼。 +Go项目包括编程语言本身,附带了相关的工具和标准库,最后但并非代表不重要的,关于简洁编程哲学的宣言。就事后诸葛的角度来看,Go语言的这些地方都做的还不错:拥有自动垃圾回收、一个包系统、函数作为一等公民、词法作用域、系统调用接口、只读的UTF8字符串等。但是Go语言本身只有很少的特性,也不太可能添加太多的特性。例如,它没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。但是语言本身是成熟和稳定的,而且承诺保证向后兼容:用之前的Go语言编写程序可以用新版本的Go语言编译器和标准库直接构建而不需要修改代码。 -Go語言有足夠的類型繫統以避免動態語言中那些粗心的類型錯誤,但是Go語言的類型繫統相比傳統的強類型語言又要簡潔很多。雖然有時候這會導致一個“無類型”的抽象類型概念,但是Go語言程序員併不需要像C++或Haskell程序員那樣糾結於具體類型的安全屬性。在實踐中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語言源代碼本身就包含了構建規范。 +Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口,包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。库和工具使用了大量的约定来减少额外的配置和解释,从而最终简化程序的逻辑,而且每个Go程序结构都是如此的相似,因此Go程序也很容易学习。使用Go语言自带工具构建Go语言项目只需要使用文件名和标识符名称, 一个偶尔的特殊注释来确定所有的库、可执行文件、测试、基准测试、例子、以及特定于平台的变量、项目的文档等;Go语言源代码本身就包含了构建规范。 diff --git a/ch0/ch0-03.md b/ch0/ch0-03.md index e838d673..16eabb2e 100644 --- a/ch0/ch0-03.md +++ b/ch0/ch0-03.md @@ -1,42 +1,42 @@ -## 本書的組織 +## 本书的组织 -我們假設你已經有一種或多種其他編程語言的使用經歷,不管是類似C、c++或Java的編譯型語言,還是類似Python、Ruby、JavaScript的腳本語言,因此我們不會像對完全的編程語言初學者那樣解釋所有的細節。因爲Go語言的變量、常量、表達式、控製流和函數等基本語法也是類似的。 +我们假设你已经有一种或多种其他编程语言的使用经历,不管是类似C、c++或Java的编译型语言,还是类似Python、Ruby、JavaScript的脚本语言,因此我们不会像对完全的编程语言初学者那样解释所有的细节。因为Go语言的变量、常量、表达式、控制流和函数等基本语法也是类似的。 -第一章包含了本敎程的基本結構,通過十幾個程序介紹了用Go語言如何實現 類似讀寫文件、文本格式化、創建圖像、網絡客戶端和服務器通訊等日常工作。 +第一章包含了本教程的基本结构,通过十几个程序介绍了用Go语言如何实现 类似读写文件、文本格式化、创建图像、网络客户端和服务器通讯等日常工作。 -第二章描述了Go語言程序的基本元素結構、變量、新類型定義、包和文件、以及作用域的概念。第三章討論了數字、布爾值、字符串和常量,併演示了如何顯示和處理Unicode字符。第四章描述了複合類型,從簡單的數組、字典、切片到動態列表。第五章涵蓋了函數,併討論了錯誤處理、panic和recover,還有defer語句。 +第二章描述了Go语言程序的基本元素结构、变量、新类型定义、包和文件、以及作用域的概念。第三章讨论了数字、布尔值、字符串和常量,并演示了如何显示和处理Unicode字符。第四章描述了复合类型,从简单的数组、字典、切片到动态列表。第五章涵盖了函数,并讨论了错误处理、panic和recover,还有defer语句。 -第一章到第五章是基礎部分,主流命令式編程語言這部分都類似。個别之處,Go語言有自己特色的語法和風格,但是大多數程序員能很快適應。其餘章節是Go語言特有的:方法、接口、併發、包、測試和反射等語言特性。 +第一章到第五章是基础部分,主流命令式编程语言这部分都类似。个别之处,Go语言有自己特色的语法和风格,但是大多数程序员能很快适应。其余章节是Go语言特有的:方法、接口、并发、包、测试和反射等语言特性。 -Go語言的面向對象機製與一般語言不同。它沒有類層次結構,甚至可以説沒有類;僅僅通過組合(而不是繼承)簡單的對象來構建複雜的對象。方法不僅可以定義在結構體上, 而且可以定義在任何用戶自定義的類型上;併且具體類型和抽象類型(接口)之間的關繫是隱式的,所以很多類型的設計者可能併不知道該類型到底實現了哪些接口。方法在第六章討論,接口在第七章討論。 +Go语言的面向对象机制与一般语言不同。它没有类层次结构,甚至可以说没有类;仅仅通过组合(而不是继承)简单的对象来构建复杂的对象。方法不仅可以定义在结构体上, 而且可以定义在任何用户自定义的类型上;并且具体类型和抽象类型(接口)之间的关系是隐式的,所以很多类型的设计者可能并不知道该类型到底实现了哪些接口。方法在第六章讨论,接口在第七章讨论。 -第八章討論了基於順序通信進程(CSP)概念的併發編程,使用goroutines和channels處理併發編程。第九章則討論了傳統的基於共享變量的併發編程。 +第八章讨论了基于顺序通信进程(CSP)概念的并发编程,使用goroutines和channels处理并发编程。第九章则讨论了传统的基于共享变量的并发编程。 -第十章描述了包機製和包的組織結構。這一章還展示了如何有效的利用Go自帶的工具,使用單個命令完成編譯、測試、基準測試、代碼格式化、文檔以及其他諸多任務。 +第十章描述了包机制和包的组织结构。这一章还展示了如何有效的利用Go自带的工具,使用单个命令完成编译、测试、基准测试、代码格式化、文档以及其他诸多任务。 -第十一章討論了單元測試,Go語言的工具和標準庫中集成了輕量級的測試功能,避免了強大但複雜的測試框架。測試庫提供了一些基本構件,必要時可以用來構建複雜的測試構件。 +第十一章讨论了单元测试,Go语言的工具和标准库中集成了轻量级的测试功能,避免了强大但复杂的测试框架。测试库提供了一些基本构件,必要时可以用来构建复杂的测试构件。 -第十二章討論了反射,一種程序在運行期間審視自己的能力。反射是一個強大的編程工具,不過要謹慎地使用;這一章利用反射機製實現一些重要的Go語言庫函數, 展示了反射的強大用法。第十三章解釋了底層編程的細節,在必要時,可以使用unsafe包繞過Go語言安全的類型繫統。 +第十二章讨论了反射,一种程序在运行期间审视自己的能力。反射是一个强大的编程工具,不过要谨慎地使用;这一章利用反射机制实现一些重要的Go语言库函数, 展示了反射的强大用法。第十三章解释了底层编程的细节,在必要时,可以使用unsafe包绕过Go语言安全的类型系统。 -部分章節的後面有練習題,根據對Go語言的理解脩改書中的例子來探索Go語言的用法。 +部分章节的后面有练习题,根据对Go语言的理解修改书中的例子来探索Go语言的用法。 -書中所有的代碼都可以從 http://gopl.io 上的Git倉庫下載。go get命令根據每個例子的導入路徑智能地獲取、構建併安裝。隻需要選擇一個目録作爲工作空間,然後將GOPATH環境變量設置爲該路徑。 +书中所有的代码都可以从 http://gopl.io 上的Git仓库下载。go get命令根据每个例子的导入路径智能地获取、构建并安装。只需要选择一个目录作为工作空间,然后将GOPATH环境变量设置为该路径。 -必要時,Go語言工具會創建目録。例如: +必要时,Go语言工具会创建目录。例如: ``` -$ export GOPATH=$HOME/gobook # 選擇工作目録 -$ go get gopl.io/ch1/helloworld # 獲取/編譯/安裝 -$ $GOPATH/bin/helloworld # 運行程序 -Hello, 世界 # 這是中文 +$ export GOPATH=$HOME/gobook # 选择工作目录 +$ go get gopl.io/ch1/helloworld # 获取/编译/安装 +$ $GOPATH/bin/helloworld # 运行程序 +Hello, 世界 # 这是中文 ``` -運行這些例子需要安裝Go1.5以上的版本。 +运行这些例子需要安装Go1.5以上的版本。 ``` $ go version go version go1.5 linux/amd64 ``` -如果使用其他的操作繫統, 請參考 https://golang.org/doc/install 提供的説明安裝。 +如果使用其他的操作系统, 请参考 https://golang.org/doc/install 提供的说明安装。 diff --git a/ch0/ch0-04.md b/ch0/ch0-04.md index d403e51b..c349061b 100644 --- a/ch0/ch0-04.md +++ b/ch0/ch0-04.md @@ -1,14 +1,14 @@ ## 更多的信息 -最佳的幫助信息來自Go語言的官方網站,https://golang.org ,它提供了完善的參考文檔,包括編程語言規范和標準庫等諸多權威的幫助信息。同時也包含了如何編寫更地道的Go程序的基本敎程,還有各種各樣的在線文本資源和視頻資源,它們是本書最有價值的補充。Go語言的官方博客 https://blog.golang.org 會不定期發布一些Go語言最好的實踐文章,包括當前語言的發展狀態、未來的計劃、會議報告和Go語言相關的各種會議的主題等信息(譯註: http://talks.golang.org/ 包含了官方收録的各種報告的講稿)。 +最佳的帮助信息来自Go语言的官方网站,https://golang.org ,它提供了完善的参考文档,包括编程语言规范和标准库等诸多权威的帮助信息。同时也包含了如何编写更地道的Go程序的基本教程,还有各种各样的在线文本资源和视频资源,它们是本书最有价值的补充。Go语言的官方博客 https://blog.golang.org 会不定期发布一些Go语言最好的实践文章,包括当前语言的发展状态、未来的计划、会议报告和Go语言相关的各种会议的主题等信息(译注: http://talks.golang.org/ 包含了官方收录的各种报告的讲稿)。 -在線訪問的一個有價值的地方是可以從web頁面運行Go語言的程序(而紙質書則沒有這麽便利了)。這個功能由來自 https://play.golang.org 的 Go Playground 提供,併且可以方便地嵌入到其他頁面中,例如 https://golang.org 的主頁,或 godoc 提供的文檔頁面中。 +在线访问的一个有价值的地方是可以从web页面运行Go语言的程序(而纸质书则没有这么便利了)。这个功能由来自 https://play.golang.org 的 Go Playground 提供,并且可以方便地嵌入到其他页面中,例如 https://golang.org 的主页,或 godoc 提供的文档页面中。 -Playground可以簡單的通過執行一個小程序來測試對語法、語義和對程序庫的理解,類似其他很多語言提供的REPL卽時運行的工具。同時它可以生成對應的url,非常適合共享Go語言代碼片段,滙報bug或提供反饋意見等。 +Playground可以简单的通过执行一个小程序来测试对语法、语义和对程序库的理解,类似其他很多语言提供的REPL即时运行的工具。同时它可以生成对应的url,非常适合共享Go语言代码片段,汇报bug或提供反馈意见等。 -基於 Playground 構建的 Go Tour,https://tour.golang.org ,是一個繫列的Go語言入門敎程,它包含了諸多基本概念和結構相關的併可在線運行的互動小程序。 +基于 Playground 构建的 Go Tour,https://tour.golang.org ,是一个系列的Go语言入门教程,它包含了诸多基本概念和结构相关的并可在线运行的互动小程序。 -當然,Playground 和 Tour 也有一些限製,它們隻能導入標準庫,而且因爲安全的原因對一些網絡庫做了限製。如果要在編譯和運行時需要訪問互聯網,對於一些更複雜的實驗,你可能需要在自己的電腦上構建併運行程序。幸運的是下載Go語言的過程很簡單,從 https://golang.org 下載安裝包應該不超過幾分鐘(譯註:感謝偉大的長城,讓大陸的Gopher們都學會了自己打洞的基本生活技能,下載時間可能會因爲洞的大小等因素從幾分鐘到幾天或更久),然後就可以在自己電腦上編寫和運行Go程序了。 +当然,Playground 和 Tour 也有一些限制,它们只能导入标准库,而且因为安全的原因对一些网络库做了限制。如果要在编译和运行时需要访问互联网,对于一些更复杂的实验,你可能需要在自己的电脑上构建并运行程序。幸运的是下载Go语言的过程很简单,从 https://golang.org 下载安装包应该不超过几分钟(译注:感谢伟大的长城,让大陆的Gopher们都学会了自己打洞的基本生活技能,下载时间可能会因为洞的大小等因素从几分钟到几天或更久),然后就可以在自己电脑上编写和运行Go程序了。 -Go語言是一個開源項目,你可以在 https://golang.org/pkg 閲讀標準庫中任意函數和類型的實現代碼,和下載安裝包的代碼完全一致。這樣你可以知道很多函數是如何工作的, 通過挖掘找出一些答案的細節,或者僅僅是出於欣賞專業級Go代碼。 +Go语言是一个开源项目,你可以在 https://golang.org/pkg 阅读标准库中任意函数和类型的实现代码,和下载安装包的代码完全一致。这样你可以知道很多函数是如何工作的, 通过挖掘找出一些答案的细节,或者仅仅是出于欣赏专业级Go代码。 diff --git a/ch0/ch0-05.md b/ch0/ch0-05.md index 25dbcc7a..a30215b5 100644 --- a/ch0/ch0-05.md +++ b/ch0/ch0-05.md @@ -1,15 +1,15 @@ -## 致謝 +## 致谢 -[Rob Pike](http://genius.cat-v.org/rob-pike/)和[Russ Cox](http://research.swtch.com/),以及很多其他Go糰隊的核心成員多次仔細閲讀了本書的手稿,他們對本書的組織結構和表述用詞等給出了很多寶貴的建議。在準備日文版翻譯的時候,Yoshiki Shibata更是仔細地審閲了本書的每個部分,及時發現了諸多英文和代碼的錯誤。我們非常感謝本書的每一位審閲者,併感謝對本書給出了重要的建議的Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder和Peter Weinberger等人。 +[Rob Pike](http://genius.cat-v.org/rob-pike/)和[Russ Cox](http://research.swtch.com/),以及很多其他Go团队的核心成员多次仔细阅读了本书的手稿,他们对本书的组织结构和表述用词等给出了很多宝贵的建议。在准备日文版翻译的时候,Yoshiki Shibata更是仔细地审阅了本书的每个部分,及时发现了诸多英文和代码的错误。我们非常感谢本书的每一位审阅者,并感谢对本书给出了重要的建议的Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder和Peter Weinberger等人。 -我們還感謝Sameer Ajmani、Ittai Balaban、David Crawshaw、Billy Donohue、Jonathan Feinberg、Andrew Gerrand、Robert Griesemer、John Linderman、Minux Ma(譯註:中国人,Go糰隊成員。)、Bryan Mills、Bala Natarajan、Cosmos Nicolaou、Paul Staniforth、Nigel Tao(譯註:好像是陶哲軒的兄弟)以及Howard Trickey給出的許多有價值的建議。我們還要感謝David Brailsford和Raph Levien關於類型設置的建議。 +我们还感谢Sameer Ajmani、Ittai Balaban、David Crawshaw、Billy Donohue、Jonathan Feinberg、Andrew Gerrand、Robert Griesemer、John Linderman、Minux Ma(译注:中国人,Go团队成员。)、Bryan Mills、Bala Natarajan、Cosmos Nicolaou、Paul Staniforth、Nigel Tao(译注:好像是陶哲轩的兄弟)以及Howard Trickey给出的许多有价值的建议。我们还要感谢David Brailsford和Raph Levien关于类型设置的建议。 -我們的來自Addison-Wesley的編輯Greg Doench收到了很多幫助,從最開始就得到了越來越多的幫助。來自AW生産糰隊的John Fuller、Dayna Isley、Julie Nahil、Chuti Prasertsith到Barbara Wood,感謝你們的熱心幫助。 +我们的来自Addison-Wesley的编辑Greg Doench收到了很多帮助,从最开始就得到了越来越多的帮助。来自AW生产团队的John Fuller、Dayna Isley、Julie Nahil、Chuti Prasertsith到Barbara Wood,感谢你们的热心帮助。 -[Alan Donovan](https://github.com/adonovan)特别感謝:Sameer Ajmani、Chris Demetriou、Walt Drummond和Google公司的Reid Tatge允許他有充裕的時間去寫本書;感謝Stephen Donovan的建議和始終如一的鼓勵,以及他的妻子Leila Kazemi併沒有讓他爲了家庭瑣事而分心,併熱情堅定地支持這個項目。 +[Alan Donovan](https://github.com/adonovan)特别感谢:Sameer Ajmani、Chris Demetriou、Walt Drummond和Google公司的Reid Tatge允许他有充裕的时间去写本书;感谢Stephen Donovan的建议和始终如一的鼓励,以及他的妻子Leila Kazemi并没有让他为了家庭琐事而分心,并热情坚定地支持这个项目。 -[Brian Kernighan](http://www.cs.princeton.edu/~bwk/)特别感謝:朋友和同事對他的耐心和寬容,讓他慢慢地梳理本書的寫作思路。同時感謝他的妻子Meg和其他很多朋友對他寫作事業的支持。 +[Brian Kernighan](http://www.cs.princeton.edu/~bwk/)特别感谢:朋友和同事对他的耐心和宽容,让他慢慢地梳理本书的写作思路。同时感谢他的妻子Meg和其他很多朋友对他写作事业的支持。 -2015年 10月 於 紐約 +2015年 10月 于 纽约 diff --git a/ch1/ch1-01.md b/ch1/ch1-01.md index 5f6c8027..683beef1 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)[^1]。C語言是直接影響Go語言設計的語言之一。這個例子體現了Go語言一些核心理念。 +我们以现已成为传统的“hello world”案例来开始吧, 这个例子首次出现于1978年出版的C语言圣经[《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)[^1]。C语言是直接影响Go语言设计的语言之一。这个例子体现了Go语言一些核心理念。 gopl.io/ch1/helloworld ```go @@ -13,76 +13,76 @@ func main() { } ``` -Go是一門編譯型語言,Go語言的工具鏈將源代碼及其依賴轉換成計算機的機器指令[^2]。Go語言提供的工具都通過一個單獨的命令`go`調用,`go`命令有一繫列子命令。最簡單的一個子命令就是run。這個命令編譯一個或多個以.go結尾的源文件,鏈接庫文件,併運行最終生成的可執行文件。(本書使用$表示命令行提示符。) +Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令[^2]。Go语言提供的工具都通过一个单独的命令`go`调用,`go`命令有一系列子命令。最简单的一个子命令就是run。这个命令编译一个或多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。(本书使用$表示命令行提示符。) ``` $ go run helloworld.go ``` -毫無意外,這個命令會輸出: +毫无意外,这个命令会输出: ``` Hello, 世界 ``` -Go語言原生支持Unicode,它可以處理全世界任何語言的文本。 +Go语言原生支持Unicode,它可以处理全世界任何语言的文本。 -如果不隻是一次性實驗,你肯定希望能夠編譯這個程序,保存編譯結果以備將來之用。可以用build子命令: +如果不只是一次性实验,你肯定希望能够编译这个程序,保存编译结果以备将来之用。可以用build子命令: ``` $ go build helloworld.go ``` -這個命令生成一個名爲helloworld的可執行的二進製文件[^3],之後你可以隨時運行它[^4],不需任何處理[^5]。 +这个命令生成一个名为helloworld的可执行的二进制文件[^3],之后你可以随时运行它[^4],不需任何处理[^5]。 ``` $ ./helloworld Hello, 世界 ``` -本書中, 所有的示例代碼上都有一行標記,利用這些標記, 可以從[gopl.io](http://gopl.io)網站上本書源碼倉庫里獲取代碼: +本书中, 所有的示例代码上都有一行标记,利用这些标记, 可以从[gopl.io](http://gopl.io)网站上本书源码仓库里获取代码: ``` gopl.io/ch1/helloworld ``` -執行 `go get gopl.io/ch1/helloworld` 命令,就會從網上獲取代碼,併放到對應目録中[^6]。2.6和10.7節有這方面更詳細的介紹。 +执行 `go get gopl.io/ch1/helloworld` 命令,就会从网上获取代码,并放到对应目录中[^6]。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`是其中一個基礎函數,可以打印以空格間隔的一個或多個值,併在最後添加一個換行符,從而輸出一整行。 +Go的标准库提供了100多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如`fmt`包,就含有格式化输出、接收输入的函数。`Println`是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。 -`main`包比較特殊。它定義了一個獨立可執行的程序,而不是一個庫。在`main`里的`main` *函數* 也很特殊,它是整個程序執行時的入口[^7]。`main`函數所做的事情就是程序做的。當然了,`main`函數一般調用其它包里的函數完成很多工作, 比如`fmt.Println`。 +`main`包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在`main`里的`main` *函数* 也很特殊,它是整个程序执行时的入口[^7]。`main`函数所做的事情就是程序做的。当然了,`main`函数一般调用其它包里的函数完成很多工作, 比如`fmt.Println`。 -必須告訴編譯器源文件需要哪些包,這就是`import`聲明以及隨後的`package`聲明扮演的角色。hello world例子隻用到了一個包,大多數程序需要導入多個包。 +必须告诉编译器源文件需要哪些包,这就是`import`声明以及随后的`package`声明扮演的角色。hello world例子只用到了一个包,大多数程序需要导入多个包。 -必須恰當導入需要的包,缺少了必要的包或者導入了不需要的包,程序都無法編譯通過。這項嚴格要求避免了程序開發過程中引入未使用的包[^8]。 +必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包[^8]。 -`import`聲明必須跟在文件的`package`聲明之後。隨後,則是組成程序的函數、變量、常量、類型的聲明語句(分别由關鍵字`func`, `var`, `const`, `type`定義)。這些內容的聲明順序併不重要[^9]。這個例子的程序已經盡可能短了,隻聲明了一個函數, 其中隻調用了一個其他函數。爲了節省篇幅,有些時候, 示例程序會省略`package`和`import`聲明,但是,這些聲明在源代碼里有,併且必須得有才能編譯。 +`import`声明必须跟在文件的`package`声明之后。随后,则是组成程序的函数、变量、常量、类型的声明语句(分别由关键字`func`, `var`, `const`, `type`定义)。这些内容的声明顺序并不重要[^9]。这个例子的程序已经尽可能短了,只声明了一个函数, 其中只调用了一个其他函数。为了节省篇幅,有些时候, 示例程序会省略`package`和`import`声明,但是,这些声明在源代码里有,并且必须得有才能编译。 -一個函數的聲明由`func`關鍵字、函數名、參數列表、返迴值列表(這個例子里的`main`函數參數列表和返迴值都是空的)以及包含在大括號里的函數體組成。第五章進一步考察函數。 +一个函数的声明由`func`关键字、函数名、参数列表、返回值列表(这个例子里的`main`函数参数列表和返回值都是空的)以及包含在大括号里的函数体组成。第五章进一步考察函数。 -Go語言不需要在語句或者聲明的末尾添加分號,除非一行上有多條語句。實際上,編譯器會主動把特定符號後的換行符轉換爲分號, 因此換行符添加的位置會影響Go代碼的正確解析[^10]。。舉個例子, 函數的左括號`{`必須和`func`函數聲明在同一行上, 且位於末尾,不能獨占一行,而在表達式`x + y`中,可在`+`後換行,不能在`+`前換行。 +Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号, 因此换行符添加的位置会影响Go代码的正确解析[^10]。。举个例子, 函数的左括号`{`必须和`func`函数声明在同一行上, 且位于末尾,不能独占一行,而在表达式`x + y`中,可在`+`后换行,不能在`+`前换行。 -Go語言在代碼格式上采取了很強硬的態度。`gofmt`工具把代碼格式化爲標準格式[^12],併且`go`工具中的`fmt`子命令會對指定包, 否則默認爲當前目録, 中所有.go源文件應用`gofmt`命令。本書中的所有代碼都被gofmt過。你也應該養成格式化自己的代碼的習慣。以法令方式規定標準的代碼格式可以避免無盡的無意義的瑣碎爭執[^13]。更重要的是,這樣可以做多種自動源碼轉換,如果放任Go語言代碼格式,這些轉換就不大可能了。 +Go语言在代码格式上采取了很强硬的态度。`gofmt`工具把代码格式化为标准格式[^12],并且`go`工具中的`fmt`子命令会对指定包, 否则默认为当前目录, 中所有.go源文件应用`gofmt`命令。本书中的所有代码都被gofmt过。你也应该养成格式化自己的代码的习惯。以法令方式规定标准的代码格式可以避免无尽的无意义的琐碎争执[^13]。更重要的是,这样可以做多种自动源码转换,如果放任Go语言代码格式,这些转换就不大可能了。 -很多文本編輯器都可以配置爲保存文件時自動執行`gofmt`,這樣你的源代碼總會被恰當地格式化。還有個相關的工具,`goimports`,可以根據代碼需要, 自動地添加或刪除`import`聲明。這個工具併沒有包含在標準的分發包中,可以用下面的命令安裝: +很多文本编辑器都可以配置为保存文件时自动执行`gofmt`,这样你的源代码总会被恰当地格式化。还有个相关的工具,`goimports`,可以根据代码需要, 自动地添加或删除`import`声明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装: ``` $ go get golang.org/x/tools/cmd/goimports ``` -對於大多數用戶來説,下載、編譯包、運行測試用例、察看Go語言的文檔等等常用功能都可以用go的工具完成。10.7節詳細介紹這些知識。 - -[^1]: 本書作者之一Brian W. Kernighan也是《The C Programming Language》一書的作者。 -[^2]: 靜態編譯。 -[^3]: Windows繫統下生成的可執行文件是helloworld.exe,增加了.exe後綴名。 -[^4]: 在Windows繫統下在命令行直接輸入helloworld.exe命令運行。 -[^5]: 因爲靜態編譯,所以不用擔心在繫統庫更新的時候衝突,幸福感滿滿。 -[^6]: 需要先安裝Git或Hg之類的版本管理工具,併將對應的命令添加到PATH環境變量中。序言已經提及,需要先設置好GOPATH環境變量,下載的代碼會放在`$GOPATH/src/gopl.io/ch1/helloworld`目録。 -[^7]: C繫語言差不多都這樣。 -[^8]: Go語言編譯過程沒有警告信息,爭議特性之一。 -[^9]: 最好還是定一下規范。 -[^10]: 比如行末是標識符、整數、浮點數、虛數、字符或字符串文字、關鍵字`break`、`continue`、`fallthrough`或`return`中的一個、運算符和分隔符`++`、`--`、`)`、`]`或`}`中的一個。 -[^11]: 以+結尾的話不會被插入分號分隔符,但是以x結尾的話則會被分號分隔符,從而導致編譯錯誤。 -[^12]: 這個格式化工具沒有任何可以調整代碼格式的參數,Go語言就是這麽任性。 -[^13]: 也導致了Go語言的TIOBE排名較低,因爲缺少撕逼的話題。 +对于大多数用户来说,下载、编译包、运行测试用例、察看Go语言的文档等等常用功能都可以用go的工具完成。10.7节详细介绍这些知识。 + +[^1]: 本书作者之一Brian W. Kernighan也是《The C Programming Language》一书的作者。 +[^2]: 静态编译。 +[^3]: Windows系统下生成的可执行文件是helloworld.exe,增加了.exe后缀名。 +[^4]: 在Windows系统下在命令行直接输入helloworld.exe命令运行。 +[^5]: 因为静态编译,所以不用担心在系统库更新的时候冲突,幸福感满满。 +[^6]: 需要先安装Git或Hg之类的版本管理工具,并将对应的命令添加到PATH环境变量中。序言已经提及,需要先设置好GOPATH环境变量,下载的代码会放在`$GOPATH/src/gopl.io/ch1/helloworld`目录。 +[^7]: C系语言差不多都这样。 +[^8]: Go语言编译过程没有警告信息,争议特性之一。 +[^9]: 最好还是定一下规范。 +[^10]: 比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字`break`、`continue`、`fallthrough`或`return`中的一个、运算符和分隔符`++`、`--`、`)`、`]`或`}`中的一个。 +[^11]: 以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误。 +[^12]: 这个格式化工具没有任何可以调整代码格式的参数,Go语言就是这么任性。 +[^13]: 也导致了Go语言的TIOBE排名较低,因为缺少撕逼的话题。 diff --git a/ch1/ch1-02.md b/ch1/ch1-02.md index b5282bec..d53f36d2 100644 --- a/ch1/ch1-02.md +++ b/ch1/ch1-02.md @@ -1,14 +1,14 @@ -## 1.2. 命令行參數 +## 1.2. 命令行参数 -大多數的程序都是處理輸入,産生輸出;這也正是“計算”的定義。但是, 程序如何獲取要處理的輸入數據呢?一些程序生成自己的數據,但通常情況下,輸入來自於程序外部:文件、網絡連接、其它程序的輸出、敲鍵盤的用戶、命令行參數或其它類似輸入源。下面幾個例子會討論其中幾個輸入源,首先是命令行參數。 +大多数的程序都是处理输入,产生输出;这也正是“计算”的定义。但是, 程序如何获取要处理的输入数据呢?一些程序生成自己的数据,但通常情况下,输入来自于程序外部:文件、网络连接、其它程序的输出、敲键盘的用户、命令行参数或其它类似输入源。下面几个例子会讨论其中几个输入源,首先是命令行参数。 -`os`包以跨平台的方式,提供了一些與操作繫統交互的函數和變量。程序的命令行參數可從os包的Args變量獲取;os包外部使用os.Args訪問該變量。 +`os`包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。 -os.Args變量是一個字符串(string)的*切片*(slice)(譯註:slice和Python語言中的切片類似,是一個簡版的動態數組),切片是Go語言的基礎概念,稍後詳細介紹。現在先把切片s當作數組元素序列, 序列的成長度動態變化, 用`s[i]`訪問單個元素,用`s[m:n]`獲取子序列(譯註:和python里的語法差不多)。序列的元素數目爲len(s)。和大多數編程語言類似,區間索引時,Go言里也采用左閉右開形式, 卽,區間包括第一個索引元素,不包括最後一個, 因爲這樣可以簡化邏輯。(譯註:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最後一個元素)。比如s[m:n]這個切片,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。 +os.Args变量是一个字符串(string)的*切片*(slice)(译注:slice和Python语言中的切片类似,是一个简版的动态数组),切片是Go语言的基础概念,稍后详细介绍。现在先把切片s当作数组元素序列, 序列的成长度动态变化, 用`s[i]`访问单个元素,用`s[m:n]`获取子序列(译注:和python里的语法差不多)。序列的元素数目为len(s)。和大多数编程语言类似,区间索引时,Go言里也采用左闭右开形式, 即,区间包括第一个索引元素,不包括最后一个, 因为这样可以简化逻辑。(译注:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最后一个元素)。比如s[m:n]这个切片,0 ≤ m ≤ n ≤ len(s),包含n-m个元素。 -os.Args的第一個元素,os.Args[0], 是命令本身的名字;其它的元素則是程序啟動時傳給它的參數。s[m:n]形式的切片表達式,産生從第m個元素到第n-1個元素的切片,下個例子用到的元素包含在os.Args[1:len(os.Args)]切片中。如果省略切片表達式的m或n,會默認傳入0或len(s),因此前面的切片可以簡寫成os.Args[1:]。 +os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数。s[m:n]形式的切片表达式,产生从第m个元素到第n-1个元素的切片,下个例子用到的元素包含在os.Args[1:len(os.Args)]切片中。如果省略切片表达式的m或n,会默认传入0或len(s),因此前面的切片可以简写成os.Args[1:]。 -下面是Unix里echo命令的一份實現,echo把它的命令行參數打印成一行。程序導入了兩個包,用括號把它們括起來寫成列表形式, 而沒有分開寫成獨立的`import`聲明。兩種形式都合法,列表形式習慣上用得多。包導入順序併不重要;gofmt工具格式化時按照字母順序對包名排序。(示例有多個版本時,我們會對示例編號, 這樣可以明確當前正在討論的是哪個。) +下面是Unix里echo命令的一份实现,echo把它的命令行参数打印成一行。程序导入了两个包,用括号把它们括起来写成列表形式, 而没有分开写成独立的`import`声明。两种形式都合法,列表形式习惯上用得多。包导入顺序并不重要;gofmt工具格式化时按照字母顺序对包名排序。(示例有多个版本时,我们会对示例编号, 这样可以明确当前正在讨论的是哪个。) gopl.io/ch1/echo1 ```go @@ -30,37 +30,37 @@ func main() { } ``` -註釋語句以`//`開頭。對於程序員來説,//之後到行末之間所有的內容都是註釋,被編譯器忽略。按照慣例,我們在每個包的包聲明前添加註釋;對於`main package`,註釋包含一句或幾句話,從整體角度對程序做個描述。 +注释语句以`//`开头。对于程序员来说,//之后到行末之间所有的内容都是注释,被编译器忽略。按照惯例,我们在每个包的包声明前添加注释;对于`main package`,注释包含一句或几句话,从整体角度对程序做个描述。 -var聲明定義了兩個string類型的變量s和sep。變量會在聲明時直接初始化。如果變量沒有顯式初始化,則被隱式地賦予其類型的*零值*(zero value),數值類型是0,字符串類型是空字符串""。這個例子里,聲明把s和sep隱式地初始化成空字符串。第2章再來詳細地講解變量和聲明。 +var声明定义了两个string类型的变量s和sep。变量会在声明时直接初始化。如果变量没有显式初始化,则被隐式地赋予其类型的*零值*(zero value),数值类型是0,字符串类型是空字符串""。这个例子里,声明把s和sep隐式地初始化成空字符串。第2章再来详细地讲解变量和声明。 -對數值類型,Go語言提供了常規的數值和邏輯運算符。而對string類型,`+`運算符連接字符串(譯註:和C++或者js是一樣的)。所以表達式: +对数值类型,Go语言提供了常规的数值和逻辑运算符。而对string类型,`+`运算符连接字符串(译注:和C++或者js是一样的)。所以表达式: ```go sep + os.Args[i] ``` -表示連接字符串sep和os.Args。程序中使用的語句: +表示连接字符串sep和os.Args。程序中使用的语句: ```go s += sep + os.Args[i] ``` -是一條*賦值語句*, 將s的舊值跟sep與os.Args[i]連接後賦值迴s,等價於: +是一条*赋值语句*, 将s的旧值跟sep与os.Args[i]连接后赋值回s,等价于: ```go s = s + sep + os.Args[i] ``` -運算符`+=`是賦值運算符(assignment operator),每種數值運算符或邏輯運算符,如`+`或`*`,都有對應的賦值運算符。 +运算符`+=`是赋值运算符(assignment operator),每种数值运算符或逻辑运算符,如`+`或`*`,都有对应的赋值运算符。 -echo程序可以每循環一次輸出一個參數,這個版本卻是不斷地把新文本追加到末尾來構造字符串。字符串s開始爲空,卽值爲"",每次循環會添加一些文本;第一次迭代之後,還會再插入一個空格,因此循環結束時每個參數中間都有一個空格。這是一種二次加工(quadratic process),當參數數量龐大時,開銷很大,但是對於echo,這種情形不大可能出現。本章會介紹echo的若榦改進版,下一章解決低效問題。 +echo程序可以每循环一次输出一个参数,这个版本却是不断地把新文本追加到末尾来构造字符串。字符串s开始为空,即值为"",每次循环会添加一些文本;第一次迭代之后,还会再插入一个空格,因此循环结束时每个参数中间都有一个空格。这是一种二次加工(quadratic process),当参数数量庞大时,开销很大,但是对于echo,这种情形不大可能出现。本章会介绍echo的若干改进版,下一章解决低效问题。 -循環索引變量i在for循環的第一部分中定義。符號`:=`是*短變量聲明*(short variable declaration)的一部分, 這是定義一個或多個變量併根據它們的初始值爲這些變量賦予適當類型的語句。下一章有這方面更多説明。 +循环索引变量i在for循环的第一部分中定义。符号`:=`是*短变量声明*(short variable declaration)的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。下一章有这方面更多说明。 -自增語句`i++`給`i`加1;這和`i += 1`以及`i = i + 1`都是等價的。對應的還有`i--`給`i`減1。它們是語句,而不像C繫的其它語言那樣是表達式。所以`j = i++`非法,而且++和--都隻能放在變量名後面,因此`--i`也非法。 +自增语句`i++`给`i`加1;这和`i += 1`以及`i = i + 1`都是等价的。对应的还有`i--`给`i`减1。它们是语句,而不像C系的其它语言那样是表达式。所以`j = i++`非法,而且++和--都只能放在变量名后面,因此`--i`也非法。 -Go語言隻有for循環這一種循環語句。for循環有多種形式,其中一種如下所示: +Go语言只有for循环这一种循环语句。for循环有多种形式,其中一种如下所示: ```go for initialization; condition; post { @@ -68,11 +68,11 @@ for initialization; condition; post { } ``` -for循環三個部分不需括號包圍。大括號強製要求, 左大括號必須和*post*語句在同一行。 +for循环三个部分不需括号包围。大括号强制要求, 左大括号必须和*post*语句在同一行。 -*initialization*語句是可選的,在循環開始前執行。*initalization*如果存在,必須是一條*簡單語句*(simple statement),卽,短變量聲明、自增語句、賦值語句或函數調用。`condition`是一個布爾表達式(boolean expression),其值在每次循環迭代開始時計算。如果爲`true`則執行循環體語句。`post`語句在循環體執行結束後執行,之後再次對`conditon`求值。`condition`值爲`false`時,循環結束。 +*initialization*语句是可选的,在循环开始前执行。*initalization*如果存在,必须是一条*简单语句*(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。`condition`是一个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为`true`则执行循环体语句。`post`语句在循环体执行结束后执行,之后再次对`conditon`求值。`condition`值为`false`时,循环结束。 -for循環的這三個部分每個都可以省略,如果省略`initialization`和`post`,分號也可以省略: +for循环的这三个部分每个都可以省略,如果省略`initialization`和`post`,分号也可以省略: ```go // a traditional "while" loop @@ -81,7 +81,7 @@ for condition { } ``` -如果連`condition`也省略了,像下面這樣: +如果连`condition`也省略了,像下面这样: ```go // a traditional infinite loop @@ -90,9 +90,9 @@ for { } ``` -這就變成一個無限循環,盡管如此,還可以用其他方式終止循環, 如一條`break`或`return`語句。 +这就变成一个无限循环,尽管如此,还可以用其他方式终止循环, 如一条`break`或`return`语句。 -`for`循環的另一種形式, 在某種數據類型的區間(range)上遍歷,如字符串或切片。`echo`的第二版本展示了這種形式: +`for`循环的另一种形式, 在某种数据类型的区间(range)上遍历,如字符串或切片。`echo`的第二版本展示了这种形式: gopl.io/ch1/echo2 ```go @@ -113,11 +113,11 @@ func main() { } ``` -每次循環迭代,`range`産生一對值;索引以及在該索引處的元素值。這個例子不需要索引,但`range`的語法要求, 要處理元素, 必須處理索引。一種思路是把索引賦值給一個臨時變量, 如`temp`, 然後忽略它的值,但Go語言不允許使用無用的局部變量(local variables),因爲這會導致編譯錯誤。 +每次循环迭代,`range`产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但`range`的语法要求, 要处理元素, 必须处理索引。一种思路是把索引赋值给一个临时变量, 如`temp`, 然后忽略它的值,但Go语言不允许使用无用的局部变量(local variables),因为这会导致编译错误。 -Go語言中這種情況的解決方法是用`空標識符`(blank identifier),卽`_`(也就是下劃線)。空標識符可用於任何語法需要變量名但程序邏輯不需要的時候, 例如, 在循環里,丟棄不需要的循環索引, 保留元素值。大多數的Go程序員都會像上面這樣使用`range`和`_`寫`echo`程序,因爲隱式地而非顯示地索引os.Args,容易寫對。 +Go语言中这种情况的解决方法是用`空标识符`(blank identifier),即`_`(也就是下划线)。空标识符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。大多数的Go程序员都会像上面这样使用`range`和`_`写`echo`程序,因为隐式地而非显示地索引os.Args,容易写对。 -`echo`的這個版本使用一條短變量聲明來聲明併初始化`s`和`seps`,也可以將這兩個變量分開聲明,聲明一個變量有好幾種方式,下面這些都等價: +`echo`的这个版本使用一条短变量声明来声明并初始化`s`和`seps`,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等价: ```go s := "" @@ -126,11 +126,11 @@ var s = "" var s string = "" ``` -用哪種不用哪種,爲什麽呢?第一種形式,是一條短變量聲明,最簡潔,但隻能用在函數內部,而不能用於包變量。第二種形式依賴於字符串的默認初始化零值機製,被初始化爲""。第三種形式用得很少,除非同時聲明多個變量。第四種形式顯式地標明變量的類型,當變量類型與初值類型相同時,類型冗餘,但如果兩者類型不同,變量類型就必須了。實踐中一般使用前兩種形式中的某個,初始值重要的話就顯式地指定變量的類型,否則使用隱式初始化。 +用哪种不用哪种,为什么呢?第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。 -如前文所述,每次循環迭代字符串s的內容都會更新。`+=`連接原字符串、空格和下個參數,産生新字符串, 併把它賦值給`s`。`s`原來的內容已經不再使用,將在適當時機對它進行垃圾迴收。 +如前文所述,每次循环迭代字符串s的内容都会更新。`+=`连接原字符串、空格和下个参数,产生新字符串, 并把它赋值给`s`。`s`原来的内容已经不再使用,将在适当时机对它进行垃圾回收。 -如果連接涉及的數據量很大,這種方式代價高昂。一種簡單且高效的解決方案是使用`strings`包的`Join`函數: +如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用`strings`包的`Join`函数: gopl.io/ch1/echo3 ```go @@ -139,16 +139,16 @@ func main() { } ``` -最後,如果不關心輸出格式,隻想看看輸出值,或許隻是爲了調試,可以用`Println`爲我們格式化輸出。 +最后,如果不关心输出格式,只想看看输出值,或许只是为了调试,可以用`Println`为我们格式化输出。 ```go fmt.Println(os.Args[1:]) ``` -這條語句的輸出結果跟`strings.Join`得到的結果很像,隻是被放到了一對方括號里。切片都會被打印成這種格式。 +这条语句的输出结果跟`strings.Join`得到的结果很像,只是被放到了一对方括号里。切片都会被打印成这种格式。 -**練習 1.1:** 脩改`echo`程序,使其能夠打印`os.Args[0]`,卽被執行命令本身的名字。 +**练习 1.1:** 修改`echo`程序,使其能够打印`os.Args[0]`,即被执行命令本身的名字。 -**練習 1.2:** 脩改`echo`程序,使其打印每個參數的索引和值,每個一行。 +**练习 1.2:** 修改`echo`程序,使其打印每个参数的索引和值,每个一行。 -**練習 1.3:** 做實驗測量潛在低效的版本和使用了`strings.Join`的版本的運行時間差異。(1.6節講解了部分`time`包,11.4節展示了如何寫標準測試程序,以得到繫統性的性能評測。) +**练习 1.3:** 做实验测量潜在低效的版本和使用了`strings.Join`的版本的运行时间差异。(1.6节讲解了部分`time`包,11.4节展示了如何写标准测试程序,以得到系统性的性能评测。) diff --git a/ch1/ch1-03.md b/ch1/ch1-03.md index b7c3bb74..b5ead742 100644 --- a/ch1/ch1-03.md +++ b/ch1/ch1-03.md @@ -1,8 +1,8 @@ -## 1.3. 査找重複的行 +## 1.3. 查找重复的行 -對文件做拷貝、打印、蒐索、排序、統計或類似事情的程序都有一個差不多的程序結構:一個處理輸入的循環,在每個元素上執行計算處理,在處理的同時或最後産生輸出。我們會展示一個名爲`dup`的程序的三個版本;靈感來自於Unix的`uniq`命令,其尋找相鄰的重複行。該程序使用的結構和包是個參考范例,可以方便地脩改。 +对文件做拷贝、打印、搜索、排序、统计或类似事情的程序都有一个差不多的程序结构:一个处理输入的循环,在每个元素上执行计算处理,在处理的同时或最后产生输出。我们会展示一个名为`dup`的程序的三个版本;灵感来自于Unix的`uniq`命令,其寻找相邻的重复行。该程序使用的结构和包是个参考范例,可以方便地修改。 -`dup`的第一個版本打印標準輸入中多次出現的行,以重複次數開頭。該程序將引入`if`語句,`map`數據類型以及`bufio`包。 +`dup`的第一个版本打印标准输入中多次出现的行,以重复次数开头。该程序将引入`if`语句,`map`数据类型以及`bufio`包。 gopl.io/ch1/dup1 ```go @@ -31,53 +31,53 @@ func main() { } ``` -正如`for`循環一樣,`if`語句條件兩邊也不加括號,但是主體部分需要加。`if`語句的`else`部分是可選的,在`if`的條件爲`false`時執行。 +正如`for`循环一样,`if`语句条件两边也不加括号,但是主体部分需要加。`if`语句的`else`部分是可选的,在`if`的条件为`false`时执行。 -**map**存儲了鍵/值(key/value)的集合,對集合元素,提供常數時間的存、取或測試操作。鍵可以是任意類型,隻要其值能用`==`運算符比較,最常見的例子是字符串;值則可以是任意類型。這個例子中的鍵是字符串,值是整數。內置函數`make`創建空`map`,此外,它還有别的作用。4.3節討論`map`。 +**map**存储了键/值(key/value)的集合,对集合元素,提供常数时间的存、取或测试操作。键可以是任意类型,只要其值能用`==`运算符比较,最常见的例子是字符串;值则可以是任意类型。这个例子中的键是字符串,值是整数。内置函数`make`创建空`map`,此外,它还有别的作用。4.3节讨论`map`。 -(譯註:從功能和實現上説,`Go`的`map`類似於`Java`語言中的`HashMap`,Python語言中的`dict`,`Lua`語言中的`table`,通常使用`hash`實現。遺憾的是,對於該詞的翻譯併不統一,數學界術語爲`映射`,而計算機界衆説紛紜莫衷一是。爲了防止對讀者造成誤解,保留不譯。) +(译注:从功能和实现上说,`Go`的`map`类似于`Java`语言中的`HashMap`,Python语言中的`dict`,`Lua`语言中的`table`,通常使用`hash`实现。遗憾的是,对于该词的翻译并不统一,数学界术语为`映射`,而计算机界众说纷纭莫衷一是。为了防止对读者造成误解,保留不译。) -每次`dup`讀取一行輸入,該行被當做`map`,其對應的值遞增。`counts[input.Text()]++`語句等價下面兩句: +每次`dup`读取一行输入,该行被当做`map`,其对应的值递增。`counts[input.Text()]++`语句等价下面两句: ```go line := input.Text() counts[line] = counts[line] + 1 ``` -`map`中不含某個鍵時不用擔心,首次讀到新行時,等號右邊的表達式`counts[line]`的值將被計算爲其類型的零值,對於int`卽0。 +`map`中不含某个键时不用担心,首次读到新行时,等号右边的表达式`counts[line]`的值将被计算为其类型的零值,对于int`即0。 -爲了打印結果,我們使用了基於`range`的循環,併在`counts`這個`map`上迭代。跟之前類似,每次迭代得到兩個結果,鍵和其在`map`中對應的值。`map`的迭代順序併不確定,從實踐來看,該順序隨機,每次運行都會變化。這種設計是有意爲之的,因爲能防止程序依賴特定遍歷順序,而這是無法保證的。 +为了打印结果,我们使用了基于`range`的循环,并在`counts`这个`map`上迭代。跟之前类似,每次迭代得到两个结果,键和其在`map`中对应的值。`map`的迭代顺序并不确定,从实践来看,该顺序随机,每次运行都会变化。这种设计是有意为之的,因为能防止程序依赖特定遍历顺序,而这是无法保证的。 -繼續來看`bufio`包,它使處理輸入和輸出方便又高效。`Scanner`類型是該包最有用的特性之一,它讀取輸入併將其拆成行或單詞;通常是處理行形式的輸入最簡單的方法。 +继续来看`bufio`包,它使处理输入和输出方便又高效。`Scanner`类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。 -程序使用短變量聲明創建`bufio.Scanner`類型的變量`input`。 +程序使用短变量声明创建`bufio.Scanner`类型的变量`input`。 ``` input := bufio.NewScanner(os.Stdin) ``` -該變量從程序的標準輸入中讀取內容。每次調用`input.Scanner`,卽讀入下一行,併移除行末的換行符;讀取的內容可以調用`input.Text()`得到。`Scan`函數在讀到一行時返迴`true`,在無輸入時返迴`false`。 +该变量从程序的标准输入中读取内容。每次调用`input.Scanner`,即读入下一行,并移除行末的换行符;读取的内容可以调用`input.Text()`得到。`Scan`函数在读到一行时返回`true`,在无输入时返回`false`。 -類似於C或其它語言里的`printf`函數,`fmt.Printf`函數對一些表達式産生格式化輸出。該函數的首個參數是個格式字符串,指定後續參數被如何格式化。各個參數的格式取決於“轉換字符”(conversion character),形式爲百分號後跟一個字母。舉個例子,`%d`表示以十進製形式打印一個整型操作數,而`%s`則表示把字符串型操作數的值展開。 +类似于C或其它语言里的`printf`函数,`fmt.Printf`函数对一些表达式产生格式化输出。该函数的首个参数是个格式字符串,指定后续参数被如何格式化。各个参数的格式取决于“转换字符”(conversion character),形式为百分号后跟一个字母。举个例子,`%d`表示以十进制形式打印一个整型操作数,而`%s`则表示把字符串型操作数的值展开。 -`Printf`有一大堆這種轉換,Go程序員稱之爲*動詞(verb)*。下面的表格雖然遠不是完整的規范,但展示了可用的很多特性: +`Printf`有一大堆这种转换,Go程序员称之为*动词(verb)*。下面的表格虽然远不是完整的规范,但展示了可用的很多特性: ``` -%d 十進製整數 -%x, %o, %b 十六進製,八進製,二進製整數。 -%f, %g, %e 浮點數: 3.141593 3.141592653589793 3.141593e+00 -%t 布爾:true或false -%c 字符(rune) (Unicode碼點) +%d 十进制整数 +%x, %o, %b 十六进制,八进制,二进制整数。 +%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00 +%t 布尔:true或false +%c 字符(rune) (Unicode码点) %s 字符串 -%q 帶雙引號的字符串"abc"或帶單引號的字符'c' -%v 變量的自然形式(natural format) -%T 變量的類型 -%% 字面上的百分號標誌(無操作數) +%q 带双引号的字符串"abc"或带单引号的字符'c' +%v 变量的自然形式(natural format) +%T 变量的类型 +%% 字面上的百分号标志(无操作数) ``` -`dup1`的格式字符串中還含有製表符`\t`和換行符`\n`。字符串字面上可能含有這些代表不可見字符的**轉義字符(escap sequences)**。默認情況下,`Printf`不會換行。按照慣例,以字母`f`結尾的格式化函數,如`log.Printf`和`fmt.Errorf`,都采用`fmt.Printf`的格式化準則。而以`ln`結尾的格式化函數,則遵循`Println`的方式,以跟`%v`差不多的方式格式化參數,併在最後添加一個換行符。(譯註:後綴`f`指`fomart`,`ln`指`line`。) +`dup1`的格式字符串中还含有制表符`\t`和换行符`\n`。字符串字面上可能含有这些代表不可见字符的**转义字符(escap sequences)**。默认情况下,`Printf`不会换行。按照惯例,以字母`f`结尾的格式化函数,如`log.Printf`和`fmt.Errorf`,都采用`fmt.Printf`的格式化准则。而以`ln`结尾的格式化函数,则遵循`Println`的方式,以跟`%v`差不多的方式格式化参数,并在最后添加一个换行符。(译注:后缀`f`指`fomart`,`ln`指`line`。) -很多程序要麽從標準輸入中讀取數據,如上面的例子所示,要麽從一繫列具名文件中讀取數據。`dup`程序的下個版本讀取標準輸入或是使用`os.Open`打開各個具名文件,併操作它們。 +很多程序要么从标准输入中读取数据,如上面的例子所示,要么从一系列具名文件中读取数据。`dup`程序的下个版本读取标准输入或是使用`os.Open`打开各个具名文件,并操作它们。 gopl.io/ch1/dup2 ```go @@ -123,19 +123,19 @@ func countLines(f *os.File, counts map[string]int) { } ``` -`os.Open`函數返迴兩個值。第一個值是被打開的文件(`*os.File`),其後被`Scanner`讀取。 +`os.Open`函数返回两个值。第一个值是被打开的文件(`*os.File`),其后被`Scanner`读取。 -`os.Open`返迴的第二個值是內置`error`類型的值。如果`err`等於內置值`nil`(譯註:相當於其它語言里的NULL),那麽文件被成功打開。讀取文件,直到文件結束,然後調用`Close`關閉該文件,併釋放占用的所有資源。相反的話,如果`err`的值不是`nil`,説明打開文件時出錯了。這種情況下,錯誤值描述了所遇到的問題。我們的錯誤處理非常簡單,隻是使用`Fprintf`與表示任意類型默認格式值的動詞`%v`,向標準錯誤流打印一條信息,然後`dup`繼續處理下一個文件;`continue`語句直接跳到`for`循環的下個迭代開始執行。 +`os.Open`返回的第二个值是内置`error`类型的值。如果`err`等于内置值`nil`(译注:相当于其它语言里的NULL),那么文件被成功打开。读取文件,直到文件结束,然后调用`Close`关闭该文件,并释放占用的所有资源。相反的话,如果`err`的值不是`nil`,说明打开文件时出错了。这种情况下,错误值描述了所遇到的问题。我们的错误处理非常简单,只是使用`Fprintf`与表示任意类型默认格式值的动词`%v`,向标准错误流打印一条信息,然后`dup`继续处理下一个文件;`continue`语句直接跳到`for`循环的下个迭代开始执行。 -爲了使示例代碼保持合理的大小,本書開始的一些示例有意簡化了錯誤處理,顯而易見的是,應該檢査`os.Open`返迴的錯誤值,然而,使用`input.Scan`讀取文件過程中,不大可能出現錯誤,因此我們忽略了錯誤處理。我們會在跳過錯誤檢査的地方做説明。5.4節中深入介紹錯誤處理。 +为了使示例代码保持合理的大小,本书开始的一些示例有意简化了错误处理,显而易见的是,应该检查`os.Open`返回的错误值,然而,使用`input.Scan`读取文件过程中,不大可能出现错误,因此我们忽略了错误处理。我们会在跳过错误检查的地方做说明。5.4节中深入介绍错误处理。 -註意`countLines`函數在其聲明前被調用。函數和包級别的變量(package-level entities)可以任意順序聲明,併不影響其被調用。(譯註:最好還是遵循一定的規范) +注意`countLines`函数在其声明前被调用。函数和包级别的变量(package-level entities)可以任意顺序声明,并不影响其被调用。(译注:最好还是遵循一定的规范) -`map`是一個由`make`函數創建的數據結構的引用。`map`作爲爲參數傳遞給某函數時,該函數接收這個引用的一份拷貝(copy,或譯爲副本),被調用函數對`map`底層數據結構的任何脩改,調用者函數都可以通過持有的`map`引用看到。在我們的例子中,`countLines`函數向`counts`插入的值,也會被`main`函數看到。(譯註:類似於C++里的引用傳遞,實際上指針是另一個指針了,但內部存的值指向同一塊內存) +`map`是一个由`make`函数创建的数据结构的引用。`map`作为为参数传递给某函数时,该函数接收这个引用的一份拷贝(copy,或译为副本),被调用函数对`map`底层数据结构的任何修改,调用者函数都可以通过持有的`map`引用看到。在我们的例子中,`countLines`函数向`counts`插入的值,也会被`main`函数看到。(译注:类似于C++里的引用传递,实际上指针是另一个指针了,但内部存的值指向同一块内存) -`dup`的前兩個版本以"流”模式讀取輸入,併根據需要拆分成多個行。理論上,這些程序可以處理任意數量的輸入數據。還有另一個方法,就是一口氣把全部輸入數據讀到內存中,一次分割爲多行,然後處理它們。下面這個版本,`dup3`,就是這麽操作的。這個例子引入了`ReadFile`函數(來自於`io/ioutil`包),其讀取指定文件的全部內容,`strings.Split`函數把字符串分割成子串的切片。(`Split`的作用與前文提到的`strings.Join`相反。) +`dup`的前两个版本以"流”模式读取输入,并根据需要拆分成多个行。理论上,这些程序可以处理任意数量的输入数据。还有另一个方法,就是一口气把全部输入数据读到内存中,一次分割为多行,然后处理它们。下面这个版本,`dup3`,就是这么操作的。这个例子引入了`ReadFile`函数(来自于`io/ioutil`包),其读取指定文件的全部内容,`strings.Split`函数把字符串分割成子串的切片。(`Split`的作用与前文提到的`strings.Join`相反。) -我們略微簡化了`dup3`。首先,由於`ReadFile`函數需要文件名作爲參數,因此隻讀指定文件,不讀標準輸入。其次,由於行計數代碼隻在一處用到,故將其移迴`main`函數。 +我们略微简化了`dup3`。首先,由于`ReadFile`函数需要文件名作为参数,因此只读指定文件,不读标准输入。其次,由于行计数代码只在一处用到,故将其移回`main`函数。 gopl.io/ch1/dup3 ```go @@ -168,8 +168,8 @@ func main() { } ``` -`ReadFile`函數返迴一個字節切片(byte slice),必須把它轉換爲`string`,才能用`strings.Split`分割。我們會在3.5.4節詳細講解字符串和字節切片。 +`ReadFile`函数返回一个字节切片(byte slice),必须把它转换为`string`,才能用`strings.Split`分割。我们会在3.5.4节详细讲解字符串和字节切片。 -實現上,`bufio.Scanner`、`outil.ReadFile`和`ioutil.WriteFile`都使用`*os.File`的`Read`和`Write`方法,但是,大多數程序員很少需要直接調用那些低級(lower-level)函數。高級(higher-level)函數,像`bufio`和`io/ioutil`包中所提供的那些,用起來要容易點。 +实现上,`bufio.Scanner`、`outil.ReadFile`和`ioutil.WriteFile`都使用`*os.File`的`Read`和`Write`方法,但是,大多数程序员很少需要直接调用那些低级(lower-level)函数。高级(higher-level)函数,像`bufio`和`io/ioutil`包中所提供的那些,用起来要容易点。 -**練習 1.4:** 脩改`dup2`,出現重複的行時打印文件名稱。 +**练习 1.4:** 修改`dup2`,出现重复的行时打印文件名称。 diff --git a/ch1/ch1-04.md b/ch1/ch1-04.md index e9994207..f47b6508 100644 --- a/ch1/ch1-04.md +++ b/ch1/ch1-04.md @@ -1,14 +1,14 @@ -## 1.4. GIF動畵 +## 1.4. GIF动画 -下面的程序會演示Go語言標準庫里的image這個package的用法,我們會用這個包來生成一繫列的bit-mapped圖,然後將這些圖片編碼爲一個GIF動畵。我們生成的圖形名字叫利薩如圖形(Lissajous figures),這種效果是在1960年代的老電影里出現的一種視覺特效。它們是協振子在兩個緯度上振動所産生的麴線,比如兩個sin正絃波分别在x軸和y軸輸入會産生的麴線。圖1.1是這樣的一個例子: +下面的程序会演示Go语言标准库里的image这个package的用法,我们会用这个包来生成一系列的bit-mapped图,然后将这些图片编码为一个GIF动画。我们生成的图形名字叫利萨如图形(Lissajous figures),这种效果是在1960年代的老电影里出现的一种视觉特效。它们是协振子在两个纬度上振动所产生的曲线,比如两个sin正弦波分别在x轴和y轴输入会产生的曲线。图1.1是这样的一个例子: ![](../images/ch1-01.png) -譯註:要看這個程序的結果,需要將標準輸出重定向到一個GIF圖像文件(使用 `./lissajous > output.gif` 命令)。下面是GIF圖像動畵效果: +译注:要看这个程序的结果,需要将标准输出重定向到一个GIF图像文件(使用 `./lissajous > output.gif` 命令)。下面是GIF图像动画效果: ![](../images/ch1-01.gif) -這段代碼里我們用了一些新的結構,包括const聲明,struct結構體類型,複合聲明。和我們舉的其它的例子不太一樣,這一個例子包含了浮點數運算。這些概念我們隻在這里簡單地説明一下,之後的章節會更詳細地講解。 +这段代码里我们用了一些新的结构,包括const声明,struct结构体类型,复合声明。和我们举的其它的例子不太一样,这一个例子包含了浮点数运算。这些概念我们只在这里简单地说明一下,之后的章节会更详细地讲解。 gopl.io/ch1/lissajous ```go @@ -66,25 +66,25 @@ func lissajous(out io.Writer) { ``` -當我們import了一個包路徑包含有多個單詞的package時,比如image/color(image和color兩個單詞),通常我們隻需要用最後那個單詞表示這個包就可以。所以當我們寫color.White時,這個變量指向的是image/color包里的變量,同理gif.GIF是屬於image/gif包里的變量。 +当我们import了一个包路径包含有多个单词的package时,比如image/color(image和color两个单词),通常我们只需要用最后那个单词表示这个包就可以。所以当我们写color.White时,这个变量指向的是image/color包里的变量,同理gif.GIF是属于image/gif包里的变量。 -這個程序里的常量聲明給出了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會出現在包級别,所以這些常量在整個包中都是可以共享的,或者你也可以把常量聲明定義在函數體內部,那麽這種常量就隻能在函數體內用。目前常量聲明的值必須是一個數字值、字符串或者一個固定的boolean值。 +这个程序里的常量声明给出了一系列的常量值,常量是指在程序编译后运行时始终都不会变化的值,比如圈数、帧数、延迟值。常量声明和变量声明一般都会出现在包级别,所以这些常量在整个包中都是可以共享的,或者你也可以把常量声明定义在函数体内部,那么这种常量就只能在函数体内用。目前常量声明的值必须是一个数字值、字符串或者一个固定的boolean值。 -[]color.Color{...}和gif.GIF{...}這兩個表達式就是我們説的複合聲明(4.2和4.4.1節有説明)。這是實例化Go語言里的複合類型的一種寫法。這里的前者生成的是一個slice切片,後者生成的是一個struct結構體。 +[]color.Color{...}和gif.GIF{...}这两个表达式就是我们说的复合声明(4.2和4.4.1节有说明)。这是实例化Go语言里的复合类型的一种写法。这里的前者生成的是一个slice切片,后者生成的是一个struct结构体。 -gif.GIF是一個struct類型(參考4.4節)。struct是一組值或者叫字段的集合,不同的類型集合在一個struct可以讓我們以一個統一的單元進行處理。anim是一個gif.GIF類型的struct變量。這種寫法會生成一個struct變量,併且其內部變量LoopCount字段會被設置爲nframes;而其它的字段會被設置爲各自類型默認的零值。struct內部的變量可以以一個點(.)來進行訪問,就像在最後兩個賦值語句中顯式地更新了anim這個struct的Delay和Image字段。 +gif.GIF是一个struct类型(参考4.4节)。struct是一组值或者叫字段的集合,不同的类型集合在一个struct可以让我们以一个统一的单元进行处理。anim是一个gif.GIF类型的struct变量。这种写法会生成一个struct变量,并且其内部变量LoopCount字段会被设置为nframes;而其它的字段会被设置为各自类型默认的零值。struct内部的变量可以以一个点(.)来进行访问,就像在最后两个赋值语句中显式地更新了anim这个struct的Delay和Image字段。 -lissajous函數內部有兩層嵌套的for循環。外層循環會循環64次,每一次都會生成一個單獨的動畵幀。它生成了一個包含兩種顔色的201&201大小的圖片,白色和黑色。所有像素點都會被默認設置爲其零值(也就是調色闆palette里的第0個值),這里我們設置的是白色。每次外層循環都會生成一張新圖片,併將一些像素設置爲黑色。其結果會append到之前結果之後。這里我們用到了append(參考4.2.1)內置函數,將結果append到anim中的幀列表末尾,併設置一個默認的80ms的延遲值。循環結束後所有的延遲值被編碼進了GIF圖片中,併將結果寫入到輸出流。out這個變量是io.Writer類型,這個類型支持把輸出結果寫到很多目標,很快我們就可以看到例子。 +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動畵。 +main函数调用lissajous函数,用它来向标准输出流打印信息,所以下面这个命令会像图1.1中产生一个GIF动画。 ``` $ go build gopl.io/ch1/lissajous $ ./lissajous >out.gif ``` -**練習 1.5:** 脩改前面的Lissajous程序里的調色闆,由黑色改爲緑色。我們可以用`color.RGBA{0xRR, 0xGG, 0xBB, 0xff}`來得到`#RRGGBB`這個色值,三個十六進製的字符串分别代表紅、緑、藍像素。 +**练习 1.5:** 修改前面的Lissajous程序里的调色板,由黑色改为绿色。我们可以用`color.RGBA{0xRR, 0xGG, 0xBB, 0xff}`来得到`#RRGGBB`这个色值,三个十六进制的字符串分别代表红、绿、蓝像素。 -**練習 1.6:** 脩改Lissajous程序,脩改其調色闆來生成更豐富的顔色,然後脩改SetColorIndex的第三個參數,看看顯示結果吧。 +**练习 1.6:** 修改Lissajous程序,修改其调色板来生成更丰富的颜色,然后修改SetColorIndex的第三个参数,看看显示结果吧。 diff --git a/ch1/ch1-05.md b/ch1/ch1-05.md index 3cec8050..e42d411b 100644 --- a/ch1/ch1-05.md +++ b/ch1/ch1-05.md @@ -1,8 +1,8 @@ -## 1.5. 獲取URL +## 1.5. 获取URL -對於很多現代應用來説,訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go語言在net這個強大package的幫助下提供了一繫列的package來做這件事情,使用這些包可以更簡單地用網絡收發信息,還可以建立更底層的網絡連接,編寫服務器程序。在這些情景下,Go語言原生的併發特性(在第八章中會介紹)顯得尤其好用。 +对于很多现代应用来说,访问互联网上的信息和访问本地文件系统一样重要。Go语言在net这个强大package的帮助下提供了一系列的package来做这件事情,使用这些包可以更简单地用网络收发信息,还可以建立更底层的网络连接,编写服务器程序。在这些情景下,Go语言原生的并发特性(在第八章中会介绍)显得尤其好用。 -爲了最簡單地展示基於HTTP獲取信息的方式,下面給出一個示例程序fetch,這個程序將獲取對應的url,併將其源文本打印出來;這個例子的靈感來源於curl工具(譯註:unix下的一個用來發http請求的工具,具體可以man curl)。當然,curl提供的功能更爲複雜豐富,這里隻編寫最簡單的樣例。這個樣例之後還會多次被用到。 +为了最简单地展示基于HTTP获取信息的方式,下面给出一个示例程序fetch,这个程序将获取对应的url,并将其源文本打印出来;这个例子的灵感来源于curl工具(译注:unix下的一个用来发http请求的工具,具体可以man curl)。当然,curl提供的功能更为复杂丰富,这里只编写最简单的样例。这个样例之后还会多次被用到。 gopl.io/ch1/fetch ```go @@ -34,7 +34,7 @@ func main() { } ``` -這個程序從兩個package中導入了函數,net/http和io/ioutil包,http.Get函數是創建HTTP請求的函數,如果獲取過程沒有出錯,那麽會在resp這個結構體中得到訪問的請求結果。resp的Body字段包括一個可讀的服務器響應流。ioutil.ReadAll函數從response中讀取到全部內容;將其結果保存在變量b中。resp.Body.Close關閉resp的Body流,防止資源洩露,Printf函數會將結果b寫出到標準輸出流中。 +这个程序从两个package中导入了函数,net/http和io/ioutil包,http.Get函数是创建HTTP请求的函数,如果获取过程没有出错,那么会在resp这个结构体中得到访问的请求结果。resp的Body字段包括一个可读的服务器响应流。ioutil.ReadAll函数从response中读取到全部内容;将其结果保存在变量b中。resp.Body.Close关闭resp的Body流,防止资源泄露,Printf函数会将结果b写出到标准输出流中。 ``` $ go build gopl.io/ch1/fetch @@ -45,24 +45,24 @@ $ ./fetch http://gopl.io ... ``` -HTTP請求如果失敗了的話,會得到下面這樣的結果: +HTTP请求如果失败了的话,会得到下面这样的结果: ``` $ ./fetch http://bad.gopl.io fetch: Get http://bad.gopl.io: dial tcp: lookup bad.gopl.io: no such host ``` -譯註:在大天朝的網絡環境下很容易重現這種錯誤,下面是Windows下運行得到的錯誤信息: +译注:在大天朝的网络环境下很容易重现这种错误,下面是Windows下运行得到的错误信息: ``` $ go run main.go http://gopl.io fetch: Get http://gopl.io: dial tcp: lookup gopl.io: getaddrinfow: No such host is known. ``` -無論哪種失敗原因,我們的程序都用了os.Exit函數來終止進程,併且返迴一個status錯誤碼,其值爲1。 +无论哪种失败原因,我们的程序都用了os.Exit函数来终止进程,并且返回一个status错误码,其值为1。 -**練習 1.7:** 函數調用io.Copy(dst, src)會從src中讀取內容,併將讀到的結果寫入到dst中,使用這個函數替代掉例子中的ioutil.ReadAll來拷貝響應結構體到os.Stdout,避免申請一個緩衝區(例子中的b)來存儲。記得處理io.Copy返迴結果中的錯誤。 +**练习 1.7:** 函数调用io.Copy(dst, src)会从src中读取内容,并将读到的结果写入到dst中,使用这个函数替代掉例子中的ioutil.ReadAll来拷贝响应结构体到os.Stdout,避免申请一个缓冲区(例子中的b)来存储。记得处理io.Copy返回结果中的错误。 -**練習 1.8:** 脩改fetch這個范例,如果輸入的url參數沒有 `http://` 前綴的話,爲這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。 +**练习 1.8:** 修改fetch这个范例,如果输入的url参数没有 `http://` 前缀的话,为这个url加上该前缀。你可能会用到strings.HasPrefix这个函数。 -**練習 1.9:** 脩改fetch打印出HTTP協議的狀態碼,可以從resp.Status變量得到該狀態碼。 +**练习 1.9:** 修改fetch打印出HTTP协议的状态码,可以从resp.Status变量得到该状态码。 diff --git a/ch1/ch1-06.md b/ch1/ch1-06.md index 3dd9d696..ab8476b6 100644 --- a/ch1/ch1-06.md +++ b/ch1/ch1-06.md @@ -1,8 +1,8 @@ -## 1.6. 併發獲取多個URL +## 1.6. 并发获取多个URL -Go語言最有意思併且最新奇的特性就是對併發編程的支持。併發編程是一個大話題,在第八章和第九章中會專門講到。這里我們隻淺嚐輒止地來體驗一下Go語言里的goroutine和channel。 +Go语言最有意思并且最新奇的特性就是对并发编程的支持。并发编程是一个大话题,在第八章和第九章中会专门讲到。这里我们只浅尝辄止地来体验一下Go语言里的goroutine和channel。 -下面的例子fetchall,和前面小節的fetch程序所要做的工作基本一致,fetchall的特别之處在於它會同時去獲取所有的URL,所以這個程序的總執行時間不會超過執行時間最長的那一個任務,前面的fetch程序執行時間則是所有任務執行時間之和。fetchall程序隻會打印獲取的內容大小和經過的時間,不會像之前那樣打印獲取的內容。 +下面的例子fetchall,和前面小节的fetch程序所要做的工作基本一致,fetchall的特别之处在于它会同时去获取所有的URL,所以这个程序的总执行时间不会超过执行时间最长的那一个任务,前面的fetch程序执行时间则是所有任务执行时间之和。fetchall程序只会打印获取的内容大小和经过的时间,不会像之前那样打印获取的内容。 gopl.io/ch1/fetchall ```go @@ -48,7 +48,7 @@ func fetch(url string, ch chan<- string) { } ``` -下面使用fetchall來請求幾個地址: +下面使用fetchall来请求几个地址: ``` $ go build gopl.io/ch1/fetchall @@ -59,10 +59,10 @@ $ ./fetchall https://golang.org http://gopl.io https://godoc.org 0.48s elapsed ``` -goroutine是一種函數的併發執行方式,而channel是用來在goroutine之間進行參數傳遞。main函數本身也運行在一個goroutine中,而go function則表示創建一個新的goroutine,併在這個新的goroutine中執行這個函數。 +goroutine是一种函数的并发执行方式,而channel是用来在goroutine之间进行参数传递。main函数本身也运行在一个goroutine中,而go function则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数。 -main函數中用make函數創建了一個傳遞string類型參數的channel,對每一個命令行參數,我們都用go這個關鍵字來創建一個goroutine,併且讓函數在這個goroutine異步執行http.Get方法。這個程序里的io.Copy會把響應的Body內容拷貝到ioutil.Discard輸出流中(譯註:可以把這個變量看作一個垃圾桶,可以向里面寫一些不需要的數據),因爲我們需要這個方法返迴的字節數,但是又不想要其內容。每當請求返迴內容時,fetch函數都會往ch這個channel里寫入一個字符串,由main函數里的第二個for循環來處理併打印channel里的這個字符串。 +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執行兩遍請求,査看兩次時間是否有較大的差别,併且每次獲取到的響應內容是否一致,脩改本節中的程序,將響應結果輸出,以便於進行對比。 +**练习 1.10:** 找一个数据量比较大的网站,用本小节中的程序调研网站的缓存策略,对每个URL执行两遍请求,查看两次时间是否有较大的差别,并且每次获取到的响应内容是否一致,修改本节中的程序,将响应结果输出,以便于进行对比。 diff --git a/ch1/ch1-07.md b/ch1/ch1-07.md index 042e92ae..32f97fc3 100644 --- a/ch1/ch1-07.md +++ b/ch1/ch1-07.md @@ -1,6 +1,6 @@ -## 1.7. Web服務 +## 1.7. Web服务 -Go語言的內置庫使得寫一個類似fetch的web服務器變得異常地簡單。在本節中,我們會展示一個微型服務器,這個服務器的功能是返迴當前用戶正在訪問的URL。比如用戶訪問的是 http://localhost:8000/hello ,那麽響應是URL.Path = "hello"。 +Go语言的内置库使得写一个类似fetch的web服务器变得异常地简单。在本节中,我们会展示一个微型服务器,这个服务器的功能是返回当前用户正在访问的URL。比如用户访问的是 http://localhost:8000/hello ,那么响应是URL.Path = "hello"。 gopl.io/ch1/server1 ```go @@ -24,15 +24,15 @@ func handler(w http.ResponseWriter, r *http.Request) { } ``` -我們隻用了八九行代碼就實現了一個Web服務程序,這都是多虧了標準庫里的方法已經幫我們完成了大量工作。main函數將所有發送到/路徑下的請求和handler函數關聯起來,/開頭的請求其實就是所有發送到當前站點上的請求,服務監聽8000端口。發送到這個服務的“請求”是一個http.Request類型的對象,這個對象中包含了請求中的一繫列相關字段,其中就包括我們需要的URL。當請求到達服務器時,這個請求會被傳給handler函數來處理,這個函數會將/hello這個路徑從請求的URL中解析出來,然後把其發送到響應中,這里我們用的是標準輸出流的fmt.Fprintf。Web服務會在第7.7節中做更詳細的闡述。 +我们只用了八九行代码就实现了一个Web服务程序,这都是多亏了标准库里的方法已经帮我们完成了大量工作。main函数将所有发送到/路径下的请求和handler函数关联起来,/开头的请求其实就是所有发送到当前站点上的请求,服务监听8000端口。发送到这个服务的“请求”是一个http.Request类型的对象,这个对象中包含了请求中的一系列相关字段,其中就包括我们需要的URL。当请求到达服务器时,这个请求会被传给handler函数来处理,这个函数会将/hello这个路径从请求的URL中解析出来,然后把其发送到响应中,这里我们用的是标准输出流的fmt.Fprintf。Web服务会在第7.7节中做更详细的阐述。 -讓我們在後台運行這個服務程序。如果你的操作繫統是Mac OS X或者Linux,那麽在運行命令的末尾加上一個&符號,卽可讓程序簡單地跑在後台,windows下可以在另外一個命令行窗口去運行這個程序。 +让我们在后台运行这个服务程序。如果你的操作系统是Mac OS X或者Linux,那么在运行命令的末尾加上一个&符号,即可让程序简单地跑在后台,windows下可以在另外一个命令行窗口去运行这个程序。 ``` $ go run src/gopl.io/ch1/server1/main.go & ``` -現在可以通過命令行來發送客戶端請求了: +现在可以通过命令行来发送客户端请求了: ``` $ go build gopl.io/ch1/fetch @@ -42,11 +42,11 @@ $ ./fetch http://localhost:8000/help URL.Path = "/help" ``` -還可以直接在瀏覽器里訪問這個URL,然後得到返迴結果,如圖1.2: +还可以直接在浏览器里访问这个URL,然后得到返回结果,如图1.2: ![](../images/ch1-02.png) -在這個服務的基礎上疊加特性是很容易的。一種比較實用的脩改是爲訪問的url添加某種狀態。比如,下面這個版本輸出了同樣的內容,但是會對請求的次數進行計算;對URL的請求結果會包含各種URL被訪問的總次數,直接對/count這個URL的訪問要除外。 +在这个服务的基础上叠加特性是很容易的。一种比较实用的修改是为访问的url添加某种状态。比如,下面这个版本输出了同样的内容,但是会对请求的次数进行计算;对URL的请求结果会包含各种URL被访问的总次数,直接对/count这个URL的访问要除外。 gopl.io/ch1/server2 ```go @@ -85,9 +85,9 @@ func counter(w http.ResponseWriter, r *http.Request) { } ``` -這個服務器有兩個請求處理函數,根據請求的url不同會調用不同的函數:對/count這個url的請求會調用到count這個函數,其它的url都會調用默認的處理函數。如果你的請求pattern是以/結尾,那麽所有以該url爲前綴的url都會被這條規則匹配。在這些代碼的背後,服務器每一次接收請求處理時都會另起一個goroutine,這樣服務器就可以同一時間處理多個請求。然而在併發情況下,假如眞的有兩個請求同一時刻去更新count,那麽這個值可能併不會被正確地增加;這個程序可能會引發一個嚴重的bug:競態條件(參見9.1)。爲了避免這個問題,我們必須保證每次脩改變量的最多隻能有一個goroutine,這也就是代碼里的mu.Lock()和mu.Unlock()調用將脩改count的所有行爲包在中間的目的。第九章中我們會進一步講解共享變量。 +这个服务器有两个请求处理函数,根据请求的url不同会调用不同的函数:对/count这个url的请求会调用到count这个函数,其它的url都会调用默认的处理函数。如果你的请求pattern是以/结尾,那么所有以该url为前缀的url都会被这条规则匹配。在这些代码的背后,服务器每一次接收请求处理时都会另起一个goroutine,这样服务器就可以同一时间处理多个请求。然而在并发情况下,假如真的有两个请求同一时刻去更新count,那么这个值可能并不会被正确地增加;这个程序可能会引发一个严重的bug:竞态条件(参见9.1)。为了避免这个问题,我们必须保证每次修改变量的最多只能有一个goroutine,这也就是代码里的mu.Lock()和mu.Unlock()调用将修改count的所有行为包在中间的目的。第九章中我们会进一步讲解共享变量。 -下面是一個更爲豐富的例子,handler函數會把請求的http頭和請求的form數據都打印出來,這樣可以使檢査和調試這個服務更爲方便: +下面是一个更为丰富的例子,handler函数会把请求的http头和请求的form数据都打印出来,这样可以使检查和调试这个服务更为方便: gopl.io/ch1/server3 ```go @@ -108,7 +108,7 @@ func handler(w http.ResponseWriter, r *http.Request) { } ``` -我們用http.Request這個struct里的字段來輸出下面這樣的內容: +我们用http.Request这个struct里的字段来输出下面这样的内容: ``` GET /?q=query HTTP/1.1 @@ -119,7 +119,7 @@ RemoteAddr = "127.0.0.1:59911" Form["q"] = ["query"] ``` -可以看到這里的ParseForm被嵌套在了if語句中。Go語言允許這樣的一個簡單的語句結果作爲循環的變量聲明出現在if語句的最前面,這一點對錯誤處理很有用處。我們還可以像下面這樣寫(當然看起來就長了一些): +可以看到这里的ParseForm被嵌套在了if语句中。Go语言允许这样的一个简单的语句结果作为循环的变量声明出现在if语句的最前面,这一点对错误处理很有用处。我们还可以像下面这样写(当然看起来就长了一些): ```go err := r.ParseForm() @@ -128,13 +128,13 @@ if err != nil { } ``` -用if和ParseForm結合可以讓代碼更加簡單,併且可以限製err這個變量的作用域,這麽做是很不錯的。我們會在2.7節中講解作用域。 +用if和ParseForm结合可以让代码更加简单,并且可以限制err这个变量的作用域,这么做是很不错的。我们会在2.7节中讲解作用域。 -在這些程序中,我們看到了很多不同的類型被輸出到標準輸出流中。比如前面的fetch程序,把HTTP的響應數據拷貝到了os.Stdout,lissajous程序里我們輸出的是一個文件。fetchall程序則完全忽略到了HTTP的響應Body,隻是計算了一下響應Body的大小,這個程序中把響應Body拷貝到了ioutil.Discard。在本節的web服務器程序中則是用fmt.Fprintf直接寫到了http.ResponseWriter中。 +在这些程序中,我们看到了很多不同的类型被输出到标准输出流中。比如前面的fetch程序,把HTTP的响应数据拷贝到了os.Stdout,lissajous程序里我们输出的是一个文件。fetchall程序则完全忽略到了HTTP的响应Body,只是计算了一下响应Body的大小,这个程序中把响应Body拷贝到了ioutil.Discard。在本节的web服务器程序中则是用fmt.Fprintf直接写到了http.ResponseWriter中。 -盡管三種具體的實現流程併不太一樣,他們都實現一個共同的接口,卽當它們被調用需要一個標準流輸出時都可以滿足。這個接口叫作io.Writer,在7.1節中會詳細討論。 +尽管三种具体的实现流程并不太一样,他们都实现一个共同的接口,即当它们被调用需要一个标准流输出时都可以满足。这个接口叫作io.Writer,在7.1节中会详细讨论。 -Go語言的接口機製會在第7章中講解,爲了在這里簡單説明接口能做什麽,讓我們簡單地將這里的web服務器和之前寫的lissajous函數結合起來,這樣GIF動畵可以被寫到HTTP的客戶端,而不是之前的標準輸出流。隻要在web服務器的代碼里加入下面這幾行。 +Go语言的接口机制会在第7章中讲解,为了在这里简单说明接口能做什么,让我们简单地将这里的web服务器和之前写的lissajous函数结合起来,这样GIF动画可以被写到HTTP的客户端,而不是之前的标准输出流。只要在web服务器的代码里加入下面这几行。 ```Go handler := func(w http.ResponseWriter, r *http.Request) { @@ -143,7 +143,7 @@ handler := func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", handler) ``` -或者另一種等價形式: +或者另一种等价形式: ```Go http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -151,11 +151,11 @@ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { }) ``` -HandleFunc函數的第二個參數是一個函數的字面值,也就是一個在使用時定義的匿名函數。這些內容我們會在5.6節中講解。 +HandleFunc函数的第二个参数是一个函数的字面值,也就是一个在使用时定义的匿名函数。这些内容我们会在5.6节中讲解。 -做完這些脩改之後,在瀏覽器里訪問 http://localhost:8000 。每次你載入這個頁面都可以看到一個像圖1.3那樣的動畵。 +做完这些修改之后,在浏览器里访问 http://localhost:8000 。每次你载入这个页面都可以看到一个像图1.3那样的动画。 ![](../images/ch1-03.png) -**練習 1.12:** 脩改Lissajour服務,從URL讀取變量,比如你可以訪問 http://localhost:8000/?cycles=20 這個URL,這樣訪問可以將程序里的cycles默認的5脩改爲20。字符串轉換爲數字可以調用strconv.Atoi函數。你可以在godoc里査看strconv.Atoi的詳細説明。 +**练习 1.12:** 修改Lissajour服务,从URL读取变量,比如你可以访问 http://localhost:8000/?cycles=20 这个URL,这样访问可以将程序里的cycles默认的5修改为20。字符串转换为数字可以调用strconv.Atoi函数。你可以在godoc里查看strconv.Atoi的详细说明。 diff --git a/ch1/ch1-08.md b/ch1/ch1-08.md index 905fe1c3..30a67ef3 100644 --- a/ch1/ch1-08.md +++ b/ch1/ch1-08.md @@ -1,8 +1,8 @@ -## 1.8. 本章要點 +## 1.8. 本章要点 -本章對Go語言做了一些介紹,Go語言很多方面在有限的篇幅中無法覆蓋到。本節會把沒有講到的內容也做一些簡單的介紹,這樣讀者在讀到完整的內容之前,可以有個簡單的印象。 +本章对Go语言做了一些介绍,Go语言很多方面在有限的篇幅中无法覆盖到。本节会把没有讲到的内容也做一些简单的介绍,这样读者在读到完整的内容之前,可以有个简单的印象。 -**控製流:** 在本章我們隻介紹了if控製和for,但是沒有提到switch多路選擇。這里是一個簡單的switch的例子: +**控制流:** 在本章我们只介绍了if控制和for,但是没有提到switch多路选择。这里是一个简单的switch的例子: ```go switch coinflip() { @@ -15,9 +15,9 @@ default: } ``` -在翻轉硬幣的時候,例子里的coinflip函數返迴幾種不同的結果,每一個case都會對應一個返迴結果,這里需要註意,Go語言併不需要顯式地在每一個case後寫break,語言默認執行完case後的邏輯語句會自動退出。當然了,如果你想要相鄰的幾個case都執行同一邏輯的話,需要自己顯式地寫上一個fallthrough語句來覆蓋這種默認行爲。不過fallthrough語句在一般的程序中很少用到。 +在翻转硬币的时候,例子里的coinflip函数返回几种不同的结果,每一个case都会对应一个返回结果,这里需要注意,Go语言并不需要显式地在每一个case后写break,语言默认执行完case后的逻辑语句会自动退出。当然了,如果你想要相邻的几个case都执行同一逻辑的话,需要自己显式地写上一个fallthrough语句来覆盖这种默认行为。不过fallthrough语句在一般的程序中很少用到。 -Go語言里的switch還可以不帶操作對象(譯註:switch不帶操作對象時默認用true值代替,然後將每個case的表達式和true值進行比較);可以直接羅列多種條件,像其它語言里面的多個if else一樣,下面是一個例子: +Go语言里的switch还可以不带操作对象(译注:switch不带操作对象时默认用true值代替,然后将每个case的表达式和true值进行比较);可以直接罗列多种条件,像其它语言里面的多个if else一样,下面是一个例子: ```go func Signum(x int) int { @@ -32,13 +32,13 @@ func Signum(x int) int { } ``` -這種形式叫做無tag switch(tagless switch);這和switch true是等價的。 +这种形式叫做无tag switch(tagless switch);这和switch true是等价的。 -像for和if控製語句一樣,switch也可以緊跟一個簡短的變量聲明,一個自增表達式、賦值語句,或者一個函數調用(譯註:比其它語言豐富)。 +像for和if控制语句一样,switch也可以紧跟一个简短的变量声明,一个自增表达式、赋值语句,或者一个函数调用(译注:比其它语言丰富)。 -break和continue語句會改變控製流。和其它語言中的break和continue一樣,break會中斷當前的循環,併開始執行循環之後的內容,而continue會中跳過當前循環,併開始執行下一次循環。這兩個語句除了可以控製for循環,還可以用來控製switch和select語句(之後會講到),在1.3節中我們看到,continue會跳過內層的循環,如果我們想跳過的是更外層的循環的話,我們可以在相應的位置加上label,這樣break和continue就可以根據我們的想法來continue和break任意循環。這看起來甚至有點像goto語句的作用了。當然,一般程序員也不會用到這種操作。這兩種行爲更多地被用到機器生成的代碼中。 +break和continue语句会改变控制流。和其它语言中的break和continue一样,break会中断当前的循环,并开始执行循环之后的内容,而continue会中跳过当前循环,并开始执行下一次循环。这两个语句除了可以控制for循环,还可以用来控制switch和select语句(之后会讲到),在1.3节中我们看到,continue会跳过内层的循环,如果我们想跳过的是更外层的循环的话,我们可以在相应的位置加上label,这样break和continue就可以根据我们的想法来continue和break任意循环。这看起来甚至有点像goto语句的作用了。当然,一般程序员也不会用到这种操作。这两种行为更多地被用到机器生成的代码中。 -**命名類型:** 類型聲明使得我們可以很方便地給一個特殊類型一個名字。因爲struct類型聲明通常非常地長,所以我們總要給這種struct取一個名字。本章中就有這樣一個例子,二維點類型: +**命名类型:** 类型声明使得我们可以很方便地给一个特殊类型一个名字。因为struct类型声明通常非常地长,所以我们总要给这种struct取一个名字。本章中就有这样一个例子,二维点类型: ```go type Point struct { @@ -47,15 +47,15 @@ type Point struct { var p Point ``` -類型聲明和命名類型會在第二章中介紹。 +类型声明和命名类型会在第二章中介绍。 -**指針:** Go語言提供了指針。指針是一種直接存儲了變量的內存地址的數據類型。在其它語言中,比如C語言,指針操作是完全不受約束的。在另外一些語言中,指針一般被處理爲“引用”,除了到處傳遞這些指針之外,併不能對這些指針做太多事情。Go語言在這兩種范圍中取了一種平衡。指針是可見的內存地址,&操作符可以返迴一個變量的內存地址,併且*操作符可以獲取指針指向的變量內容,但是在Go語言里沒有指針運算,也就是不能像c語言里可以對指針進行加或減操作。我們會在2.3.2中進行詳細介紹。 +**指针:** Go语言提供了指针。指针是一种直接存储了变量的内存地址的数据类型。在其它语言中,比如C语言,指针操作是完全不受约束的。在另外一些语言中,指针一般被处理为“引用”,除了到处传递这些指针之外,并不能对这些指针做太多事情。Go语言在这两种范围中取了一种平衡。指针是可见的内存地址,&操作符可以返回一个变量的内存地址,并且*操作符可以获取指针指向的变量内容,但是在Go语言里没有指针运算,也就是不能像c语言里可以对指针进行加或减操作。我们会在2.3.2中进行详细介绍。 -**方法和接口:** 方法是和命名類型關聯的一類函數。Go語言里比較特殊的是方法可以被關聯到任意一種命名類型。在第六章我們會詳細地講方法。接口是一種抽象類型,這種類型可以讓我們以同樣的方式來處理不同的固有類型,不用關心它們的具體實現,而隻需要關註它們提供的方法。第七章中會詳細説明這些內容。 +**方法和接口:** 方法是和命名类型关联的一类函数。Go语言里比较特殊的是方法可以被关联到任意一种命名类型。在第六章我们会详细地讲方法。接口是一种抽象类型,这种类型可以让我们以同样的方式来处理不同的固有类型,不用关心它们的具体实现,而只需要关注它们提供的方法。第七章中会详细说明这些内容。 -**包(packages):** Go語言提供了一些很好用的package,併且這些package是可以擴展的。Go語言社區已經創造併且分享了很多很多。所以Go語言編程大多數情況下就是用已有的package來寫我們自己的代碼。通過這本書,我們會講解一些重要的標準庫內的package,但是還是有很多限於篇幅沒有去説明,因爲我們沒法在這樣的厚度的書里去做一部代碼大全。 +**包(packages):** Go语言提供了一些很好用的package,并且这些package是可以扩展的。Go语言社区已经创造并且分享了很多很多。所以Go语言编程大多数情况下就是用已有的package来写我们自己的代码。通过这本书,我们会讲解一些重要的标准库内的package,但是还是有很多限于篇幅没有去说明,因为我们没法在这样的厚度的书里去做一部代码大全。 -在你開始寫一個新程序之前,最好先去檢査一下是不是已經有了現成的庫可以幫助你更高效地完成這件事情。你可以在 https://golang.org/pkg 和 https://godoc.org 中找到標準庫和社區寫的package。godoc這個工具可以讓你直接在本地命令行閲讀標準庫的文檔。比如下面這個例子。 +在你开始写一个新程序之前,最好先去检查一下是不是已经有了现成的库可以帮助你更高效地完成这件事情。你可以在 https://golang.org/pkg 和 https://godoc.org 中找到标准库和社区写的package。godoc这个工具可以让你直接在本地命令行阅读标准库的文档。比如下面这个例子。 ``` $ go doc http.ListenAndServe @@ -66,7 +66,7 @@ func ListenAndServe(addr string, handler Handler) error ... ``` -**註釋:** 我們之前已經提到過了在源文件的開頭寫的註釋是這個源文件的文檔。在每一個函數之前寫一個説明函數行爲的註釋也是一個好習慣。這些慣例很重要,因爲這些內容會被像godoc這樣的工具檢測到,併且在執行命令時顯示這些註釋。具體可以參考10.7.4。 +**注释:** 我们之前已经提到过了在源文件的开头写的注释是这个源文件的文档。在每一个函数之前写一个说明函数行为的注释也是一个好习惯。这些惯例很重要,因为这些内容会被像godoc这样的工具检测到,并且在执行命令时显示这些注释。具体可以参考10.7.4。 -多行註釋可以用 `/* ... */` 來包裹,和其它大多數語言一樣。在文件一開頭的註釋一般都是這種形式,或者一大段的解釋性的註釋文字也會被這符號包住,來避免每一行都需要加//。在註釋中//和/*是沒什麽意義的,所以不要在註釋中再嵌入註釋。 +多行注释可以用 `/* ... */` 来包裹,和其它大多数语言一样。在文件一开头的注释一般都是这种形式,或者一大段的解释性的注释文字也会被这符号包住,来避免每一行都需要加//。在注释中//和/*是没什么意义的,所以不要在注释中再嵌入注释。 diff --git a/ch1/ch1.md b/ch1/ch1.md index 833914e6..849b526d 100644 --- a/ch1/ch1.md +++ b/ch1/ch1.md @@ -1,5 +1,5 @@ -# 第1章 入門 +# 第1章 入门 -本章介紹Go語言的基礎組件。本章提供了足夠的信息和示例程序,希望可以幫你盡快入門, 寫出有用的程序。本章和之後章節的示例程序都針對你可能遇到的現實案例。先了解幾個Go程序,涉及的主題從簡單的文件處理、圖像處理到互聯網客戶端和服務端併發。當然,第一章不會解釋細枝末節,但用這些程序來學習一門新語言還是很有效的。 +本章介绍Go语言的基础组件。本章提供了足够的信息和示例程序,希望可以帮你尽快入门, 写出有用的程序。本章和之后章节的示例程序都针对你可能遇到的现实案例。先了解几个Go程序,涉及的主题从简单的文件处理、图像处理到互联网客户端和服务端并发。当然,第一章不会解释细枝末节,但用这些程序来学习一门新语言还是很有效的。 -學習一門新語言時,會有一種自然的傾向, 按照自己熟悉的語言的套路寫新語言程序。學習Go語言的過程中,請警惕這種想法,盡量别這麽做。我們會演示怎麽寫好Go語言程序,所以請使用本書的代碼作爲你自己寫程序時的指南。 +学习一门新语言时,会有一种自然的倾向, 按照自己熟悉的语言的套路写新语言程序。学习Go语言的过程中,请警惕这种想法,尽量别这么做。我们会演示怎么写好Go语言程序,所以请使用本书的代码作为你自己写程序时的指南。 diff --git a/ch10/ch10-01.md b/ch10/ch10-01.md index aacc9d53..56214602 100644 --- a/ch10/ch10-01.md +++ b/ch10/ch10-01.md @@ -1,9 +1,9 @@ -## 10.1. 包簡介 +## 10.1. 包简介 -任何包繫統設計的目的都是爲了簡化大型程序的設計和維護工作,通過將一組相關的特性放進一個獨立的單元以便於理解和更新,在每個單元更新的同時保持和程序中其它單元的相對獨立性。這種模塊化的特性允許每個包可以被其它的不同項目共享和重用,在項目范圍內、甚至全球范圍統一的分發和複用。 +任何包系统设计的目的都是为了简化大型程序的设计和维护工作,通过将一组相关的特性放进一个独立的单元以便于理解和更新,在每个单元更新的同时保持和程序中其它单元的相对独立性。这种模块化的特性允许每个包可以被其它的不同项目共享和重用,在项目范围内、甚至全球范围统一的分发和复用。 -每個包一般都定義了一個不同的名字空間用於它內部的每個標識符的訪問。每個名字空間關聯到一個特定的包,讓我們給類型、函數等選擇簡短明了的名字,這樣可以避免在我們使用它們的時候減少和其它部分名字的衝突。 +每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问。每个名字空间关联到一个特定的包,让我们给类型、函数等选择简短明了的名字,这样可以避免在我们使用它们的时候减少和其它部分名字的冲突。 -每個包還通過控製包內名字的可見性和是否導出來實現封裝特性。通過限製包成員的可見性併隱藏包API的具體實現,將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現。通過限製包內變量的可見性,還可以強製用戶通過某些特定函數來訪問和更新內部變量,這樣可以保證內部變量的一致性和併發時的互斥約束。 +每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包API的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。 -當我們脩改了一個源文件,我們必須重新編譯該源文件對應的包和所有依賴該包的其他包。卽使是從頭構建,Go語言編譯器的編譯速度也明顯快於其它編譯語言。Go語言的閃電般的編譯速度主要得益於三個語言特性。第一點,所有導入的包必須在每個文件的開頭顯式聲明,這樣的話編譯器就沒有必要讀取和分析整個源文件來判斷包的依賴關繫。第二點,禁止包的環狀依賴,因爲沒有循環依賴,包的依賴關繫形成一個有向無環圖,每個包可以被獨立編譯,而且很可能是被併發編譯。第三點,編譯後包的目標文件不僅僅記録包本身的導出信息,目標文件同時還記録了包的依賴關繫。因此,在編譯一個包的時候,編譯器隻需要讀取每個直接導入包的目標文件,而不需要遍歷所有依賴的的文件(譯註:很多都是重複的間接依賴)。 +当我们修改了一个源文件,我们必须重新编译该源文件对应的包和所有依赖该包的其他包。即使是从头构建,Go语言编译器的编译速度也明显快于其它编译语言。Go语言的闪电般的编译速度主要得益于三个语言特性。第一点,所有导入的包必须在每个文件的开头显式声明,这样的话编译器就没有必要读取和分析整个源文件来判断包的依赖关系。第二点,禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。第三点,编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。因此,在编译一个包的时候,编译器只需要读取每个直接导入包的目标文件,而不需要遍历所有依赖的的文件(译注:很多都是重复的间接依赖)。 diff --git a/ch10/ch10-02.md b/ch10/ch10-02.md index d32c6922..18ca7aaa 100644 --- a/ch10/ch10-02.md +++ b/ch10/ch10-02.md @@ -1,6 +1,6 @@ -## 10.2. 導入路徑 +## 10.2. 导入路径 -每個包是由一個全局唯一的字符串所標識的導入路徑定位。出現在import語句中的導入路徑也是字符串。 +每个包是由一个全局唯一的字符串所标识的导入路径定位。出现在import语句中的导入路径也是字符串。 ```Go import ( @@ -14,6 +14,6 @@ import ( ) ``` -就像我們在2.6.1節提到過的,Go語言的規范併沒有指明包的導入路徑字符串的具體含義,導入路徑的具體含義是由構建工具來解釋的。在本章,我們將深入討論Go語言工具箱的功能,包括大家經常使用的構建測試等功能。當然,也有第三方擴展的工具箱存在。例如,Google公司內部的Go語言碼農,他們就使用內部的多語言構建繫統(譯註:Google公司使用的是類似[Bazel](http://bazel.io)的構建繫統,支持多種編程語言,目前該構件繫統還不能完整支持Windows環境),用不同的規則來處理包名字和定位包,用不同的規則來處理單元測試等等,因爲這樣可以更緊密適配他們內部環境。 +就像我们在2.6.1节提到过的,Go语言的规范并没有指明包的导入路径字符串的具体含义,导入路径的具体含义是由构建工具来解释的。在本章,我们将深入讨论Go语言工具箱的功能,包括大家经常使用的构建测试等功能。当然,也有第三方扩展的工具箱存在。例如,Google公司内部的Go语言码农,他们就使用内部的多语言构建系统(译注:Google公司使用的是类似[Bazel](http://bazel.io)的构建系统,支持多种编程语言,目前该构件系统还不能完整支持Windows环境),用不同的规则来处理包名字和定位包,用不同的规则来处理单元测试等等,因为这样可以更紧密适配他们内部环境。 -如果你計劃分享或發布包,那麽導入路徑最好是全球唯一的。爲了避免衝突,所有非標準庫包的導入路徑建議以所在組織的互聯網域名爲前綴;而且這樣也有利於包的檢索。例如,上面的import語句導入了Go糰隊維護的HTML解析器和一個流行的第三方維護的MySQL驅動。 +如果你计划分享或发布包,那么导入路径最好是全球唯一的。为了避免冲突,所有非标准库包的导入路径建议以所在组织的互联网域名为前缀;而且这样也有利于包的检索。例如,上面的import语句导入了Go团队维护的HTML解析器和一个流行的第三方维护的MySQL驱动。 diff --git a/ch10/ch10-03.md b/ch10/ch10-03.md index f4e6774e..011b80bb 100644 --- a/ch10/ch10-03.md +++ b/ch10/ch10-03.md @@ -1,8 +1,8 @@ -## 10.3. 包聲明 +## 10.3. 包声明 -在每個Go語音源文件的開頭都必須有包聲明語句。包聲明語句的主要目的是確定當前包被其它包導入時默認的標識符(也稱爲包名)。 +在每个Go语音源文件的开头都必须有包声明语句。包声明语句的主要目的是确定当前包被其它包导入时默认的标识符(也称为包名)。 -例如,math/rand包的每個源文件的開頭都包含`package rand`包聲明語句,所以當你導入這個包,你就可以用rand.Int、rand.Float64類似的方式訪問包的成員。 +例如,math/rand包的每个源文件的开头都包含`package rand`包声明语句,所以当你导入这个包,你就可以用rand.Int、rand.Float64类似的方式访问包的成员。 ```Go package main @@ -17,10 +17,10 @@ func main() { } ``` -通常來説,默認的包名就是包導入路徑名的最後一段,因此卽使兩個包的導入路徑不同,它們依然可能有一個相同的包名。例如,math/rand包和crypto/rand包的包名都是rand。稍後我們將看到如何同時導入兩個有相同包名的包。 +通常来说,默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名。例如,math/rand包和crypto/rand包的包名都是rand。稍后我们将看到如何同时导入两个有相同包名的包。 -關於默認包名一般采用導入路徑名的最後一段的約定也有三種例外情況。第一個例外,包對應一個可執行程序,也就是main包,這時候main包本身的導入路徑是無關緊要的。名字爲main的包是給go build(§10.7.3)構建命令一個信息,這個包編譯完之後必須調用連接器生成一個可執行程序。 +关于默认包名一般采用导入路径名的最后一段的约定也有三种例外情况。第一个例外,包对应一个可执行程序,也就是main包,这时候main包本身的导入路径是无关紧要的。名字为main的包是给go build(§10.7.3)构建命令一个信息,这个包编译完之后必须调用连接器生成一个可执行程序。 -第二個例外,包所在的目録中可能有一些文件名是以_test.go爲後綴的Go源文件(譯註:前面必須有其它的字符,因爲以`_`前綴的源文件是被忽略的),併且這些源文件聲明的包名也是以_test爲後綴名的。這種目録可以包含兩種包:一種普通包,加一種則是測試的外部擴展包。所有以_test爲後綴包名的測試外部擴展包都由go test命令獨立編譯,普通包和測試的外部擴展包是相互獨立的。測試的外部擴展包一般用來避免測試代碼中的循環導入依賴,具體細節我們將在11.2.4節中介紹。 +第二个例外,包所在的目录中可能有一些文件名是以_test.go为后缀的Go源文件(译注:前面必须有其它的字符,因为以`_`前缀的源文件是被忽略的),并且这些源文件声明的包名也是以_test为后缀名的。这种目录可以包含两种包:一种普通包,加一种则是测试的外部扩展包。所有以_test为后缀包名的测试外部扩展包都由go test命令独立编译,普通包和测试的外部扩展包是相互独立的。测试的外部扩展包一般用来避免测试代码中的循环导入依赖,具体细节我们将在11.2.4节中介绍。 -第三個例外,一些依賴版本號的管理工具會在導入路徑後追加版本號信息,例如"gopkg.in/yaml.v2"。這種情況下包的名字併不包含版本號後綴,而是yaml。 +第三个例外,一些依赖版本号的管理工具会在导入路径后追加版本号信息,例如"gopkg.in/yaml.v2"。这种情况下包的名字并不包含版本号后缀,而是yaml。 diff --git a/ch10/ch10-04.md b/ch10/ch10-04.md index fac4e7db..30741bc1 100644 --- a/ch10/ch10-04.md +++ b/ch10/ch10-04.md @@ -1,6 +1,6 @@ -## 10.4. 導入聲明 +## 10.4. 导入声明 -可以在一個Go語言源文件包聲明語句之後,其它非導入聲明語句之前,包含零到多個導入包聲明語句。每個導入聲明可以單獨指定一個導入路徑,也可以通過圓括號同時導入多個導入路徑。下面兩個導入形式是等價的,但是第二種形式更爲常見。 +可以在一个Go语言源文件包声明语句之后,其它非导入声明语句之前,包含零到多个导入包声明语句。每个导入声明可以单独指定一个导入路径,也可以通过圆括号同时导入多个导入路径。下面两个导入形式是等价的,但是第二种形式更为常见。 ```Go import "fmt" @@ -12,7 +12,7 @@ import ( ) ``` -導入的包之間可以通過添加空行來分組;通常將來自不同組織的包獨自分組。包的導入順序無關緊要,但是在每個分組中一般會根據字符串順序排列。(gofmt和goimports工具都可以將不同分組導入的包獨立排序。) +导入的包之间可以通过添加空行来分组;通常将来自不同组织的包独自分组。包的导入顺序无关紧要,但是在每个分组中一般会根据字符串顺序排列。(gofmt和goimports工具都可以将不同分组导入的包独立排序。) ```Go import ( @@ -25,7 +25,7 @@ import ( ) ``` -如果我們想同時導入兩個有着名字相同的包,例如math/rand包和crypto/rand包,那麽導入聲明必須至少爲一個同名包指定一個新的包名以避免衝突。這叫做導入包的重命名。 +如果我们想同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。这叫做导入包的重命名。 ```Go import ( @@ -34,8 +34,8 @@ import ( ) ``` -導入包的重命名隻影響當前的源文件。其它的源文件如果導入了相同的包,可以用導入包原本默認的名字或重命名爲另一個完全不同的名字。 +导入包的重命名只影响当前的源文件。其它的源文件如果导入了相同的包,可以用导入包原本默认的名字或重命名为另一个完全不同的名字。 -導入包重命名是一個有用的特性,它不僅僅隻是爲了解決名字衝突。如果導入的一個包名很笨重,特别是在一些自動生成的代碼中,這時候用一個簡短名稱會更方便。選擇用簡短名稱重命名導入包時候最好統一,以避免包名混亂。選擇另一個包名稱還可以幫助避免和本地普通變量名産生衝突。例如,如果文件中已經有了一個名爲path的變量,那麽我們可以將"path"標準包重命名爲pathpkg。 +导入包重命名是一个有用的特性,它不仅仅只是为了解决名字冲突。如果导入的一个包名很笨重,特别是在一些自动生成的代码中,这时候用一个简短名称会更方便。选择用简短名称重命名导入包时候最好统一,以避免包名混乱。选择另一个包名称还可以帮助避免和本地普通变量名产生冲突。例如,如果文件中已经有了一个名为path的变量,那么我们可以将"path"标准包重命名为pathpkg。 -每個導入聲明語句都明確指定了當前包和被導入包之間的依賴關繫。如果遇到包循環導入的情況,Go語言的構建工具將報告錯誤。 +每个导入声明语句都明确指定了当前包和被导入包之间的依赖关系。如果遇到包循环导入的情况,Go语言的构建工具将报告错误。 diff --git a/ch10/ch10-05.md b/ch10/ch10-05.md index 53e47623..18c5cdbc 100644 --- a/ch10/ch10-05.md +++ b/ch10/ch10-05.md @@ -1,14 +1,14 @@ -## 10.5. 包的匿名導入 +## 10.5. 包的匿名导入 -如果隻是導入一個包而併不使用導入的包將會導致一個編譯錯誤。但是有時候我們隻是想利用導入包而産生的副作用:它會計算包級變量的初始化表達式和執行導入包的init初始化函數(§2.6.2)。這時候我們需要抑製“unused import”編譯錯誤,我們可以用下劃線`_`來重命名導入的包。像往常一樣,下劃線`_`爲空白標識符,併不能被訪問。 +如果只是导入一个包而并不使用导入的包将会导致一个编译错误。但是有时候我们只是想利用导入包而产生的副作用:它会计算包级变量的初始化表达式和执行导入包的init初始化函数(§2.6.2)。这时候我们需要抑制“unused import”编译错误,我们可以用下划线`_`来重命名导入的包。像往常一样,下划线`_`为空白标识符,并不能被访问。 ```Go import _ "image/png" // register PNG decoder ``` -這個被稱爲包的匿名導入。它通常是用來實現一個編譯時機製,然後通過在main主程序入口選擇性地導入附加的包。首先,讓我們看看如何使用該特性,然後再看看它是如何工作的。 +这个被称为包的匿名导入。它通常是用来实现一个编译时机制,然后通过在main主程序入口选择性地导入附加的包。首先,让我们看看如何使用该特性,然后再看看它是如何工作的。 -標準庫的image圖像包包含了一個`Decode`函數,用於從`io.Reader`接口讀取數據併解碼圖像,它調用底層註冊的圖像解碼器來完成任務,然後返迴image.Image類型的圖像。使用`image.Decode`很容易編寫一個圖像格式的轉換工具,讀取一種格式的圖像,然後編碼爲另一種圖像格式: +标准库的image图像包包含了一个`Decode`函数,用于从`io.Reader`接口读取数据并解码图像,它调用底层注册的图像解码器来完成任务,然后返回image.Image类型的图像。使用`image.Decode`很容易编写一个图像格式的转换工具,读取一种格式的图像,然后编码为另一种图像格式: gopl.io/ch10/jpeg ```Go @@ -42,7 +42,7 @@ func toJPEG(in io.Reader, out io.Writer) error { } ``` -如果我們將`gopl.io/ch3/mandelbrot`(§3.3)的輸出導入到這個程序的標準輸入,它將解碼輸入的PNG格式圖像,然後轉換爲JPEG格式的圖像輸出(圖3.3)。 +如果我们将`gopl.io/ch3/mandelbrot`(§3.3)的输出导入到这个程序的标准输入,它将解码输入的PNG格式图像,然后转换为JPEG格式的图像输出(图3.3)。 ``` $ go build gopl.io/ch3/mandelbrot @@ -51,7 +51,7 @@ $ ./mandelbrot | ./jpeg >mandelbrot.jpg Input format = png ``` -要註意image/png包的匿名導入語句。如果沒有這一行語句,程序依然可以編譯和運行,但是它將不能正確識别和解碼PNG格式的圖像: +要注意image/png包的匿名导入语句。如果没有这一行语句,程序依然可以编译和运行,但是它将不能正确识别和解码PNG格式的图像: ``` $ go build gopl.io/ch10/jpeg @@ -59,7 +59,7 @@ $ ./mandelbrot | ./jpeg >mandelbrot.jpg jpeg: image: unknown format ``` -下面的代碼演示了它的工作機製。標準庫還提供了GIF、PNG和JPEG等格式圖像的解碼器,用戶也可以提供自己的解碼器,但是爲了保持程序體積較小,很多解碼器併沒有被全部包含,除非是明確需要支持的格式。image.Decode函數在解碼時會依次査詢支持的格式列表。每個格式驅動列表的每個入口指定了四件事情:格式的名稱;一個用於描述這種圖像數據開頭部分模式的字符串,用於解碼器檢測識别;一個Decode函數用於完成解碼圖像工作;一個DecodeConfig函數用於解碼圖像的大小和顔色空間的信息。每個驅動入口是通過調用image.RegisterFormat函數註冊,一般是在每個格式包的init初始化函數中調用,例如image/png包是這樣註冊的: +下面的代码演示了它的工作机制。标准库还提供了GIF、PNG和JPEG等格式图像的解码器,用户也可以提供自己的解码器,但是为了保持程序体积较小,很多解码器并没有被全部包含,除非是明确需要支持的格式。image.Decode函数在解码时会依次查询支持的格式列表。每个格式驱动列表的每个入口指定了四件事情:格式的名称;一个用于描述这种图像数据开头部分模式的字符串,用于解码器检测识别;一个Decode函数用于完成解码图像工作;一个DecodeConfig函数用于解码图像的大小和颜色空间的信息。每个驱动入口是通过调用image.RegisterFormat函数注册,一般是在每个格式包的init初始化函数中调用,例如image/png包是这样注册的: ```Go package png // image/png @@ -73,9 +73,9 @@ func init() { } ``` -最終的效果是,主程序隻需要匿名導入特定圖像驅動包就可以用image.Decode解碼對應格式的圖像了。 +最终的效果是,主程序只需要匿名导入特定图像驱动包就可以用image.Decode解码对应格式的图像了。 -數據庫包database/sql也是采用了類似的技術,讓用戶可以根據自己需要選擇導入必要的數據庫驅動。例如: +数据库包database/sql也是采用了类似的技术,让用户可以根据自己需要选择导入必要的数据库驱动。例如: ```Go import ( @@ -89,6 +89,6 @@ db, err = sql.Open("mysql", dbname) // OK db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3" ``` -**練習 10.1:** 擴展jpeg程序,以支持任意圖像格式之間的相互轉換,使用image.Decode檢測支持的格式類型,然後通過flag命令行標誌參數選擇輸出的格式。 +**练习 10.1:** 扩展jpeg程序,以支持任意图像格式之间的相互转换,使用image.Decode检测支持的格式类型,然后通过flag命令行标志参数选择输出的格式。 -**練習 10.2:** 設計一個通用的壓縮文件讀取框架,用來讀取ZIP(archive/zip)和POSIX tar(archive/tar)格式壓縮的文檔。使用類似上面的註冊技術來擴展支持不同的壓縮格式,然後根據需要通過匿名導入選擇導入要支持的壓縮格式的驅動包。 +**练习 10.2:** 设计一个通用的压缩文件读取框架,用来读取ZIP(archive/zip)和POSIX tar(archive/tar)格式压缩的文档。使用类似上面的注册技术来扩展支持不同的压缩格式,然后根据需要通过匿名导入选择导入要支持的压缩格式的驱动包。 diff --git a/ch10/ch10-06.md b/ch10/ch10-06.md index 02ed5cfc..32dd38e9 100644 --- a/ch10/ch10-06.md +++ b/ch10/ch10-06.md @@ -1,22 +1,22 @@ ## 10.6. 包和命名 -在本節中,我們將提供一些關於Go語言獨特的包和成員命名的約定。 +在本节中,我们将提供一些关于Go语言独特的包和成员命名的约定。 -當創建一個包,一般要用短小的包名,但也不能太短導致難以理解。標準庫中最常用的包有bufio、bytes、flag、fmt、http、io、json、os、sort、sync和time等包。 +当创建一个包,一般要用短小的包名,但也不能太短导致难以理解。标准库中最常用的包有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關鍵字衝突。 +包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。 -要避免包名有其它的含義。例如,2.5節中我們的溫度轉換包最初使用了temp包名,雖然併沒有持續多久。但這是一個糟糕的嚐試,因爲temp幾乎是臨時變量的同義詞。然後我們有一段時間使用了temperature作爲包名,雖然名字併沒有表達包的眞實用途。最後我們改成了和strconv標準包類似的tempconv包名,這個名字比之前的就好多了。 +要避免包名有其它的含义。例如,2.5节中我们的温度转换包最初使用了temp包名,虽然并没有持续多久。但这是一个糟糕的尝试,因为temp几乎是临时变量的同义词。然后我们有一段时间使用了temperature作为包名,虽然名字并没有表达包的真实用途。最后我们改成了和strconv标准包类似的tempconv包名,这个名字比之前的就好多了。 -現在讓我們看看如何命名包的成員。由於是通過包的導入名字引入包里面的成員,例如fmt.Println,同時包含了包名和成員名信息。因此,我們一般併不需要關註Println的具體內容,因爲fmt包名已經包含了這個信息。當設計一個包的時候,需要考慮包名和成員名兩個部分如何很好地配合。下面有一些例子: +现在让我们看看如何命名包的成员。由于是通过包的导入名字引入包里面的成员,例如fmt.Println,同时包含了包名和成员名信息。因此,我们一般并不需要关注Println的具体内容,因为fmt包名已经包含了这个信息。当设计一个包的时候,需要考虑包名和成员名两个部分如何很好地配合。下面有一些例子: ``` bytes.Equal flag.Int http.Get json.Marshal ``` -我們可以看到一些常用的命名模式。strings包提供了和字符串相關的諸多操作: +我们可以看到一些常用的命名模式。strings包提供了和字符串相关的诸多操作: ```Go package strings @@ -30,9 +30,9 @@ type Reader struct{ /* ... */ } func NewReader(s string) *Reader ``` -字符串string本身併沒有出現在每個成員名字中。因爲用戶會這樣引用這些成員strings.Index、strings.Replacer等。 +字符串string本身并没有出现在每个成员名字中。因为用户会这样引用这些成员strings.Index、strings.Replacer等。 -其它一些包,可能隻描述了單一的數據類型,例如html/template和math/rand等,隻暴露一個主要的數據結構和與它相關的方法,還有一個以New命名的函數用於創建實例。 +其它一些包,可能只描述了单一的数据类型,例如html/template和math/rand等,只暴露一个主要的数据结构和与它相关的方法,还有一个以New命名的函数用于创建实例。 ```Go package rand // "math/rand" @@ -41,6 +41,6 @@ type Rand struct{ /* ... */ } func New(source Source) *Rand ``` -這可能導致一些名字重複,例如template.Template或rand.Rand,這就是爲什麽這些種類的包名往往特别短的原因之一。 +这可能导致一些名字重复,例如template.Template或rand.Rand,这就是为什么这些种类的包名往往特别短的原因之一。 -在另一個極端,還有像net/http包那樣含有非常多的名字和種類不多的數據類型,因爲它們都是要執行一個複雜的複合任務。盡管有將近二十種類型和更多的函數,但是包中最重要的成員名字卻是簡單明了的:Get、Post、Handle、Error、Client、Server等。 +在另一个极端,还有像net/http包那样含有非常多的名字和种类不多的数据类型,因为它们都是要执行一个复杂的复合任务。尽管有将近二十种类型和更多的函数,但是包中最重要的成员名字却是简单明了的:Get、Post、Handle、Error、Client、Server等。 diff --git a/ch10/ch10-07-1.md b/ch10/ch10-07-1.md index fe1ac255..7e02bb0a 100644 --- a/ch10/ch10-07-1.md +++ b/ch10/ch10-07-1.md @@ -1,13 +1,13 @@ -### 10.7.1. 工作區結構 +### 10.7.1. 工作区结构 -對於大多數的Go語言用戶,隻需要配置一個名叫GOPATH的環境變量,用來指定當前工作目録卽可。當需要切換到不同工作區的時候,隻要更新GOPATH就可以了。例如,我們在編寫本書時將GOPATH設置爲`$HOME/gobook`: +对于大多数的Go语言用户,只需要配置一个名叫GOPATH的环境变量,用来指定当前工作目录即可。当需要切换到不同工作区的时候,只要更新GOPATH就可以了。例如,我们在编写本书时将GOPATH设置为`$HOME/gobook`: ``` $ export GOPATH=$HOME/gobook $ go get gopl.io/... ``` -當你用前面介紹的命令下載本書全部的例子源碼之後,你的當前工作區的目録結構應該是這樣的: +当你用前面介绍的命令下载本书全部的例子源码之后,你的当前工作区的目录结构应该是这样的: ``` GOPATH/ @@ -34,11 +34,11 @@ GOPATH/ ... ``` -GOPATH對應的工作區目録有三個子目録。其中src子目録用於存儲源代碼。每個包被保存在與$GOPATH/src的相對路徑爲包導入路徑的子目録中,例如gopl.io/ch1/helloworld相對應的路徑目録。我們看到,一個GOPATH工作區的src目録中可能有多個獨立的版本控製繫統,例如gopl.io和golang.org分别對應不同的Git倉庫。其中pkg子目録用於保存編譯後的包的目標文件,bin子目録用於保存編譯後的可執行程序,例如helloworld可執行程序。 +GOPATH对应的工作区目录有三个子目录。其中src子目录用于存储源代码。每个包被保存在与$GOPATH/src的相对路径为包导入路径的子目录中,例如gopl.io/ch1/helloworld相对应的路径目录。我们看到,一个GOPATH工作区的src目录中可能有多个独立的版本控制系统,例如gopl.io和golang.org分别对应不同的Git仓库。其中pkg子目录用于保存编译后的包的目标文件,bin子目录用于保存编译后的可执行程序,例如helloworld可执行程序。 -第二個環境變量GOROOT用來指定Go的安裝目録,還有它自帶的標準庫包的位置。GOROOT的目録結構和GOPATH類似,因此存放fmt包的源代碼對應目録應該爲$GOROOT/src/fmt。用戶一般不需要設置GOROOT,默認情況下Go語言安裝工具會將其設置爲安裝的目録路徑。 +第二个环境变量GOROOT用来指定Go的安装目录,还有它自带的标准库包的位置。GOROOT的目录结构和GOPATH类似,因此存放fmt包的源代码对应目录应该为$GOROOT/src/fmt。用户一般不需要设置GOROOT,默认情况下Go语言安装工具会将其设置为安装的目录路径。 -其中`go env`命令用於査看Go語音工具涉及的所有環境變量的值,包括未設置環境變量的默認值。GOOS環境變量用於指定目標操作繫統(例如android、linux、darwin或windows),GOARCH環境變量用於指定處理器的類型,例如amd64、386或arm等。雖然GOPATH環境變量是唯一必需要設置的,但是其它環境變量也會偶爾用到。 +其中`go env`命令用于查看Go语音工具涉及的所有环境变量的值,包括未设置环境变量的默认值。GOOS环境变量用于指定目标操作系统(例如android、linux、darwin或windows),GOARCH环境变量用于指定处理器的类型,例如amd64、386或arm等。虽然GOPATH环境变量是唯一必需要设置的,但是其它环境变量也会偶尔用到。 ``` $ go env diff --git a/ch10/ch10-07-2.md b/ch10/ch10-07-2.md index 6da37e5d..a2f1a4f9 100644 --- a/ch10/ch10-07-2.md +++ b/ch10/ch10-07-2.md @@ -1,10 +1,10 @@ -### 10.7.2. 下載包 +### 10.7.2. 下载包 -使用Go語言工具箱的go命令,不僅可以根據包導入路徑找到本地工作區的包,甚至可以從互聯網上找到和更新包。 +使用Go语言工具箱的go命令,不仅可以根据包导入路径找到本地工作区的包,甚至可以从互联网上找到和更新包。 -使用命令`go get`可以下載一個單一的包或者用`...`下載整個子目録里面的每個包。Go語言工具箱的go命令同時計算併下載所依賴的每個包,這也是前一個例子中golang.org/x/net/html自動出現在本地工作區目録的原因。 +使用命令`go get`可以下载一个单一的包或者用`...`下载整个子目录里面的每个包。Go语言工具箱的go命令同时计算并下载所依赖的每个包,这也是前一个例子中golang.org/x/net/html自动出现在本地工作区目录的原因。 -一旦`go get`命令下載了包,然後就是安裝包或包對應的可執行的程序。我們將在下一節再關註它的細節,現在隻是展示整個下載過程是如何的簡單。第一個命令是獲取golint工具,它用於檢測Go源代碼的編程風格是否有問題。第二個命令是用golint命令對2.6.2節的gopl.io/ch2/popcount包代碼進行編碼風格檢査。它友好地報告了忘記了包的文檔: +一旦`go get`命令下载了包,然后就是安装包或包对应的可执行的程序。我们将在下一节再关注它的细节,现在只是展示整个下载过程是如何的简单。第一个命令是获取golint工具,它用于检测Go源代码的编程风格是否有问题。第二个命令是用golint命令对2.6.2节的gopl.io/ch2/popcount包代码进行编码风格检查。它友好地报告了忘记了包的文档: ``` $ go get github.com/golang/lint/golint @@ -13,9 +13,9 @@ src/gopl.io/ch2/popcount/main.go:1:1: package comment should be of the form "Package popcount ..." ``` -`go get`命令支持當前流行的託管網站GitHub、Bitbucket和Launchpad,可以直接向它們的版本控製繫統請求代碼。對於其它的網站,你可能需要指定版本控製繫統的具體路徑和協議,例如 Git或Mercurial。運行`go help importpath`獲取相關的信息。 +`go get`命令支持当前流行的托管网站GitHub、Bitbucket和Launchpad,可以直接向它们的版本控制系统请求代码。对于其它的网站,你可能需要指定版本控制系统的具体路径和协议,例如 Git或Mercurial。运行`go help importpath`获取相关的信息。 -`go get`命令獲取的代碼是眞實的本地存儲倉庫,而不僅僅隻是複製源文件,因此你依然可以使用版本管理工具比較本地代碼的變更或者切換到其它的版本。例如golang.org/x/net包目録對應一個Git倉庫: +`go get`命令获取的代码是真实的本地存储仓库,而不仅仅只是复制源文件,因此你依然可以使用版本管理工具比较本地代码的变更或者切换到其它的版本。例如golang.org/x/net包目录对应一个Git仓库: ``` $ cd $GOPATH/src/golang.org/x/net @@ -24,7 +24,7 @@ origin https://go.googlesource.com/net (fetch) origin https://go.googlesource.com/net (push) ``` -需要註意的是導入路徑含有的網站域名和本地Git倉庫對應遠程服務地址併不相同,眞實的Git地址是go.googlesource.com。這其實是Go語言工具的一個特性,可以讓包用一個自定義的導入路徑,但是眞實的代碼卻是由更通用的服務提供,例如googlesource.com或github.com。因爲頁面 https://golang.org/x/net/html 包含了如下的元數據,它告訴Go語言的工具當前包眞實的Git倉庫託管地址: +需要注意的是导入路径含有的网站域名和本地Git仓库对应远程服务地址并不相同,真实的Git地址是go.googlesource.com。这其实是Go语言工具的一个特性,可以让包用一个自定义的导入路径,但是真实的代码却是由更通用的服务提供,例如googlesource.com或github.com。因为页面 https://golang.org/x/net/html 包含了如下的元数据,它告诉Go语言的工具当前包真实的Git仓库托管地址: ``` $ go build gopl.io/ch1/fetch @@ -33,8 +33,8 @@ $ ./fetch https://golang.org/x/net/html | grep go-import content="golang.org/x/net git https://go.googlesource.com/net"> ``` -如果指定`-u`命令行標誌參數,`go get`命令將確保所有的包和依賴的包的版本都是最新的,然後重新編譯和安裝它們。如果不包含該標誌參數的話,而且如果包已經在本地存在,那麽代碼那麽將不會被自動更新。 +如果指定`-u`命令行标志参数,`go get`命令将确保所有的包和依赖的包的版本都是最新的,然后重新编译和安装它们。如果不包含该标志参数的话,而且如果包已经在本地存在,那么代码那么将不会被自动更新。 -`go get -u`命令隻是簡單地保證每個包是最新版本,如果是第一次下載包則是比較很方便的;但是對於發布程序則可能是不合適的,因爲本地程序可能需要對依賴的包做精確的版本依賴管理。通常的解決方案是使用vendor的目録用於存儲依賴包的固定版本的源代碼,對本地依賴的包的版本更新也是謹慎和持續可控的。在Go1.5之前,一般需要脩改包的導入路徑,所以複製後golang.org/x/net/html導入路徑可能會變爲gopl.io/vendor/golang.org/x/net/html。最新的Go語言命令已經支持vendor特性,但限於篇幅這里併不討論vendor的具體細節。不過可以通過`go help gopath`命令査看Vendor的幫助文檔。 +`go get -u`命令只是简单地保证每个包是最新版本,如果是第一次下载包则是比较很方便的;但是对于发布程序则可能是不合适的,因为本地程序可能需要对依赖的包做精确的版本依赖管理。通常的解决方案是使用vendor的目录用于存储依赖包的固定版本的源代码,对本地依赖的包的版本更新也是谨慎和持续可控的。在Go1.5之前,一般需要修改包的导入路径,所以复制后golang.org/x/net/html导入路径可能会变为gopl.io/vendor/golang.org/x/net/html。最新的Go语言命令已经支持vendor特性,但限于篇幅这里并不讨论vendor的具体细节。不过可以通过`go help gopath`命令查看Vendor的帮助文档。 -**練習 10.3:** 從 http://gopl.io/ch1/helloworld?go-get=1 獲取內容,査看本書的代碼的眞實託管的網址(`go get`請求HTML頁面時包含了`go-get`參數,以區别普通的瀏覽器請求)。 +**练习 10.3:** 从 http://gopl.io/ch1/helloworld?go-get=1 获取内容,查看本书的代码的真实托管的网址(`go get`请求HTML页面时包含了`go-get`参数,以区别普通的浏览器请求)。 diff --git a/ch10/ch10-07-3.md b/ch10/ch10-07-3.md index d3b765f2..f96f0894 100644 --- a/ch10/ch10-07-3.md +++ b/ch10/ch10-07-3.md @@ -1,10 +1,10 @@ -### 10.7.3. 構建包 +### 10.7.3. 构建包 -`go build`命令編譯命令行參數指定的每個包。如果包是一個庫,則忽略輸出結果;這可以用於檢測包的可以正確編譯的。如果包的名字是main,`go build`將調用連接器在當前目録創建一個可執行程序;以導入路徑的最後一段作爲可執行程序的名字。 +`go build`命令编译命令行参数指定的每个包。如果包是一个库,则忽略输出结果;这可以用于检测包的可以正确编译的。如果包的名字是main,`go build`将调用连接器在当前目录创建一个可执行程序;以导入路径的最后一段作为可执行程序的名字。 -因爲每個目録隻包含一個包,因此每個對應可執行程序或者叫Unix術語中的命令的包,會要求放到一個獨立的目録中。這些目録有時候會放在名叫cmd目録的子目録下面,例如用於提供Go文檔服務的golang.org/x/tools/cmd/godoc命令就是放在cmd子目録(§10.7.4)。 +因为每个目录只包含一个包,因此每个对应可执行程序或者叫Unix术语中的命令的包,会要求放到一个独立的目录中。这些目录有时候会放在名叫cmd目录的子目录下面,例如用于提供Go文档服务的golang.org/x/tools/cmd/godoc命令就是放在cmd子目录(§10.7.4)。 -每個包可以由它們的導入路徑指定,就像前面看到的那樣,或者用一個相對目録的路徑知指定,相對路徑必須以`.`或`..`開頭。如果沒有指定參數,那麽默認指定爲當前目録對應的包。 下面的命令用於構建同一個包, 雖然它們的寫法各不相同: +每个包可以由它们的导入路径指定,就像前面看到的那样,或者用一个相对目录的路径知指定,相对路径必须以`.`或`..`开头。如果没有指定参数,那么默认指定为当前目录对应的包。 下面的命令用于构建同一个包, 虽然它们的写法各不相同: ``` $ cd $GOPATH/src/gopl.io/ch1/helloworld @@ -25,7 +25,7 @@ $ cd $GOPATH $ go build ./src/gopl.io/ch1/helloworld ``` -但不能這樣: +但不能这样: ``` $ cd $GOPATH @@ -33,7 +33,7 @@ $ go build src/gopl.io/ch1/helloworld Error: cannot find package "src/gopl.io/ch1/helloworld". ``` -也可以指定包的源文件列表,這一般這隻用於構建一些小程序或做一些臨時性的實驗。如果是main包,將會以第一個Go源文件的基礎文件名作爲最終的可執行程序的名字。 +也可以指定包的源文件列表,这一般这只用于构建一些小程序或做一些临时性的实验。如果是main包,将会以第一个Go源文件的基础文件名作为最终的可执行程序的名字。 ``` $ cat quoteargs.go @@ -52,22 +52,22 @@ $ ./quoteargs one "two three" four\ five ["one" "two three" "four five"] ``` -特别是對於這類一次性運行的程序,我們希望盡快的構建併運行它。`go run`命令實際上是結合了構建和運行的兩個步驟: +特别是对于这类一次性运行的程序,我们希望尽快的构建并运行它。`go run`命令实际上是结合了构建和运行的两个步骤: ``` $ go run quoteargs.go one "two three" four\ five ["one" "two three" "four five"] ``` -第一行的參數列表中,第一個不是以`.go`結尾的將作爲可執行程序的參數運行。 +第一行的参数列表中,第一个不是以`.go`结尾的将作为可执行程序的参数运行。 -默認情況下,`go build`命令構建指定的包和它依賴的包,然後丟棄除了最後的可執行文件之外所有的中間編譯結果。依賴分析和編譯過程雖然都是很快的,但是隨着項目增加到幾十個包和成韆上萬行代碼,依賴關繫分析和編譯時間的消耗將變的可觀,有時候可能需要幾秒種,卽使這些依賴項沒有改變。 +默认情况下,`go build`命令构建指定的包和它依赖的包,然后丢弃除了最后的可执行文件之外所有的中间编译结果。依赖分析和编译过程虽然都是很快的,但是随着项目增加到几十个包和成千上万行代码,依赖关系分析和编译时间的消耗将变的可观,有时候可能需要几秒种,即使这些依赖项没有改变。 -`go install`命令和`go build`命令很相似,但是它會保存每個包的編譯成果,而不是將它們都丟棄。被編譯的包會被保存到$GOPATH/pkg目録下,目録路徑和 src目録路徑對應,可執行程序被保存到$GOPATH/bin目録。(很多用戶會將$GOPATH/bin添加到可執行程序的蒐索列表中。)還有,`go install`命令和`go build`命令都不會重新編譯沒有發生變化的包,這可以使後續構建更快捷。爲了方便編譯依賴的包,`go build -i`命令將安裝每個目標所依賴的包。 +`go install`命令和`go build`命令很相似,但是它会保存每个包的编译成果,而不是将它们都丢弃。被编译的包会被保存到$GOPATH/pkg目录下,目录路径和 src目录路径对应,可执行程序被保存到$GOPATH/bin目录。(很多用户会将$GOPATH/bin添加到可执行程序的搜索列表中。)还有,`go install`命令和`go build`命令都不会重新编译没有发生变化的包,这可以使后续构建更快捷。为了方便编译依赖的包,`go build -i`命令将安装每个目标所依赖的包。 -因爲編譯對應不同的操作繫統平台和CPU架構,`go install`命令會將編譯結果安裝到GOOS和GOARCH對應的目録。例如,在Mac繫統,golang.org/x/net/html包將被安裝到$GOPATH/pkg/darwin_amd64目録下的golang.org/x/net/html.a文件。 +因为编译对应不同的操作系统平台和CPU架构,`go install`命令会将编译结果安装到GOOS和GOARCH对应的目录。例如,在Mac系统,golang.org/x/net/html包将被安装到$GOPATH/pkg/darwin_amd64目录下的golang.org/x/net/html.a文件。 -針對不同操作繫統或CPU的交叉構建也是很簡單的。隻需要設置好目標對應的GOOS和GOARCH,然後運行構建命令卽可。下面交叉編譯的程序將輸出它在編譯時操作繫統和CPU類型: +针对不同操作系统或CPU的交叉构建也是很简单的。只需要设置好目标对应的GOOS和GOARCH,然后运行构建命令即可。下面交叉编译的程序将输出它在编译时操作系统和CPU类型: gopl.io/ch10/cross ```Go @@ -76,7 +76,7 @@ func main() { } ``` -下面以64位和32位環境分别執行程序: +下面以64位和32位环境分别执行程序: ``` $ go build gopl.io/ch10/cross @@ -87,19 +87,19 @@ $ ./cross darwin 386 ``` -有些包可能需要針對不同平台和處理器類型使用不同版本的代碼文件,以便於處理底層的可移植性問題或提供爲一些特定代碼提供優化。如果一個文件名包含了一個操作繫統或處理器類型名字,例如net_linux.go或asm_amd64.s,Go語言的構建工具將隻在對應的平台編譯這些文件。還有一個特别的構建註釋註釋可以提供更多的構建過程控製。例如,文件中可能包含下面的註釋: +有些包可能需要针对不同平台和处理器类型使用不同版本的代码文件,以便于处理底层的可移植性问题或提供为一些特定代码提供优化。如果一个文件名包含了一个操作系统或处理器类型名字,例如net_linux.go或asm_amd64.s,Go语言的构建工具将只在对应的平台编译这些文件。还有一个特别的构建注释注释可以提供更多的构建过程控制。例如,文件中可能包含下面的注释: ```Go // +build linux darwin ``` -在包聲明和包註釋的前面,該構建註釋參數告訴`go build`隻在編譯程序對應的目標操作繫統是Linux或Mac OS X時才編譯這個文件。下面的構建註釋則表示不編譯這個文件: +在包声明和包注释的前面,该构建注释参数告诉`go build`只在编译程序对应的目标操作系统是Linux或Mac OS X时才编译这个文件。下面的构建注释则表示不编译这个文件: ```Go // +build ignore ``` -更多細節,可以參考go/build包的構建約束部分的文檔。 +更多细节,可以参考go/build包的构建约束部分的文档。 ``` $ go doc go/build diff --git a/ch10/ch10-07-4.md b/ch10/ch10-07-4.md index 9d7e6023..8e1fd538 100644 --- a/ch10/ch10-07-4.md +++ b/ch10/ch10-07-4.md @@ -1,8 +1,8 @@ -### 10.7.4. 包文檔 +### 10.7.4. 包文档 -Go語言的編碼風格鼓勵爲每個包提供良好的文檔。包中每個導出的成員和包聲明前都應該包含目的和用法説明的註釋。 +Go语言的编码风格鼓励为每个包提供良好的文档。包中每个导出的成员和包声明前都应该包含目的和用法说明的注释。 -Go語言中包文檔註釋一般是完整的句子,第一行是包的摘要説明,註釋後僅跟着包聲明語句。註釋中函數的參數或其它的標識符併不需要額外的引號或其它標記註明。例如,下面是fmt.Fprintf的文檔註釋。 +Go语言中包文档注释一般是完整的句子,第一行是包的摘要说明,注释后仅跟着包声明语句。注释中函数的参数或其它的标识符并不需要额外的引号或其它标记注明。例如,下面是fmt.Fprintf的文档注释。 ```Go // Fprintf formats according to a format specifier and writes to w. @@ -10,13 +10,13 @@ Go語言中包文檔註釋一般是完整的句子,第一行是包的摘要説 func Fprintf(w io.Writer, format string, a ...interface{}) (int, error) ``` -Fprintf函數格式化的細節在fmt包文檔中描述。如果註釋後僅跟着包聲明語句,那註釋對應整個包的文檔。包文檔對應的註釋隻能有一個(譯註:其實可以有多個,它們會組合成一個包文檔註釋),包註釋可以出現在任何一個源文件中。如果包的註釋內容比較長,一般會放到一個獨立的源文件中;fmt包註釋就有300行之多。這個專門用於保存包文檔的源文件通常叫doc.go。 +Fprintf函数格式化的细节在fmt包文档中描述。如果注释后仅跟着包声明语句,那注释对应整个包的文档。包文档对应的注释只能有一个(译注:其实可以有多个,它们会组合成一个包文档注释),包注释可以出现在任何一个源文件中。如果包的注释内容比较长,一般会放到一个独立的源文件中;fmt包注释就有300行之多。这个专门用于保存包文档的源文件通常叫doc.go。 -好的文檔併不需要面面俱到,文檔本身應該是簡潔但可不忽略的。事實上,Go語言的風格更喜歡簡潔的文檔,併且文檔也是需要像代碼一樣維護的。對於一組聲明語句,可以用一個精鍊的句子描述,如果是顯而易見的功能則併不需要註釋。 +好的文档并不需要面面俱到,文档本身应该是简洁但可不忽略的。事实上,Go语言的风格更喜欢简洁的文档,并且文档也是需要像代码一样维护的。对于一组声明语句,可以用一个精炼的句子描述,如果是显而易见的功能则并不需要注释。 -在本書中,隻要空間允許,我們之前很多包聲明都包含了註釋文檔,但你可以從標準庫中發現很多更好的例子。有兩個工具可以幫到你。 +在本书中,只要空间允许,我们之前很多包声明都包含了注释文档,但你可以从标准库中发现很多更好的例子。有两个工具可以帮到你。 -首先是`go doc`命令,該命令打印包的聲明和每個成員的文檔註釋,下面是整個包的文檔: +首先是`go doc`命令,该命令打印包的声明和每个成员的文档注释,下面是整个包的文档: ``` $ go doc time @@ -34,7 +34,7 @@ type Time struct { ... } ...many more... ``` -或者是某個具體包成員的註釋文檔: +或者是某个具体包成员的注释文档: ``` $ go doc time.Since @@ -44,7 +44,7 @@ func Since(t Time) Duration It is shorthand for time.Now().Sub(t). ``` -或者是某個具體包的一個方法的註釋文檔: +或者是某个具体包的一个方法的注释文档: ``` $ go doc time.Duration.Seconds @@ -53,7 +53,7 @@ func (d Duration) Seconds() float64 Seconds returns the duration as a floating-point number of seconds. ``` -該命令併不需要輸入完整的包導入路徑或正確的大小寫。下面的命令將打印encoding/json包的`(*json.Decoder).Decode`方法的文檔: +该命令并不需要输入完整的包导入路径或正确的大小写。下面的命令将打印encoding/json包的`(*json.Decoder).Decode`方法的文档: ``` $ go doc json.decode @@ -63,12 +63,12 @@ func (dec *Decoder) Decode(v interface{}) error it in the value pointed to by v. ``` -第二個工具,名字也叫godoc,它提供可以相互交叉引用的HTML頁面,但是包含和`go doc`命令相同以及更多的信息。10.1節演示了time包的文檔,11.6節將看到godoc演示可以交互的示例程序。godoc的在線服務 https://godoc.org ,包含了成韆上萬的開源包的檢索工具。 +第二个工具,名字也叫godoc,它提供可以相互交叉引用的HTML页面,但是包含和`go doc`命令相同以及更多的信息。10.1节演示了time包的文档,11.6节将看到godoc演示可以交互的示例程序。godoc的在线服务 https://godoc.org ,包含了成千上万的开源包的检索工具。 -你也可以在自己的工作區目録運行godoc服務。運行下面的命令,然後在瀏覽器査看 http://localhost:8000/pkg 頁面: +你也可以在自己的工作区目录运行godoc服务。运行下面的命令,然后在浏览器查看 http://localhost:8000/pkg 页面: ``` $ godoc -http :8000 ``` -其中`-analysis=type`和`-analysis=pointer`命令行標誌參數用於打開文檔和代碼中關於靜態分析的結果。 +其中`-analysis=type`和`-analysis=pointer`命令行标志参数用于打开文档和代码中关于静态分析的结果。 diff --git a/ch10/ch10-07-5.md b/ch10/ch10-07-5.md index 8d9a63bd..af934b44 100644 --- a/ch10/ch10-07-5.md +++ b/ch10/ch10-07-5.md @@ -1,12 +1,12 @@ -### 10.7.5. 內部包 +### 10.7.5. 内部包 -在Go語音程序中,包的封裝機製是一個重要的特性。沒有導出的標識符隻在同一個包內部可以訪問,而導出的標識符則是面向全宇宙都是可見的。 +在Go语音程序中,包的封装机制是一个重要的特性。没有导出的标识符只在同一个包内部可以访问,而导出的标识符则是面向全宇宙都是可见的。 -有時候,一個中間的狀態可能也是有用的,對於一小部分信任的包是可見的,但併不是對所有調用者都可見。例如,當我們計劃將一個大的包拆分爲很多小的更容易維護的子包,但是我們併不想將內部的子包結構也完全暴露出去。同時,我們可能還希望在內部子包之間共享一些通用的處理包,或者我們隻是想實驗一個新包的還併不穩定的接口,暫時隻暴露給一些受限製的用戶使用。 +有时候,一个中间的状态可能也是有用的,对于一小部分信任的包是可见的,但并不是对所有调用者都可见。例如,当我们计划将一个大的包拆分为很多小的更容易维护的子包,但是我们并不想将内部的子包结构也完全暴露出去。同时,我们可能还希望在内部子包之间共享一些通用的处理包,或者我们只是想实验一个新包的还并不稳定的接口,暂时只暴露给一些受限制的用户使用。 ![](../images/ch10-01.png) -爲了滿足這些需求,Go語言的構建工具對包含internal名字的路徑段的包導入路徑做了特殊處理。這種包叫internal包,一個internal包隻能被和internal目録有同一個父目録的包所導入。例如,net/http/internal/chunked內部包隻能被net/http/httputil或net/http包導入,但是不能被net/url包導入。不過net/url包卻可以導入net/http/httputil包。 +为了满足这些需求,Go语言的构建工具对包含internal名字的路径段的包导入路径做了特殊处理。这种包叫internal包,一个internal包只能被和internal目录有同一个父目录的包所导入。例如,net/http/internal/chunked内部包只能被net/http/httputil或net/http包导入,但是不能被net/url包导入。不过net/url包却可以导入net/http/httputil包。 ``` net/http diff --git a/ch10/ch10-07-6.md b/ch10/ch10-07-6.md index 996692a7..91ad830c 100644 --- a/ch10/ch10-07-6.md +++ b/ch10/ch10-07-6.md @@ -1,13 +1,13 @@ -### 10.7.6. 査詢包 +### 10.7.6. 查询包 -`go list`命令可以査詢可用包的信息。其最簡單的形式,可以測試包是否在工作區併打印它的導入路徑: +`go list`命令可以查询可用包的信息。其最简单的形式,可以测试包是否在工作区并打印它的导入路径: ``` $ go list github.com/go-sql-driver/mysql github.com/go-sql-driver/mysql ``` -`go list`命令的參數還可以用`"..."`表示匹配任意的包的導入路徑。我們可以用它來列表工作區中的所有包: +`go list`命令的参数还可以用`"..."`表示匹配任意的包的导入路径。我们可以用它来列表工作区中的所有包: ``` $ go list ... @@ -20,7 +20,7 @@ cmd/api ...many more... ``` -或者是特定子目録下的所有包: +或者是特定子目录下的所有包: ``` $ go list gopl.io/ch3/... @@ -33,7 +33,7 @@ gopl.io/ch3/printints gopl.io/ch3/surface ``` -或者是和某個主題相關的所有包: +或者是和某个主题相关的所有包: ``` $ go list ...xml... @@ -41,7 +41,7 @@ encoding/xml gopl.io/ch7/xmlselect ``` -`go list`命令還可以獲取每個包完整的元信息,而不僅僅隻是導入路徑,這些元信息可以以不同格式提供給用戶。其中`-json`命令行參數表示用JSON格式打印每個包的元信息。 +`go list`命令还可以获取每个包完整的元信息,而不仅仅只是导入路径,这些元信息可以以不同格式提供给用户。其中`-json`命令行参数表示用JSON格式打印每个包的元信息。 ``` $ go list -json hash @@ -71,7 +71,7 @@ $ go list -json hash } ``` -命令行參數`-f`則允許用戶使用text/template包(§4.6)的模闆語言定義輸出文本的格式。下面的命令將打印strconv包的依賴的包,然後用join模闆函數將結果鏈接爲一行,連接時每個結果之間用一個空格分隔: +命令行参数`-f`则允许用户使用text/template包(§4.6)的模板语言定义输出文本的格式。下面的命令将打印strconv包的依赖的包,然后用join模板函数将结果链接为一行,连接时每个结果之间用一个空格分隔: {% raw %} ``` @@ -80,7 +80,7 @@ errors math runtime unicode/utf8 unsafe ``` {% endraw %} -譯註:上面的命令在Windows的命令行運行會遇到`template: main:1: unclosed action`的錯誤。産生這個錯誤的原因是因爲命令行對命令中的`" "`參數進行了轉義處理。可以按照下面的方法解決轉義字符串的問題: +译注:上面的命令在Windows的命令行运行会遇到`template: main:1: unclosed action`的错误。产生这个错误的原因是因为命令行对命令中的`" "`参数进行了转义处理。可以按照下面的方法解决转义字符串的问题: {% raw %} ``` @@ -88,7 +88,7 @@ $ go list -f "{{join .Deps \" \"}}" strconv ``` {% endraw %} -下面的命令打印compress子目録下所有包的依賴包列表: +下面的命令打印compress子目录下所有包的依赖包列表: {% raw %} ``` @@ -101,7 +101,7 @@ compress/zlib -> bufio compress/flate errors fmt hash hash/adler32 io ``` {% endraw %} -譯註:Windows下有同樣有問題,要避免轉義字符串的榦擾: +译注:Windows下有同样有问题,要避免转义字符串的干扰: {% raw %} ``` @@ -109,8 +109,8 @@ $ go list -f "{{.ImportPath}} -> {{join .Imports \" \"}}" compress/... ``` {% endraw %} -`go list`命令對於一次性的交互式査詢或自動化構建或測試腳本都很有幫助。我們將在11.2.4節中再次使用它。每個子命令的更多信息,包括可設置的字段和意義,可以用`go help list`命令査看。 +`go list`命令对于一次性的交互式查询或自动化构建或测试脚本都很有帮助。我们将在11.2.4节中再次使用它。每个子命令的更多信息,包括可设置的字段和意义,可以用`go help list`命令查看。 -在本章,我們解釋了Go語言工具中除了測試命令之外的所有重要的子命令。在下一章,我們將看到如何用`go test`命令去運行Go語言程序中的測試代碼。 +在本章,我们解释了Go语言工具中除了测试命令之外的所有重要的子命令。在下一章,我们将看到如何用`go test`命令去运行Go语言程序中的测试代码。 -**練習 10.4:** 創建一個工具,根據命令行指定的參數,報告工作區所有依賴指定包的其它包集合。提示:你需要運行`go list`命令兩次,一次用於初始化包,一次用於所有包。你可能需要用encoding/json(§4.5)包來分析輸出的JSON格式的信息。 +**练习 10.4:** 创建一个工具,根据命令行指定的参数,报告工作区所有依赖指定包的其它包集合。提示:你需要运行`go list`命令两次,一次用于初始化包,一次用于所有包。你可能需要用encoding/json(§4.5)包来分析输出的JSON格式的信息。 diff --git a/ch10/ch10-07.md b/ch10/ch10-07.md index 3146659c..02c1280a 100644 --- a/ch10/ch10-07.md +++ b/ch10/ch10-07.md @@ -1,10 +1,10 @@ ## 10.7. 工具 -本章剩下的部分將討論Go語言工具箱的具體功能,包括如何下載、格式化、構建、測試和安裝Go語言編寫的程序。 +本章剩下的部分将讨论Go语言工具箱的具体功能,包括如何下载、格式化、构建、测试和安装Go语言编写的程序。 -Go語言的工具箱集合了一繫列的功能的命令集。它可以看作是一個包管理器(類似於Linux中的apt和rpm工具),用於包的査詢、計算的包依賴關繫、從遠程版本控製繫統和下載它們等任務。它也是一個構建繫統,計算文件的依賴關繫,然後調用編譯器、滙編器和連接器構建程序,雖然它故意被設計成沒有標準的make命令那麽複雜。它也是一個單元測試和基準測試的驅動程序,我們將在第11章討論測試話題。 +Go语言的工具箱集合了一系列的功能的命令集。它可以看作是一个包管理器(类似于Linux中的apt和rpm工具),用于包的查询、计算的包依赖关系、从远程版本控制系统和下载它们等任务。它也是一个构建系统,计算文件的依赖关系,然后调用编译器、汇编器和连接器构建程序,虽然它故意被设计成没有标准的make命令那么复杂。它也是一个单元测试和基准测试的驱动程序,我们将在第11章讨论测试话题。 -Go語言工具箱的命令有着類似“瑞士軍刀”的風格,帶着一打子的子命令,有一些我們經常用到,例如get、run、build和fmt等。你可以運行go或go help命令査看內置的幫助文檔,爲了査詢方便,我們列出了最常用的命令: +Go语言工具箱的命令有着类似“瑞士军刀”的风格,带着一打子的子命令,有一些我们经常用到,例如get、run、build和fmt等。你可以运行go或go help命令查看内置的帮助文档,为了查询方便,我们列出了最常用的命令: ``` $ go @@ -26,7 +26,7 @@ Use "go help [command]" for more information about a command. ... ``` -爲了達到零配置的設計目標,Go語言的工具箱很多地方都依賴各種約定。例如,根據給定的源文件的名稱,Go語言的工具可以找到源文件對應的包,因爲每個目録隻包含了單一的包,併且到的導入路徑和工作區的目録結構是對應的。給定一個包的導入路徑,Go語言的工具可以找到對應的目録中沒個實體對應的源文件。它還可以根據導入路徑找到存儲代碼倉庫的遠程服務器的URL。 +为了达到零配置的设计目标,Go语言的工具箱很多地方都依赖各种约定。例如,根据给定的源文件的名称,Go语言的工具可以找到源文件对应的包,因为每个目录只包含了单一的包,并且到的导入路径和工作区的目录结构是对应的。给定一个包的导入路径,Go语言的工具可以找到对应的目录中没个实体对应的源文件。它还可以根据导入路径找到存储代码仓库的远程服务器的URL。 {% include "./ch10-07-1.md" %} diff --git a/ch10/ch10.md b/ch10/ch10.md index 1bffd831..1992ef85 100644 --- a/ch10/ch10.md +++ b/ch10/ch10.md @@ -1,7 +1,7 @@ # 第十章 包和工具 -現在隨便一個小程序的實現都可能包含超過10000個函數。然而作者一般隻需要考慮其中很小的一部分和做很少的設計,因爲絶大部分代碼都是由他人編寫的,它們通過類似包或模塊的方式被重用。 +现在随便一个小程序的实现都可能包含超过10000个函数。然而作者一般只需要考虑其中很小的一部分和做很少的设计,因为绝大部分代码都是由他人编写的,它们通过类似包或模块的方式被重用。 -Go語言有超過100個的標準包(譯註:可以用`go list std | wc -l`命令査看標準包的具體數目),標準庫爲大多數的程序提供了必要的基礎構件。在Go的社區,有很多成熟的包被設計、共享、重用和改進,目前互聯網上已經發布了非常多的Go語音開源包,它們可以通過 http://godoc.org 檢索。在本章,我們將演示如果使用已有的包和創建新的包。 +Go语言有超过100个的标准包(译注:可以用`go list std | wc -l`命令查看标准包的具体数目),标准库为大多数的程序提供了必要的基础构件。在Go的社区,有很多成熟的包被设计、共享、重用和改进,目前互联网上已经发布了非常多的Go语音开源包,它们可以通过 http://godoc.org 检索。在本章,我们将演示如果使用已有的包和创建新的包。 -Go還自帶了工具箱,里面有很多用來簡化工作區和包管理的小工具。在本書開始的時候,我們已經見識過如何使用工具箱自帶的工具來下載、構件和運行我們的演示程序了。在本章,我們將看看這些工具的基本設計理論和嚐試更多的功能,例如打印工作區中包的文檔和査詢相關的元數據等。在下一章,我們將探討探索包的單元測試用法。 +Go还自带了工具箱,里面有很多用来简化工作区和包管理的小工具。在本书开始的时候,我们已经见识过如何使用工具箱自带的工具来下载、构件和运行我们的演示程序了。在本章,我们将看看这些工具的基本设计理论和尝试更多的功能,例如打印工作区中包的文档和查询相关的元数据等。在下一章,我们将探讨探索包的单元测试用法。 diff --git a/ch11/ch11-01.md b/ch11/ch11-01.md index db9bb3e7..11512a77 100644 --- a/ch11/ch11-01.md +++ b/ch11/ch11-01.md @@ -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文件中,有三种类型的函数:测试函数、基准测试函数、示例函数。一个测试函数是以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包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。 diff --git a/ch11/ch11-02-1.md b/ch11/ch11-02-1.md index ad5d1596..f807cf1d 100644 --- a/ch11/ch11-02-1.md +++ b/ch11/ch11-02-1.md @@ -1,10 +1,10 @@ -### 11.2.1. 隨機測試 +### 11.2.1. 随机测试 -表格驅動的測試便於構造基於精心挑選的測試數據的測試用例。另一種測試思路是隨機測試,也就是通過構造更廣泛的隨機輸入來測試探索函數的行爲。 +表格驱动的测试便于构造基于精心挑选的测试数据的测试用例。另一种测试思路是随机测试,也就是通过构造更广泛的随机输入来测试探索函数的行为。 -那麽對於一個隨機的輸入,我們如何能知道希望的輸出結果呢?這里有兩種處理策略。第一個是編寫另一個對照函數,使用簡單和清晰的算法,雖然效率較低但是行爲和要測試的函數是一致的,然後針對相同的隨機輸入檢査兩者的輸出結果。第二種是生成的隨機輸入的數據遵循特定的模式,這樣我們就可以知道期望的輸出的模式。 +那么对于一个随机的输入,我们如何能知道希望的输出结果呢?这里有两种处理策略。第一个是编写另一个对照函数,使用简单和清晰的算法,虽然效率较低但是行为和要测试的函数是一致的,然后针对相同的随机输入检查两者的输出结果。第二种是生成的随机输入的数据遵循特定的模式,这样我们就可以知道期望的输出的模式。 -下面的例子使用的是第二種方法:randomPalindrome函數用於隨機生成迴文字符串。 +下面的例子使用的是第二种方法:randomPalindrome函数用于随机生成回文字符串。 ```Go import "math/rand" @@ -37,13 +37,13 @@ func TestRandomPalindromes(t *testing.T) { } ``` -雖然隨機測試會有不確定因素,但是它也是至關重要的,我們可以從失敗測試的日誌獲取足夠的信息。在我們的例子中,輸入IsPalindrome的p參數將告訴我們眞實的數據,但是對於函數將接受更複雜的輸入,不需要保存所有的輸入,隻要日誌中簡單地記録隨機數種子卽可(像上面的方式)。有了這些隨機數初始化種子,我們可以很容易脩改測試代碼以重現失敗的隨機測試。 +虽然随机测试会有不确定因素,但是它也是至关重要的,我们可以从失败测试的日志获取足够的信息。在我们的例子中,输入IsPalindrome的p参数将告诉我们真实的数据,但是对于函数将接受更复杂的输入,不需要保存所有的输入,只要日志中简单地记录随机数种子即可(像上面的方式)。有了这些随机数初始化种子,我们可以很容易修改测试代码以重现失败的随机测试。 -通過使用當前時間作爲隨機種子,在整個過程中的每次運行測試命令時都將探索新的隨機數據。如果你使用的是定期運行的自動化測試集成繫統,隨機測試將特别有價值。 +通过使用当前时间作为随机种子,在整个过程中的每次运行测试命令时都将探索新的随机数据。如果你使用的是定期运行的自动化测试集成系统,随机测试将特别有价值。 -**練習 11.3:** TestRandomPalindromes測試函數隻測試了迴文字符串。編寫新的隨機測試生成器,用於測試隨機生成的非迴文字符串。 +**练习 11.3:** TestRandomPalindromes测试函数只测试了回文字符串。编写新的随机测试生成器,用于测试随机生成的非回文字符串。 -**練習 11.4:** 脩改randomPalindrome函數,以探索IsPalindrome是否對標點和空格做了正確處理。 +**练习 11.4:** 修改randomPalindrome函数,以探索IsPalindrome是否对标点和空格做了正确处理。 diff --git a/ch11/ch11-02-2.md b/ch11/ch11-02-2.md index 8084719c..f93ef013 100644 --- a/ch11/ch11-02-2.md +++ b/ch11/ch11-02-2.md @@ -1,8 +1,8 @@ -### 11.2.2. 測試一個命令 +### 11.2.2. 测试一个命令 -對於測試包`go test`是一個的有用的工具,但是稍加努力我們也可以用它來測試可執行程序。如果一個包的名字是 main,那麽在構建時會生成一個可執行程序,不過main包可以作爲一個包被測試器代碼導入。 +对于测试包`go test`是一个的有用的工具,但是稍加努力我们也可以用它来测试可执行程序。如果一个包的名字是 main,那么在构建时会生成一个可执行程序,不过main包可以作为一个包被测试器代码导入。 -讓我們爲2.3.2節的echo程序編寫一個測試。我們先將程序拆分爲兩個函數:echo函數完成眞正的工作,main函數用於處理命令行輸入參數和echo可能返迴的錯誤。 +让我们为2.3.2节的echo程序编写一个测试。我们先将程序拆分为两个函数:echo函数完成真正的工作,main函数用于处理命令行输入参数和echo可能返回的错误。 gopl.io/ch11/echo ```Go @@ -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 @@ -82,15 +82,15 @@ func TestEcho(t *testing.T) { } ``` -要註意的是測試代碼和産品代碼在同一個包。雖然是main包,也有對應的main入口函數,但是在測試的時候main包隻是TestEcho測試函數導入的一個普通包,里面main函數併沒有被導出,而是被忽略的。 +要注意的是测试代码和产品代码在同一个包。虽然是main包,也有对应的main入口函数,但是在测试的时候main包只是TestEcho测试函数导入的一个普通包,里面main函数并没有被导出,而是被忽略的。 -通過將測試放到表格中,我們很容易添加新的測試用例。讓我通過增加下面的測試用例來看看失敗的情況是怎麽樣的: +通过将测试放到表格中,我们很容易添加新的测试用例。让我通过增加下面的测试用例来看看失败的情况是怎么样的: ```Go {true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation! ``` -`go test`輸出如下: +`go test`输出如下: ``` $ go test gopl.io/ch11/echo @@ -100,6 +100,6 @@ FAIL FAIL gopl.io/ch11/echo 0.006s ``` -錯誤信息描述了嚐試的操作(使用Go類似語法),實際的結果和期望的結果。通過這樣的錯誤信息,你可以在檢視代碼之前就很容易定位錯誤的原因。 +错误信息描述了尝试的操作(使用Go类似语法),实际的结果和期望的结果。通过这样的错误信息,你可以在检视代码之前就很容易定位错误的原因。 -要註意的是在測試代碼中併沒有調用log.Fatal或os.Exit,因爲調用這類函數會導致程序提前退出;調用這些函數的特權應該放在main函數中。如果眞的有意外的事情導致函數發生panic異常,測試驅動應該嚐試用recover捕獲異常,然後將當前測試當作失敗處理。如果是可預期的錯誤,例如非法的用戶輸入、找不到文件或配置文件不當等應該通過返迴一個非空的error的方式處理。幸運的是(上面的意外隻是一個插麴),我們的echo示例是比較簡單的也沒有需要返迴非空error的情況。 +要注意的是在测试代码中并没有调用log.Fatal或os.Exit,因为调用这类函数会导致程序提前退出;调用这些函数的特权应该放在main函数中。如果真的有意外的事情导致函数发生panic异常,测试驱动应该尝试用recover捕获异常,然后将当前测试当作失败处理。如果是可预期的错误,例如非法的用户输入、找不到文件或配置文件不当等应该通过返回一个非空的error的方式处理。幸运的是(上面的意外只是一个插曲),我们的echo示例是比较简单的也没有需要返回非空error的情况。 diff --git a/ch11/ch11-02-3.md b/ch11/ch11-02-3.md index b9a2d5d0..31efbf93 100644 --- a/ch11/ch11-02-3.md +++ b/ch11/ch11-02-3.md @@ -1,14 +1,14 @@ -### 11.2.3. 白盒測試 +### 11.2.3. 白盒测试 -一種測試分類的方法是基於測試者是否需要了解被測試對象的內部工作原理。黑盒測試隻需要測試包公開的文檔和API行爲,內部實現對測試代碼是透明的。相反,白盒測試有訪問包內部函數和數據結構的權限,因此可以做到一下普通客戶端無法實現的測試。例如,一個白盒測試可以在每個操作之後檢測不變量的數據類型。(白盒測試隻是一個傳統的名稱,其實稱爲clear box測試會更準確。) +一种测试分类的方法是基于测试者是否需要了解被测试对象的内部工作原理。黑盒测试只需要测试包公开的文档和API行为,内部实现对测试代码是透明的。相反,白盒测试有访问包内部函数和数据结构的权限,因此可以做到一下普通客户端无法实现的测试。例如,一个白盒测试可以在每个操作之后检测不变量的数据类型。(白盒测试只是一个传统的名称,其实称为clear box测试会更准确。) -黑盒和白盒這兩種測試方法是互補的。黑盒測試一般更健壯,隨着軟件實現的完善測試代碼很少需要更新。它們可以幫助測試者了解眞是客戶的需求,也可以幫助發現API設計的一些不足之處。相反,白盒測試則可以對內部一些棘手的實現提供更多的測試覆蓋。 +黑盒和白盒这两种测试方法是互补的。黑盒测试一般更健壮,随着软件实现的完善测试代码很少需要更新。它们可以帮助测试者了解真是客户的需求,也可以帮助发现API设计的一些不足之处。相反,白盒测试则可以对内部一些棘手的实现提供更多的测试覆盖。 -我們已經看到兩種測試的例子。TestIsPalindrome測試僅僅使用導出的IsPalindrome函數,因此這是一個黑盒測試。TestEcho測試則調用了內部的echo函數,併且更新了內部的out包級變量,這兩個都是未導出的,因此這是白盒測試。 +我们已经看到两种测试的例子。TestIsPalindrome测试仅仅使用导出的IsPalindrome函数,因此这是一个黑盒测试。TestEcho测试则调用了内部的echo函数,并且更新了内部的out包级变量,这两个都是未导出的,因此这是白盒测试。 -當我們準備TestEcho測試的時候,我們脩改了echo函數使用包級的out變量作爲輸出對象,因此測試代碼可以用另一個實現代替標準輸出,這樣可以方便對比echo輸出的數據。使用類似的技術,我們可以將産品代碼的其他部分也替換爲一個容易測試的僞對象。使用僞對象的好處是我們可以方便配置,容易預測,更可靠,也更容易觀察。同時也可以避免一些不良的副作用,例如更新生産數據庫或信用卡消費行爲。 +当我们准备TestEcho测试的时候,我们修改了echo函数使用包级的out变量作为输出对象,因此测试代码可以用另一个实现代替标准输出,这样可以方便对比echo输出的数据。使用类似的技术,我们可以将产品代码的其他部分也替换为一个容易测试的伪对象。使用伪对象的好处是我们可以方便配置,容易预测,更可靠,也更容易观察。同时也可以避免一些不良的副作用,例如更新生产数据库或信用卡消费行为。 -下面的代碼演示了爲用戶提供網絡存儲的web服務中的配額檢測邏輯。當用戶使用了超過90%的存儲配額之後將發送提醒郵件。 +下面的代码演示了为用户提供网络存储的web服务中的配额检测逻辑。当用户使用了超过90%的存储配额之后将发送提醒邮件。 gopl.io/ch11/storage1 ```Go @@ -48,7 +48,7 @@ func CheckQuota(username string) { } ``` -我們想測試這個代碼,但是我們併不希望發送眞實的郵件。因此我們將郵件處理邏輯放到一個私有的notifyUser函數中。 +我们想测试这个代码,但是我们并不希望发送真实的邮件。因此我们将邮件处理逻辑放到一个私有的notifyUser函数中。 gopl.io/ch11/storage2 ```Go @@ -73,7 +73,7 @@ func CheckQuota(username string) { } ``` -現在我們可以在測試中用僞郵件發送函數替代眞實的郵件發送函數。它隻是簡單記録要通知的用戶和郵件的內容。 +现在我们可以在测试中用伪邮件发送函数替代真实的邮件发送函数。它只是简单记录要通知的用户和邮件的内容。 ```Go package storage @@ -107,7 +107,7 @@ func TestCheckQuotaNotifiesUser(t *testing.T) { } ``` -這里有一個問題:當測試函數返迴後,CheckQuota將不能正常工作,因爲notifyUsers依然使用的是測試函數的僞發送郵件函數(當更新全局對象的時候總會有這種風險)。 我們必須脩改測試代碼恢複notifyUsers原先的狀態以便後續其他的測試沒有影響,要確保所有的執行路徑後都能恢複,包括測試失敗或panic異常的情形。在這種情況下,我們建議使用defer語句來延後執行處理恢複的代碼。 +这里有一个问题:当测试函数返回后,CheckQuota将不能正常工作,因为notifyUsers依然使用的是测试函数的伪发送邮件函数(当更新全局对象的时候总会有这种风险)。 我们必须修改测试代码恢复notifyUsers原先的状态以便后续其他的测试没有影响,要确保所有的执行路径后都能恢复,包括测试失败或panic异常的情形。在这种情况下,我们建议使用defer语句来延后执行处理恢复的代码。 ```Go func TestCheckQuotaNotifiesUser(t *testing.T) { @@ -124,6 +124,6 @@ func TestCheckQuotaNotifiesUser(t *testing.T) { } ``` -這種處理模式可以用來暫時保存和恢複所有的全局變量,包括命令行標誌參數、調試選項和優化參數;安裝和移除導致生産代碼産生一些調試信息的鉤子函數;還有有些誘導生産代碼進入某些重要狀態的改變,比如超時、錯誤,甚至是一些刻意製造的併發行爲等因素。 +这种处理模式可以用来暂时保存和恢复所有的全局变量,包括命令行标志参数、调试选项和优化参数;安装和移除导致生产代码产生一些调试信息的钩子函数;还有有些诱导生产代码进入某些重要状态的改变,比如超时、错误,甚至是一些刻意制造的并发行为等因素。 -以這種方式使用全局變量是安全的,因爲go test命令併不會同時併發地執行多個測試。 +以这种方式使用全局变量是安全的,因为go test命令并不会同时并发地执行多个测试。 diff --git a/ch11/ch11-02-4.md b/ch11/ch11-02-4.md index ff43cfbf..5c1145e8 100644 --- a/ch11/ch11-02-4.md +++ b/ch11/ch11-02-4.md @@ -1,20 +1,20 @@ -### 11.2.4. 擴展測試包 +### 11.2.4. 扩展测试包 -考慮下這兩個包:net/url包,提供了URL解析的功能;net/http包,提供了web服務和HTTP客戶端的功能。如我們所料,上層的net/http包依賴下層的net/url包。然後,net/url包中的一個測試是演示不同URL和HTTP客戶端的交互行爲。也就是説,一個下層包的測試代碼導入了上層的包。 +考虑下这两个包:net/url包,提供了URL解析的功能;net/http包,提供了web服务和HTTP客户端的功能。如我们所料,上层的net/http包依赖下层的net/url包。然后,net/url包中的一个测试是演示不同URL和HTTP客户端的交互行为。也就是说,一个下层包的测试代码导入了上层的包。 ![](../images/ch11-01.png) -這樣的行爲在net/url包的測試代碼中會導致包的循環依賴,正如圖11.1中向上箭頭所示,同時正如我們在10.1節所講的,Go語言規范是禁止包的循環依賴的。 +这样的行为在net/url包的测试代码中会导致包的循环依赖,正如图11.1中向上箭头所示,同时正如我们在10.1节所讲的,Go语言规范是禁止包的循环依赖的。 -不過我們可以通過測試擴展包的方式解決循環依賴的問題,也就是在net/url包所在的目録聲明一個獨立的url_test測試擴展包。其中測試擴展包名的`_test`後綴告訴go test工具它應該建立一個額外的包來運行測試。我們將這個擴展測試包的導入路徑視作是net/url_test會更容易理解,但實際上它併不能被其他任何包導入。 +不过我们可以通过测试扩展包的方式解决循环依赖的问题,也就是在net/url包所在的目录声明一个独立的url_test测试扩展包。其中测试扩展包名的`_test`后缀告诉go test工具它应该建立一个额外的包来运行测试。我们将这个扩展测试包的导入路径视作是net/url_test会更容易理解,但实际上它并不能被其他任何包导入。 -因爲測試擴展包是一個獨立的包,所以可以導入測試代碼依賴的其他的輔助包;包內的測試代碼可能無法做到。在設計層面,測試擴展包是在所以它依賴的包的上層,正如圖11.2所示。 +因为测试扩展包是一个独立的包,所以可以导入测试代码依赖的其他的辅助包;包内的测试代码可能无法做到。在设计层面,测试扩展包是在所以它依赖的包的上层,正如图11.2所示。 ![](../images/ch11-02.png) -通過迴避循環導入依賴,擴展測試包可以更靈活的編寫測試,特别是集成測試(需要測試多個組件之間的交互),可以像普通應用程序那樣自由地導入其他包。 +通过回避循环导入依赖,扩展测试包可以更灵活的编写测试,特别是集成测试(需要测试多个组件之间的交互),可以像普通应用程序那样自由地导入其他包。 -我們可以用go list命令査看包對應目録中哪些Go源文件是産品代碼,哪些是包內測試,還哪些測試擴展包。我們以fmt包作爲一個例子:GoFiles表示産品代碼對應的Go源文件列表;也就是go build命令要編譯的部分。 +我们可以用go list命令查看包对应目录中哪些Go源文件是产品代码,哪些是包内测试,还哪些测试扩展包。我们以fmt包作为一个例子:GoFiles表示产品代码对应的Go源文件列表;也就是go build命令要编译的部分。 {% raw %} @@ -25,7 +25,7 @@ $ go list -f={{.GoFiles}} fmt {% endraw %} -TestGoFiles表示的是fmt包內部測試測試代碼,以_test.go爲後綴文件名,不過隻在測試時被構建: +TestGoFiles表示的是fmt包内部测试测试代码,以_test.go为后缀文件名,不过只在测试时被构建: {% raw %} @@ -36,9 +36,9 @@ $ go list -f={{.TestGoFiles}} fmt {% endraw %} -包的測試代碼通常都在這些文件中,不過fmt包併非如此;稍後我們再解釋export_test.go文件的作用。 +包的测试代码通常都在这些文件中,不过fmt包并非如此;稍后我们再解释export_test.go文件的作用。 -XTestGoFiles表示的是屬於測試擴展包的測試代碼,也就是fmt_test包,因此它們必須先導入fmt包。同樣,這些文件也隻是在測試時被構建運行: +XTestGoFiles表示的是属于测试扩展包的测试代码,也就是fmt_test包,因此它们必须先导入fmt包。同样,这些文件也只是在测试时被构建运行: {% raw %} @@ -49,11 +49,11 @@ $ go list -f={{.XTestGoFiles}} fmt {% endraw %} -有時候測試擴展包也需要訪問被測試包內部的代碼,例如在一個爲了避免循環導入而被獨立到外部測試擴展包的白盒測試。在這種情況下,我們可以通過一些技巧解決:我們在包內的一個_test.go文件中導出一個內部的實現給測試擴展包。因爲這些代碼隻有在測試時才需要,因此一般會放在export_test.go文件中。 +有时候测试扩展包也需要访问被测试包内部的代码,例如在一个为了避免循环导入而被独立到外部测试扩展包的白盒测试。在这种情况下,我们可以通过一些技巧解决:我们在包内的一个_test.go文件中导出一个内部的实现给测试扩展包。因为这些代码只有在测试时才需要,因此一般会放在export_test.go文件中。 -例如,fmt包的fmt.Scanf函數需要unicode.IsSpace函數提供的功能。但是爲了避免太多的依賴,fmt包併沒有導入包含鉅大表格數據的unicode包;相反fmt包有一個叫isSpace內部的簡易實現。 +例如,fmt包的fmt.Scanf函数需要unicode.IsSpace函数提供的功能。但是为了避免太多的依赖,fmt包并没有导入包含巨大表格数据的unicode包;相反fmt包有一个叫isSpace内部的简易实现。 -爲了確保fmt.isSpace和unicode.IsSpace函數的行爲一致,fmt包謹慎地包含了一個測試。是一個在測試擴展包內的白盒測試,是無法直接訪問到isSpace內部函數的,因此fmt通過一個祕密出口導出了isSpace函數。export_test.go文件就是專門用於測試擴展包的祕密出口。 +为了确保fmt.isSpace和unicode.IsSpace函数的行为一致,fmt包谨慎地包含了一个测试。是一个在测试扩展包内的白盒测试,是无法直接访问到isSpace内部函数的,因此fmt通过一个秘密出口导出了isSpace函数。export_test.go文件就是专门用于测试扩展包的秘密出口。 ```Go package fmt @@ -61,5 +61,5 @@ package fmt var IsSpace = isSpace ``` -這個測試文件併沒有定義測試代碼;它隻是通過fmt.IsSpace簡單導出了內部的isSpace函數,提供給測試擴展包使用。這個技巧可以廣泛用於位於測試擴展包的白盒測試。 +这个测试文件并没有定义测试代码;它只是通过fmt.IsSpace简单导出了内部的isSpace函数,提供给测试扩展包使用。这个技巧可以广泛用于位于测试扩展包的白盒测试。 diff --git a/ch11/ch11-02-5.md b/ch11/ch11-02-5.md index 58a91c15..a5645965 100644 --- a/ch11/ch11-02-5.md +++ b/ch11/ch11-02-5.md @@ -1,10 +1,10 @@ -### 11.2.5. 編寫有效的測試 +### 11.2.5. 编写有效的测试 -許多Go語言新人會驚異於它的極簡的測試框架。很多其它語言的測試框架都提供了識别測試函數的機製(通常使用反射或元數據),通過設置一些“setup”和“teardown”的鉤子函數來執行測試用例運行的初始化和之後的清理操作,同時測試工具箱還提供了很多類似assert斷言,值比較函數,格式化輸出錯誤信息和停止一個識别的測試等輔助函數(通常使用異常機製)。雖然這些機製可以使得測試非常簡潔,但是測試輸出的日誌卻會像火星文一般難以理解。此外,雖然測試最終也會輸出PASS或FAIL的報告,但是它們提供的信息格式卻非常不利於代碼維護者快速定位問題,因爲失敗的信息的具體含義是非常隱晦的,比如“assert: 0 == 1”或成頁的海量跟蹤日誌。 +许多Go语言新人会惊异于它的极简的测试框架。很多其它语言的测试框架都提供了识别测试函数的机制(通常使用反射或元数据),通过设置一些“setup”和“teardown”的钩子函数来执行测试用例运行的初始化和之后的清理操作,同时测试工具箱还提供了很多类似assert断言,值比较函数,格式化输出错误信息和停止一个识别的测试等辅助函数(通常使用异常机制)。虽然这些机制可以使得测试非常简洁,但是测试输出的日志却会像火星文一般难以理解。此外,虽然测试最终也会输出PASS或FAIL的报告,但是它们提供的信息格式却非常不利于代码维护者快速定位问题,因为失败的信息的具体含义是非常隐晦的,比如“assert: 0 == 1”或成页的海量跟踪日志。 -Go語言的測試風格則形成鮮明對比。它期望測試者自己完成大部分的工作,定義函數避免重複,就像普通編程那樣。編寫測試併不是一個機械的填空過程;一個測試也有自己的接口,盡管它的維護者也是測試僅有的一個用戶。一個好的測試不應該引發其他無關的錯誤信息,它隻要清晰簡潔地描述問題的癥狀卽可,有時候可能還需要一些上下文信息。在理想情況下,維護者可以在不看代碼的情況下就能根據錯誤信息定位錯誤産生的原因。一個好的測試不應該在遇到一點小錯誤時就立刻退出測試,它應該嚐試報告更多的相關的錯誤信息,因爲我們可能從多個失敗測試的模式中發現錯誤産生的規律。 +Go语言的测试风格则形成鲜明对比。它期望测试者自己完成大部分的工作,定义函数避免重复,就像普通编程那样。编写测试并不是一个机械的填空过程;一个测试也有自己的接口,尽管它的维护者也是测试仅有的一个用户。一个好的测试不应该引发其他无关的错误信息,它只要清晰简洁地描述问题的症状即可,有时候可能还需要一些上下文信息。在理想情况下,维护者可以在不看代码的情况下就能根据错误信息定位错误产生的原因。一个好的测试不应该在遇到一点小错误时就立刻退出测试,它应该尝试报告更多的相关的错误信息,因为我们可能从多个失败测试的模式中发现错误产生的规律。 -下面的斷言函數比較兩個值,然後生成一個通用的錯誤信息,併停止程序。它很方便使用也確實有效果,但是當測試失敗的時候,打印的錯誤信息卻幾乎是沒有價值的。它併沒有爲快速解決問題提供一個很好的入口。 +下面的断言函数比较两个值,然后生成一个通用的错误信息,并停止程序。它很方便使用也确实有效果,但是当测试失败的时候,打印的错误信息却几乎是没有价值的。它并没有为快速解决问题提供一个很好的入口。 ```Go import ( @@ -25,7 +25,7 @@ func TestSplit(t *testing.T) { } ``` -從這個意義上説,斷言函數犯了過早抽象的錯誤:僅僅測試兩個整數是否相同,而放棄了根據上下文提供更有意義的錯誤信息的做法。我們可以根據具體的錯誤打印一個更有價值的錯誤信息,就像下面例子那樣。測試在隻有一次重複的模式出現時引入抽象。 +从这个意义上说,断言函数犯了过早抽象的错误:仅仅测试两个整数是否相同,而放弃了根据上下文提供更有意义的错误信息的做法。我们可以根据具体的错误打印一个更有价值的错误信息,就像下面例子那样。测试在只有一次重复的模式出现时引入抽象。 ```Go func TestSplit(t *testing.T) { @@ -39,10 +39,10 @@ func TestSplit(t *testing.T) { } ``` -現在的測試不僅報告了調用的具體函數、它的輸入和結果的意義;併且打印的眞實返迴的值和期望返迴的值;併且卽使斷言失敗依然會繼續嚐試運行更多的測試。一旦我們寫了這樣結構的測試,下一步自然不是用更多的if語句來擴展測試用例,我們可以用像IsPalindrome的表驅動測試那樣來準備更多的s和sep測試用例。 +现在的测试不仅报告了调用的具体函数、它的输入和结果的意义;并且打印的真实返回的值和期望返回的值;并且即使断言失败依然会继续尝试运行更多的测试。一旦我们写了这样结构的测试,下一步自然不是用更多的if语句来扩展测试用例,我们可以用像IsPalindrome的表驱动测试那样来准备更多的s和sep测试用例。 -前面的例子併不需要額外的輔助函數,如果有可以使測試代碼更簡單的方法我們也樂意接受。(我們將在13.3節看到一個類似reflect.DeepEqual輔助函數。)開始一個好的測試的關鍵是通過實現你眞正想要的具體行爲,然後才是考慮然後簡化測試代碼。最好的接口是直接從庫的抽象接口開始,針對公共接口編寫一些測試函數。 +前面的例子并不需要额外的辅助函数,如果有可以使测试代码更简单的方法我们也乐意接受。(我们将在13.3节看到一个类似reflect.DeepEqual辅助函数。)开始一个好的测试的关键是通过实现你真正想要的具体行为,然后才是考虑然后简化测试代码。最好的接口是直接从库的抽象接口开始,针对公共接口编写一些测试函数。 -**練習11.5:** 用表格驅動的技術擴展TestSplit測試,併打印期望的輸出結果。 +**练习11.5:** 用表格驱动的技术扩展TestSplit测试,并打印期望的输出结果。 diff --git a/ch11/ch11-02-6.md b/ch11/ch11-02-6.md index a143c150..8e092af2 100644 --- a/ch11/ch11-02-6.md +++ b/ch11/ch11-02-6.md @@ -1,8 +1,8 @@ -### 11.2.6. 避免的不穩定的測試 +### 11.2.6. 避免的不稳定的测试 -如果一個應用程序對於新出現的但有效的輸入經常失敗説明程序不夠穩健;同樣如果一個測試僅僅因爲聲音變化就會導致失敗也是不合邏輯的。就像一個不夠穩健的程序會挫敗它的用戶一樣,一個脆弱性測試同樣會激怒它的維護者。最脆弱的測試代碼會在程序沒有任何變化的時候産生不同的結果,時好時壞,處理它們會耗費大量的時間但是併不會得到任何好處。 +如果一个应用程序对于新出现的但有效的输入经常失败说明程序不够稳健;同样如果一个测试仅仅因为声音变化就会导致失败也是不合逻辑的。就像一个不够稳健的程序会挫败它的用户一样,一个脆弱性测试同样会激怒它的维护者。最脆弱的测试代码会在程序没有任何变化的时候产生不同的结果,时好时坏,处理它们会耗费大量的时间但是并不会得到任何好处。 -當一個測試函數産生一個複雜的輸出如一個很長的字符串,或一個精心設計的數據結構或一個文件,它可以用於和預設的“golden”結果數據對比,用這種簡單方式寫測試是誘人的。但是隨着項目的發展,輸出的某些部分很可能會發生變化,盡管很可能是一個改進的實現導致的。而且不僅僅是輸出部分,函數複雜複製的輸入部分可能也跟着變化了,因此測試使用的輸入也就不在有效了。 +当一个测试函数产生一个复杂的输出如一个很长的字符串,或一个精心设计的数据结构或一个文件,它可以用于和预设的“golden”结果数据对比,用这种简单方式写测试是诱人的。但是随着项目的发展,输出的某些部分很可能会发生变化,尽管很可能是一个改进的实现导致的。而且不仅仅是输出部分,函数复杂复制的输入部分可能也跟着变化了,因此测试使用的输入也就不在有效了。 -避免脆弱測試代碼的方法是隻檢測你眞正關心的屬性。保持測試代碼的簡潔和內部結構的穩定。特别是對斷言部分要有所選擇。不要檢査字符串的全匹配,但是尋找相關的子字符串,因爲某些子字符串在項目的發展中是比較穩定不變的。通常編寫一個重複雜的輸出中提取必要精華信息以用於斷言是值得的,雖然這可能會帶來很多前期的工作,但是它可以幫助迅速及時脩複因爲項目演化而導致的不合邏輯的失敗測試。 +避免脆弱测试代码的方法是只检测你真正关心的属性。保持测试代码的简洁和内部结构的稳定。特别是对断言部分要有所选择。不要检查字符串的全匹配,但是寻找相关的子字符串,因为某些子字符串在项目的发展中是比较稳定不变的。通常编写一个重复杂的输出中提取必要精华信息以用于断言是值得的,虽然这可能会带来很多前期的工作,但是它可以帮助迅速及时修复因为项目演化而导致的不合逻辑的失败测试。 diff --git a/ch11/ch11-02.md b/ch11/ch11-02.md index 30613c41..8a8df995 100644 --- a/ch11/ch11-02.md +++ b/ch11/ch11-02.md @@ -1,6 +1,6 @@ -## 11.2. 測試函數 +## 11.2. 测试函数 -每個測試函數必須導入testing包。測試函數有如下的籤名: +每个测试函数必须导入testing包。测试函数有如下的签名: ```Go func TestName(t *testing.T) { @@ -8,7 +8,7 @@ func TestName(t *testing.T) { } ``` -測試函數的名字必須以Test開頭,可選的後綴名必須以大寫字母開頭: +测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头: ```Go func TestSin(t *testing.T) { /* ... */ } @@ -16,7 +16,7 @@ func TestCos(t *testing.T) { /* ... */ } func TestLog(t *testing.T) { /* ... */ } ``` -其中t參數用於報告測試失敗和附加的日誌信息。讓我們定義一個實例包gopl.io/ch11/word1,其中隻有一個函數IsPalindrome用於檢査一個字符串是否從前向後和從後向前讀都是一樣的。(下面這個實現對於一個字符串是否是迴文字符串前後重複測試了兩次;我們稍後會再討論這個問題。) +其中t参数用于报告测试失败和附加的日志信息。让我们定义一个实例包gopl.io/ch11/word1,其中只有一个函数IsPalindrome用于检查一个字符串是否从前向后和从后向前读都是一样的。(下面这个实现对于一个字符串是否是回文字符串前后重复测试了两次;我们稍后会再讨论这个问题。) gopl.io/ch11/word1 ```Go @@ -35,7 +35,7 @@ func IsPalindrome(s string) bool { } ``` -在相同的目録下,word_test.go測試文件中包含了TestPalindrome和TestNonPalindrome兩個測試函數。每一個都是測試IsPalindrome是否給出正確的結果,併使用t.Error報告失敗信息: +在相同的目录下,word_test.go测试文件中包含了TestPalindrome和TestNonPalindrome两个测试函数。每一个都是测试IsPalindrome是否给出正确的结果,并使用t.Error报告失败信息: ```Go package word @@ -58,7 +58,7 @@ func TestNonPalindrome(t *testing.T) { } ``` -`go test`命令如果沒有參數指定包那麽將默認采用當前目録對應的包(和`go build`命令一樣)。我們可以用下面的命令構建和運行測試。 +`go test`命令如果没有参数指定包那么将默认采用当前目录对应的包(和`go build`命令一样)。我们可以用下面的命令构建和运行测试。 ``` $ cd $GOPATH/src/gopl.io/ch11/word1 @@ -66,7 +66,7 @@ $ go test ok gopl.io/ch11/word1 0.008s ``` -結果還比較滿意,我們運行了這個程序, 不過沒有提前退出是因爲還沒有遇到BUG報告。不過一個法国名爲“Noelle Eve Elleon”的用戶會抱怨IsPalindrome函數不能識别“été”。另外一個來自美国中部用戶的抱怨則是不能識别“A man, a plan, a canal: Panama.”。執行特殊和小的BUG報告爲我們提供了新的更自然的測試用例。 +结果还比较满意,我们运行了这个程序, 不过没有提前退出是因为还没有遇到BUG报告。不过一个法国名为“Noelle Eve Elleon”的用户会抱怨IsPalindrome函数不能识别“été”。另外一个来自美国中部用户的抱怨则是不能识别“A man, a plan, a canal: Panama.”。执行特殊和小的BUG报告为我们提供了新的更自然的测试用例。 ```Go func TestFrenchPalindrome(t *testing.T) { @@ -83,9 +83,9 @@ func TestCanalPalindrome(t *testing.T) { } ``` -爲了避免兩次輸入較長的字符串,我們使用了提供了有類似Printf格式化功能的 Errorf函數來滙報錯誤結果。 +为了避免两次输入较长的字符串,我们使用了提供了有类似Printf格式化功能的 Errorf函数来汇报错误结果。 -當添加了這兩個測試用例之後,`go test`返迴了測試失敗的信息。 +当添加了这两个测试用例之后,`go test`返回了测试失败的信息。 ``` $ go test @@ -97,11 +97,11 @@ FAIL FAIL gopl.io/ch11/word1 0.014s ``` -先編寫測試用例併觀察到測試用例觸發了和用戶報告的錯誤相同的描述是一個好的測試習慣。隻有這樣,我們才能定位我們要眞正解決的問題。 +先编写测试用例并观察到测试用例触发了和用户报告的错误相同的描述是一个好的测试习惯。只有这样,我们才能定位我们要真正解决的问题。 -先寫測試用例的另外的好處是,運行測試通常會比手工描述報告的處理更快,這讓我們可以進行快速地迭代。如果測試集有很多運行緩慢的測試,我們可以通過隻選擇運行某些特定的測試來加快測試速度。 +先写测试用例的另外的好处是,运行测试通常会比手工描述报告的处理更快,这让我们可以进行快速地迭代。如果测试集有很多运行缓慢的测试,我们可以通过只选择运行某些特定的测试来加快测试速度。 -參數`-v`可用於打印每個測試函數的名字和運行時間: +参数`-v`可用于打印每个测试函数的名字和运行时间: ``` $ go test -v @@ -120,7 +120,7 @@ exit status 1 FAIL gopl.io/ch11/word1 0.017s ``` -參數`-run`對應一個正則表達式,隻有測試函數名被它正確匹配的測試函數才會被`go test`測試命令運行: +参数`-run`对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被`go test`测试命令运行: ``` $ go test -v -run="French|Canal" @@ -135,11 +135,11 @@ exit status 1 FAIL gopl.io/ch11/word1 0.014s ``` -當然,一旦我們已經脩複了失敗的測試用例,在我們提交代碼更新之前,我們應該以不帶參數的`go test`命令運行全部的測試用例,以確保脩複失敗測試的同時沒有引入新的問題。 +当然,一旦我们已经修复了失败的测试用例,在我们提交代码更新之前,我们应该以不带参数的`go test`命令运行全部的测试用例,以确保修复失败测试的同时没有引入新的问题。 -我們現在的任務就是脩複這些錯誤。簡要分析後發現第一個BUG的原因是我們采用了 byte而不是rune序列,所以像“été”中的é等非ASCII字符不能正確處理。第二個BUG是因爲沒有忽略空格和字母的大小寫導致的。 +我们现在的任务就是修复这些错误。简要分析后发现第一个BUG的原因是我们采用了 byte而不是rune序列,所以像“été”中的é等非ASCII字符不能正确处理。第二个BUG是因为没有忽略空格和字母的大小写导致的。 -針對上述兩個BUG,我們仔細重寫了函數: +针对上述两个BUG,我们仔细重写了函数: gopl.io/ch11/word2 ```Go @@ -166,7 +166,7 @@ func IsPalindrome(s string) bool { } ``` -同時我們也將之前的所有測試數據合併到了一個測試中的表格中。 +同时我们也将之前的所有测试数据合并到了一个测试中的表格中。 ```Go func TestIsPalindrome(t *testing.T) { @@ -196,24 +196,24 @@ func TestIsPalindrome(t *testing.T) { } ``` -現在我們的新測試阿都通過了: +现在我们的新测试阿都通过了: ``` $ go test gopl.io/ch11/word2 ok gopl.io/ch11/word2 0.015s ``` -這種表格驅動的測試在Go語言中很常見的。我們很容易向表格添加新的測試數據,併且後面的測試邏輯也沒有冗餘,這樣我們可以有更多的精力地完善錯誤信息。 +这种表格驱动的测试在Go语言中很常见的。我们很容易向表格添加新的测试数据,并且后面的测试逻辑也没有冗余,这样我们可以有更多的精力地完善错误信息。 -失敗測試的輸出併不包括調用t.Errorf時刻的堆棧調用信息。和其他編程語言或測試框架的assert斷言不同,t.Errorf調用也沒有引起panic異常或停止測試的執行。卽使表格中前面的數據導致了測試的失敗,表格後面的測試數據依然會運行測試,因此在一個測試中我們可能了解多個失敗的信息。 +失败测试的输出并不包括调用t.Errorf时刻的堆栈调用信息。和其他编程语言或测试框架的assert断言不同,t.Errorf调用也没有引起panic异常或停止测试的执行。即使表格中前面的数据导致了测试的失败,表格后面的测试数据依然会运行测试,因此在一个测试中我们可能了解多个失败的信息。 -如果我們眞的需要停止測試,或許是因爲初始化失敗或可能是早先的錯誤導致了後續錯誤等原因,我們可以使用t.Fatal或t.Fatalf停止當前測試函數。它們必須在和測試函數同一個goroutine內調用。 +如果我们真的需要停止测试,或许是因为初始化失败或可能是早先的错误导致了后续错误等原因,我们可以使用t.Fatal或t.Fatalf停止当前测试函数。它们必须在和测试函数同一个goroutine内调用。 -測試失敗的信息一般的形式是“f(x) = y, want z”,其中f(x)解釋了失敗的操作和對應的輸出,y是實際的運行結果,z是期望的正確的結果。就像前面檢査迴文字符串的例子,實際的函數用於f(x)部分。如果顯示x是表格驅動型測試中比較重要的部分,因爲同一個斷言可能對應不同的表格項執行多次。要避免無用和冗餘的信息。在測試類似IsPalindrome返迴布爾類型的函數時,可以忽略併沒有額外信息的z部分。如果x、y或z是y的長度,輸出一個相關部分的簡明總結卽可。測試的作者應該要努力幫助程序員診斷測試失敗的原因。 +测试失败的信息一般的形式是“f(x) = y, want z”,其中f(x)解释了失败的操作和对应的输出,y是实际的运行结果,z是期望的正确的结果。就像前面检查回文字符串的例子,实际的函数用于f(x)部分。如果显示x是表格驱动型测试中比较重要的部分,因为同一个断言可能对应不同的表格项执行多次。要避免无用和冗余的信息。在测试类似IsPalindrome返回布尔类型的函数时,可以忽略并没有额外信息的z部分。如果x、y或z是y的长度,输出一个相关部分的简明总结即可。测试的作者应该要努力帮助程序员诊断测试失败的原因。 -**練習 11.1:** 爲4.3節中的charcount程序編寫測試。 +**练习 11.1:** 为4.3节中的charcount程序编写测试。 -**練習 11.2:** 爲(§6.5)的IntSet編寫一組測試,用於檢査每個操作後的行爲和基於內置map的集合等價,後面練習11.7將會用到。 +**练习 11.2:** 为(§6.5)的IntSet编写一组测试,用于检查每个操作后的行为和基于内置map的集合等价,后面练习11.7将会用到。 {% include "./ch11-02-1.md" %} diff --git a/ch11/ch11-03.md b/ch11/ch11-03.md index 954df50b..452695e9 100644 --- a/ch11/ch11-03.md +++ b/ch11/ch11-03.md @@ -1,12 +1,12 @@ -## 11.3. 測試覆蓋率 +## 11.3. 测试覆盖率 -就其性質而言,測試不可能是完整的。計算機科學家Edsger Dijkstra曾説過:“測試可以顯示存在缺陷,但是併不是説沒有BUG。”再多的測試也不能證明一個程序沒有BUG。在最好的情況下,測試可以增強我們的信心:代碼在我們測試的環境是可以正常工作的。 +就其性质而言,测试不可能是完整的。计算机科学家Edsger Dijkstra曾说过:“测试可以显示存在缺陷,但是并不是说没有BUG。”再多的测试也不能证明一个程序没有BUG。在最好的情况下,测试可以增强我们的信心:代码在我们测试的环境是可以正常工作的。 -由測試驅動觸發運行到的被測試函數的代碼數目稱爲測試的覆蓋率。測試覆蓋率併不能量化——甚至連最簡單的動態程序也難以精確測量——但是可以啟發併幫助我們編寫的有效的測試代碼。 +由测试驱动触发运行到的被测试函数的代码数目称为测试的覆盖率。测试覆盖率并不能量化——甚至连最简单的动态程序也难以精确测量——但是可以启发并帮助我们编写的有效的测试代码。 -這些幫助信息中語句的覆蓋率是最簡單和最廣泛使用的。語句的覆蓋率是指在測試中至少被運行一次的代碼占總代碼數的比例。在本節中,我們使用`go test`命令中集成的測試覆蓋率工具,來度量下面代碼的測試覆蓋率,幫助我們識别測試和我們期望間的差距。 +这些帮助信息中语句的覆盖率是最简单和最广泛使用的。语句的覆盖率是指在测试中至少被运行一次的代码占总代码数的比例。在本节中,我们使用`go test`命令中集成的测试覆盖率工具,来度量下面代码的测试覆盖率,帮助我们识别测试和我们期望间的差距。 -下面的代碼是一個表格驅動的測試,用於測試第七章的表達式求值程序: +下面的代码是一个表格驱动的测试,用于测试第七章的表达式求值程序: gopl.io/ch7/eval ```Go @@ -45,7 +45,7 @@ func TestCoverage(t *testing.T) { } ``` -首先,我們要確保所有的測試都正常通過: +首先,我们要确保所有的测试都正常通过: ``` $ go test -v -run=Coverage gopl.io/ch7/eval @@ -55,7 +55,7 @@ PASS ok gopl.io/ch7/eval 0.011s ``` -下面這個命令可以顯示測試覆蓋率工具的使用用法: +下面这个命令可以显示测试覆盖率工具的使用用法: ``` $ go tool cover @@ -68,20 +68,20 @@ Open a web browser displaying annotated source code: ... ``` -`go tool`命令運行Go工具鏈的底層可執行程序。這些底層可執行程序放在$GOROOT/pkg/tool/${GOOS}_${GOARCH}目録。因爲有`go build`命令的原因,我們很少直接調用這些底層工具。 +`go tool`命令运行Go工具链的底层可执行程序。这些底层可执行程序放在$GOROOT/pkg/tool/${GOOS}_${GOARCH}目录。因为有`go build`命令的原因,我们很少直接调用这些底层工具。 -現在我們可以用`-coverprofile`標誌參數重新運行測試: +现在我们可以用`-coverprofile`标志参数重新运行测试: ``` $ go test -run=Coverage -coverprofile=c.out gopl.io/ch7/eval ok gopl.io/ch7/eval 0.032s coverage: 68.5% of statements ``` -這個標誌參數通過在測試代碼中插入生成鉤子來統計覆蓋率數據。也就是説,在運行每個測試前,它會脩改要測試代碼的副本,在每個詞法塊都會設置一個布爾標誌變量。當被脩改後的被測試代碼運行退出時,將統計日誌數據寫入c.out文件,併打印一部分執行的語句的一個總結。(如果你需要的是摘要,使用`go test -cover`。) +这个标志参数通过在测试代码中插入生成钩子来统计覆盖率数据。也就是说,在运行每个测试前,它会修改要测试代码的副本,在每个词法块都会设置一个布尔标志变量。当被修改后的被测试代码运行退出时,将统计日志数据写入c.out文件,并打印一部分执行的语句的一个总结。(如果你需要的是摘要,使用`go test -cover`。) -如果使用了`-covermode=count`標誌參數,那麽將在每個代碼塊插入一個計數器而不是布爾標誌量。在統計結果中記録了每個塊的執行次數,這可以用於衡量哪些是被頻繁執行的熱點代碼。 +如果使用了`-covermode=count`标志参数,那么将在每个代码块插入一个计数器而不是布尔标志量。在统计结果中记录了每个块的执行次数,这可以用于衡量哪些是被频繁执行的热点代码。 -爲了收集數據,我們運行了測試覆蓋率工具,打印了測試日誌,生成一個HTML報告,然後在瀏覽器中打開(圖11.3)。 +为了收集数据,我们运行了测试覆盖率工具,打印了测试日志,生成一个HTML报告,然后在浏览器中打开(图11.3)。 ``` $ go tool cover -html=c.out @@ -89,12 +89,12 @@ $ go tool cover -html=c.out ![](../images/ch11-03.png) -緑色的代碼塊被測試覆蓋到了,紅色的則表示沒有被覆蓋到。爲了清晰起見,我們將的背景紅色文本的背景設置成了陰影效果。我們可以馬上發現unary操作的Eval方法併沒有被執行到。如果我們針對這部分未被覆蓋的代碼添加下面的測試用例,然後重新運行上面的命令,那麽我們將會看到那個紅色部分的代碼也變成緑色了: +绿色的代码块被测试覆盖到了,红色的则表示没有被覆盖到。为了清晰起见,我们将的背景红色文本的背景设置成了阴影效果。我们可以马上发现unary操作的Eval方法并没有被执行到。如果我们针对这部分未被覆盖的代码添加下面的测试用例,然后重新运行上面的命令,那么我们将会看到那个红色部分的代码也变成绿色了: ``` {"-x * -x", eval.Env{"x": 2}, "4"} ``` -不過兩個panic語句依然是紅色的。這是沒有問題的,因爲這兩個語句併不會被執行到。 +不过两个panic语句依然是红色的。这是没有问题的,因为这两个语句并不会被执行到。 -實現100%的測試覆蓋率聽起來很美,但是在具體實踐中通常是不可行的,也不是值得推薦的做法。因爲那隻能説明代碼被執行過而已,併不意味着代碼就是沒有BUG的;因爲對於邏輯複雜的語句需要針對不同的輸入執行多次。有一些語句,例如上面的panic語句則永遠都不會被執行到。另外,還有一些隱晦的錯誤在現實中很少遇到也很難編寫對應的測試代碼。測試從本質上來説是一個比較務實的工作,編寫測試代碼和編寫應用代碼的成本對比是需要考慮的。測試覆蓋率工具可以幫助我們快速識别測試薄弱的地方,但是設計好的測試用例和編寫應用代碼一樣需要嚴密的思考。 +实现100%的测试覆盖率听起来很美,但是在具体实践中通常是不可行的,也不是值得推荐的做法。因为那只能说明代码被执行过而已,并不意味着代码就是没有BUG的;因为对于逻辑复杂的语句需要针对不同的输入执行多次。有一些语句,例如上面的panic语句则永远都不会被执行到。另外,还有一些隐晦的错误在现实中很少遇到也很难编写对应的测试代码。测试从本质上来说是一个比较务实的工作,编写测试代码和编写应用代码的成本对比是需要考虑的。测试覆盖率工具可以帮助我们快速识别测试薄弱的地方,但是设计好的测试用例和编写应用代码一样需要严密的思考。 diff --git a/ch11/ch11-04.md b/ch11/ch11-04.md index c96cd1b4..59770474 100644 --- a/ch11/ch11-04.md +++ b/ch11/ch11-04.md @@ -1,8 +1,8 @@ -## 11.4. 基準測試 +## 11.4. 基准测试 -基準測試是測量一個程序在固定工作負載下的性能。在Go語言中,基準測試函數和普通測試函數寫法類似,但是以Benchmark爲前綴名,併且帶有一個`*testing.B`類型的參數;`*testing.B`參數除了提供和`*testing.T`類似的方法,還有額外一些和性能測量相關的方法。它還提供了一個整數N,用於指定操作執行的循環次數。 +基准测试是测量一个程序在固定工作负载下的性能。在Go语言中,基准测试函数和普通测试函数写法类似,但是以Benchmark为前缀名,并且带有一个`*testing.B`类型的参数;`*testing.B`参数除了提供和`*testing.T`类似的方法,还有额外一些和性能测量相关的方法。它还提供了一个整数N,用于指定操作执行的循环次数。 -下面是IsPalindrome函數的基準測試,其中循環將執行N次。 +下面是IsPalindrome函数的基准测试,其中循环将执行N次。 ```Go import "testing" @@ -14,7 +14,7 @@ func BenchmarkIsPalindrome(b *testing.B) { } ``` -我們用下面的命令運行基準測試。和普通測試不同的是,默認情況下不運行任何基準測試。我們需要通過`-bench`命令行標誌參數手工指定要運行的基準測試函數。該參數是一個正則表達式,用於匹配要執行的基準測試函數的名字,默認值是空的。其中“.”模式將可以匹配所有基準測試函數,但是這里總共隻有一個基準測試函數,因此和`-bench=IsPalindrome`參數是等價的效果。 +我们用下面的命令运行基准测试。和普通测试不同的是,默认情况下不运行任何基准测试。我们需要通过`-bench`命令行标志参数手工指定要运行的基准测试函数。该参数是一个正则表达式,用于匹配要执行的基准测试函数的名字,默认值是空的。其中“.”模式将可以匹配所有基准测试函数,但是这里总共只有一个基准测试函数,因此和`-bench=IsPalindrome`参数是等价的效果。 ``` $ cd $GOPATH/src/gopl.io/ch11/word2 @@ -24,13 +24,13 @@ BenchmarkIsPalindrome-8 1000000 1035 ns/op ok gopl.io/ch11/word2 2.179s ``` -結果中基準測試名的數字後綴部分,這里是8,表示運行時對應的GOMAXPROCS的值,這對於一些和併發相關的基準測試是重要的信息。 +结果中基准测试名的数字后缀部分,这里是8,表示运行时对应的GOMAXPROCS的值,这对于一些和并发相关的基准测试是重要的信息。 -報告顯示每次調用IsPalindrome函數花費1.035微秒,是執行1,000,000次的平均時間。因爲基準測試驅動器開始時併不知道每個基準測試函數運行所花的時間,它會嚐試在眞正運行基準測試前先嚐試用較小的N運行測試來估算基準測試函數所需要的時間,然後推斷一個較大的時間保證穩定的測量結果。 +报告显示每次调用IsPalindrome函数花费1.035微秒,是执行1,000,000次的平均时间。因为基准测试驱动器开始时并不知道每个基准测试函数运行所花的时间,它会尝试在真正运行基准测试前先尝试用较小的N运行测试来估算基准测试函数所需要的时间,然后推断一个较大的时间保证稳定的测量结果。 -循環在基準測試函數內實現,而不是放在基準測試框架內實現,這樣可以讓每個基準測試函數有機會在循環啟動前執行初始化代碼,這樣併不會顯著影響每次迭代的平均運行時間。如果還是擔心初始化代碼部分對測量時間帶來榦擾,那麽可以通過testing.B參數提供的方法來臨時關閉或重置計時器,不過這些一般很少會用到。 +循环在基准测试函数内实现,而不是放在基准测试框架内实现,这样可以让每个基准测试函数有机会在循环启动前执行初始化代码,这样并不会显著影响每次迭代的平均运行时间。如果还是担心初始化代码部分对测量时间带来干扰,那么可以通过testing.B参数提供的方法来临时关闭或重置计时器,不过这些一般很少会用到。 -現在我們有了一個基準測試和普通測試,我們可以很容易測試新的讓程序運行更快的想法。也許最明顯的優化是在IsPalindrome函數中第二個循環的停止檢査,這樣可以避免每個比較都做兩次: +现在我们有了一个基准测试和普通测试,我们可以很容易测试新的让程序运行更快的想法。也许最明显的优化是在IsPalindrome函数中第二个循环的停止检查,这样可以避免每个比较都做两次: ```Go n := len(letters)/2 @@ -42,7 +42,7 @@ for i := 0; i < n; i++ { return true ``` -不過很多情況下,一個明顯的優化併不一定就能代碼預期的效果。這個改進在基準測試中隻帶來了4%的性能提陞。 +不过很多情况下,一个明显的优化并不一定就能代码预期的效果。这个改进在基准测试中只带来了4%的性能提升。 ``` $ go test -bench=. @@ -51,7 +51,7 @@ BenchmarkIsPalindrome-8 1000000 992 ns/op ok gopl.io/ch11/word2 2.093s ``` -另一個改進想法是在開始爲每個字符預先分配一個足夠大的數組,這樣就可以避免在append調用時可能會導致內存的多次重新分配。聲明一個letters數組變量,併指定合適的大小,像下面這樣, +另一个改进想法是在开始为每个字符预先分配一个足够大的数组,这样就可以避免在append调用时可能会导致内存的多次重新分配。声明一个letters数组变量,并指定合适的大小,像下面这样, ```Go letters := make([]rune, 0, len(s)) @@ -62,7 +62,7 @@ for _, r := range s { } ``` -這個改進提陞性能約35%,報告結果是基於2,000,000次迭代的平均運行時間統計。 +这个改进提升性能约35%,报告结果是基于2,000,000次迭代的平均运行时间统计。 ``` $ go test -bench=. @@ -71,7 +71,7 @@ BenchmarkIsPalindrome-8 2000000 697 ns/op ok gopl.io/ch11/word2 1.468s ``` -如這個例子所示,快的程序往往是伴隨着較少的內存分配。`-benchmem`命令行標誌參數將在報告中包含內存的分配數據統計。我們可以比較優化前後內存的分配情況: +如这个例子所示,快的程序往往是伴随着较少的内存分配。`-benchmem`命令行标志参数将在报告中包含内存的分配数据统计。我们可以比较优化前后内存的分配情况: ``` $ go test -bench=. -benchmem @@ -79,7 +79,7 @@ PASS BenchmarkIsPalindrome 1000000 1026 ns/op 304 B/op 4 allocs/op ``` -這是優化之後的結果: +这是优化之后的结果: ``` $ go test -bench=. -benchmem @@ -87,11 +87,11 @@ PASS BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op ``` -用一次內存分配代替多次的內存分配節省了75%的分配調用次數和減少近一半的內存需求。 +用一次内存分配代替多次的内存分配节省了75%的分配调用次数和减少近一半的内存需求。 -這個基準測試告訴我們所需的絶對時間依賴給定的具體操作,兩個不同的操作所需時間的差異也是和不同環境相關的。例如,如果一個函數需要1ms處理1,000個元素,那麽處理10000或1百萬將需要多少時間呢?這樣的比較揭示了漸近增長函數的運行時間。另一個例子:I/O緩存該設置爲多大呢?基準測試可以幫助我們選擇較小的緩存但能帶來滿意的性能。第三個例子:對於一個確定的工作那種算法更好?基準測試可以評估兩種不同算法對於相同的輸入在不同的場景和負載下的優缺點。 +这个基准测试告诉我们所需的绝对时间依赖给定的具体操作,两个不同的操作所需时间的差异也是和不同环境相关的。例如,如果一个函数需要1ms处理1,000个元素,那么处理10000或1百万将需要多少时间呢?这样的比较揭示了渐近增长函数的运行时间。另一个例子:I/O缓存该设置为多大呢?基准测试可以帮助我们选择较小的缓存但能带来满意的性能。第三个例子:对于一个确定的工作那种算法更好?基准测试可以评估两种不同算法对于相同的输入在不同的场景和负载下的优缺点。 -一般比較基準測試都是結構類似的代碼。它們通常是采用一個參數的函數,從幾個標誌的基準測試函數入口調用,就像這樣: +一般比较基准测试都是结构类似的代码。它们通常是采用一个参数的函数,从几个标志的基准测试函数入口调用,就像这样: ```Go func benchmark(b *testing.B, size int) { /* ... */ } @@ -100,13 +100,13 @@ func Benchmark100(b *testing.B) { benchmark(b, 100) } func Benchmark1000(b *testing.B) { benchmark(b, 1000) } ``` -通過函數參數來指定輸入的大小,但是參數變量對於每個具體的基準測試都是固定的。要避免直接脩改b.N來控製輸入的大小。除非你將它作爲一個固定大小的迭代計算輸入,否則基準測試的結果將毫無意義。 +通过函数参数来指定输入的大小,但是参数变量对于每个具体的基准测试都是固定的。要避免直接修改b.N来控制输入的大小。除非你将它作为一个固定大小的迭代计算输入,否则基准测试的结果将毫无意义。 -基準測試對於編寫代碼是很有幫助的,但是卽使工作完成了也應當保存基準測試代碼。因爲隨着項目的發展,或者是輸入的增加,或者是部署到新的操作繫統或不同的處理器,我們可以再次用基準測試來幫助我們改進設計。 +基准测试对于编写代码是很有帮助的,但是即使工作完成了也应当保存基准测试代码。因为随着项目的发展,或者是输入的增加,或者是部署到新的操作系统或不同的处理器,我们可以再次用基准测试来帮助我们改进设计。 -**練習 11.6:** 爲2.6.2節的練習2.4和練習2.5的PopCount函數編寫基準測試。看看基於表格算法在不同情況下對提陞性能會有多大幫助。 +**练习 11.6:** 为2.6.2节的练习2.4和练习2.5的PopCount函数编写基准测试。看看基于表格算法在不同情况下对提升性能会有多大帮助。 -**練習 11.7:** 爲\*IntSet(§6.5)的Add、UnionWith和其他方法編寫基準測試,使用大量隨機輸入。你可以讓這些方法跑多快?選擇字的大小對於性能的影響如何?IntSet和基於內建map的實現相比有多快? +**练习 11.7:** 为\*IntSet(§6.5)的Add、UnionWith和其他方法编写基准测试,使用大量随机输入。你可以让这些方法跑多快?选择字的大小对于性能的影响如何?IntSet和基于内建map的实现相比有多快? diff --git a/ch11/ch11-05.md b/ch11/ch11-05.md index dcf52a11..cc949f20 100644 --- a/ch11/ch11-05.md +++ b/ch11/ch11-05.md @@ -1,22 +1,22 @@ ## 11.5. 剖析 -測量基準對於衡量特定操作的性能是有幫助的,但是當我們視圖讓程序跑的更快的時候,我們通常併不知道從哪里開始優化。每個碼農都應該知道Donald Knuth在1974年的“Structured Programming with go to Statements”上所説的格言。雖然經常被解讀爲不重視性能的意思,但是從原文我們可以看到不同的含義: +测量基准对于衡量特定操作的性能是有帮助的,但是当我们视图让程序跑的更快的时候,我们通常并不知道从哪里开始优化。每个码农都应该知道Donald Knuth在1974年的“Structured Programming with go to Statements”上所说的格言。虽然经常被解读为不重视性能的意思,但是从原文我们可以看到不同的含义: -> 毫無疑問,效率會導致各種濫用。程序員需要浪費大量的時間思考或者擔心,被部分程序的速度所榦擾,實際上這些嚐試提陞效率的行爲可能産生強烈的負面影響,特别是當調試和維護的時候。我們不應該過度糾結於細節的優化,應該説約97%的場景:過早的優化是萬惡之源。 +> 毫无疑问,效率会导致各种滥用。程序员需要浪费大量的时间思考或者担心,被部分程序的速度所干扰,实际上这些尝试提升效率的行为可能产生强烈的负面影响,特别是当调试和维护的时候。我们不应该过度纠结于细节的优化,应该说约97%的场景:过早的优化是万恶之源。 > -> 我們當然不應該放棄那關鍵的3%的機會。一個好的程序員不會因爲這個理由而滿足,他們會明智地觀察和識别哪些是關鍵的代碼;但是隻有在關鍵代碼已經被確認的前提下才會進行優化。對於判斷哪些部分是關鍵代碼是經常容易犯經驗性錯誤的地方,因此程序員普通使用的測量工具,使得他們的直覺很不靠譜。 +> 我们当然不应该放弃那关键的3%的机会。一个好的程序员不会因为这个理由而满足,他们会明智地观察和识别哪些是关键的代码;但是只有在关键代码已经被确认的前提下才会进行优化。对于判断哪些部分是关键代码是经常容易犯经验性错误的地方,因此程序员普通使用的测量工具,使得他们的直觉很不靠谱。 -當我們想仔細觀察我們程序的運行速度的時候,最好的技術是如何識别關鍵代碼。自動化的剖析技術是基於程序執行期間一些抽樣數據,然後推斷後面的執行狀態;最終産生一個運行時間的統計數據文件。 +当我们想仔细观察我们程序的运行速度的时候,最好的技术是如何识别关键代码。自动化的剖析技术是基于程序执行期间一些抽样数据,然后推断后面的执行状态;最终产生一个运行时间的统计数据文件。 -Go語言支持多種類型的剖析性能分析,每一種關註不同的方面,但它們都涉及到每個采樣記録的感興趣的一繫列事件消息,每個事件都包含函數調用時函數調用堆棧的信息。內建的`go test`工具對幾種分析方式都提供了支持。 +Go语言支持多种类型的剖析性能分析,每一种关注不同的方面,但它们都涉及到每个采样记录的感兴趣的一系列事件消息,每个事件都包含函数调用时函数调用堆栈的信息。内建的`go test`工具对几种分析方式都提供了支持。 -CPU分析文件標識了函數執行時所需要的CPU時間。當前運行的繫統線程在每隔幾毫秒都會遇到操作繫統的中斷事件,每次中斷時都會記録一個分析文件然後恢複正常的運行。 +CPU分析文件标识了函数执行时所需要的CPU时间。当前运行的系统线程在每隔几毫秒都会遇到操作系统的中断事件,每次中断时都会记录一个分析文件然后恢复正常的运行。 -堆分析則記録了程序的內存使用情況。每個內存分配操作都會觸發內部平均內存分配例程,每個512KB的內存申請都會觸發一個事件。 +堆分析则记录了程序的内存使用情况。每个内存分配操作都会触发内部平均内存分配例程,每个512KB的内存申请都会触发一个事件。 -阻塞分析則記録了goroutine最大的阻塞操作,例如繫統調用、管道發送和接收,還有獲取鎖等。分析庫會記録每個goroutine被阻塞時的相關操作。 +阻塞分析则记录了goroutine最大的阻塞操作,例如系统调用、管道发送和接收,还有获取锁等。分析库会记录每个goroutine被阻塞时的相关操作。 -在測試環境下隻需要一個標誌參數就可以生成各種分析文件。當一次使用多個標誌參數時需要當心,因爲分析操作本身也可能會影像程序的運行。 +在测试环境下只需要一个标志参数就可以生成各种分析文件。当一次使用多个标志参数时需要当心,因为分析操作本身也可能会影像程序的运行。 ``` $ go test -cpuprofile=cpu.out @@ -24,13 +24,13 @@ $ go test -blockprofile=block.out $ go test -memprofile=mem.out ``` -對於一些非測試程序也很容易支持分析的特性,具體的實現方式和程序是短時間運行的小工具還是長時間運行的服務會有很大不同,因此Go的runtime運行時包提供了程序運行時控製分析特性的接口。 +对于一些非测试程序也很容易支持分析的特性,具体的实现方式和程序是短时间运行的小工具还是长时间运行的服务会有很大不同,因此Go的runtime运行时包提供了程序运行时控制分析特性的接口。 -一旦我們已經收集到了用於分析的采樣數據,我們就可以使用pprof來分析這些數據。這是Go工具箱自帶的一個工具,但併不是一個日常工具,它對應`go tool pprof`命令。該命令有許多特性和選項,但是最重要的有兩個,就是生成這個概要文件的可執行程序和對於的分析日誌文件。 +一旦我们已经收集到了用于分析的采样数据,我们就可以使用pprof来分析这些数据。这是Go工具箱自带的一个工具,但并不是一个日常工具,它对应`go tool pprof`命令。该命令有许多特性和选项,但是最重要的有两个,就是生成这个概要文件的可执行程序和对于的分析日志文件。 -爲了提高分析效率和減少空間,分析日誌本身併不包含函數的名字;它隻包含函數對應的地址。也就是説pprof需要和分析日誌對於的可執行程序。雖然`go test`命令通常會丟棄臨時用的測試程序,但是在啟用分析的時候會將測試程序保存爲foo.test文件,其中foo部分對於測試包的名字。 +为了提高分析效率和减少空间,分析日志本身并不包含函数的名字;它只包含函数对应的地址。也就是说pprof需要和分析日志对于的可执行程序。虽然`go test`命令通常会丢弃临时用的测试程序,但是在启用分析的时候会将测试程序保存为foo.test文件,其中foo部分对于测试包的名字。 -下面的命令演示了如何生成一個CPU分析文件。我們選擇`net/http`包的一個基準測試爲例。通常是基於一個已經確定了是關鍵代碼的部分進行基準測試。基準測試會默認包含單元測試,這里我們用-run=NONE參數禁止單元測試。 +下面的命令演示了如何生成一个CPU分析文件。我们选择`net/http`包的一个基准测试为例。通常是基于一个已经确定了是关键代码的部分进行基准测试。基准测试会默认包含单元测试,这里我们用-run=NONE参数禁止单元测试。 ``` $ go test -run=NONE -bench=ClientServerParallelTLS64 \ @@ -57,10 +57,10 @@ Showing top 10 nodes out of 166 (cum >= 60ms) 50ms 1.39% 71.59% 60ms 1.67% crypto/elliptic.p256Sum ``` -參數`-text`用於指定輸出格式,在這里每行是一個函數,根據使用CPU的時間長短來排序。其中`-nodecount=10`標誌參數限製了隻輸出前10行的結果。對於嚴重的性能問題,這個文本格式基本可以幫助査明原因了。 +参数`-text`用于指定输出格式,在这里每行是一个函数,根据使用CPU的时间长短来排序。其中`-nodecount=10`标志参数限制了只输出前10行的结果。对于严重的性能问题,这个文本格式基本可以帮助查明原因了。 -這個概要文件告訴我們,HTTPS基準測試中`crypto/elliptic.p256ReduceDegree`函數占用了將近一半的CPU資源。相比之下,如果一個概要文件中主要是runtime包的內存分配的函數,那麽減少內存消耗可能是一個值得嚐試的優化策略。 +这个概要文件告诉我们,HTTPS基准测试中`crypto/elliptic.p256ReduceDegree`函数占用了将近一半的CPU资源。相比之下,如果一个概要文件中主要是runtime包的内存分配的函数,那么减少内存消耗可能是一个值得尝试的优化策略。 -對於一些更微妙的問題,你可能需要使用pprof的圖形顯示功能。這個需要安裝GraphViz工具,可以從 http://www.graphviz.org 下載。參數`-web`用於生成一個有向圖文件,包含了CPU的使用和最熱點的函數等信息。 +对于一些更微妙的问题,你可能需要使用pprof的图形显示功能。这个需要安装GraphViz工具,可以从 http://www.graphviz.org 下载。参数`-web`用于生成一个有向图文件,包含了CPU的使用和最热点的函数等信息。 -這一節我們隻是簡單看了下Go語言的分析據工具。如果想了解更多,可以閲讀Go官方博客的“Profiling Go Programs”一文。 +这一节我们只是简单看了下Go语言的分析据工具。如果想了解更多,可以阅读Go官方博客的“Profiling Go Programs”一文。 diff --git a/ch11/ch11-06.md b/ch11/ch11-06.md index c5b99968..235cd936 100644 --- a/ch11/ch11-06.md +++ b/ch11/ch11-06.md @@ -1,6 +1,6 @@ -## 11.6. 示例函數 +## 11.6. 示例函数 -第三種`go test`特别處理的函數是示例函數,以Example爲函數名開頭。示例函數沒有函數參數和返迴值。下面是IsPalindrome函數對應的示例函數: +第三种`go test`特别处理的函数是示例函数,以Example为函数名开头。示例函数没有函数参数和返回值。下面是IsPalindrome函数对应的示例函数: ```Go func ExampleIsPalindrome() { @@ -12,14 +12,14 @@ func ExampleIsPalindrome() { } ``` -示例函數有三個用處。最主要的一個是作爲文檔:一個包的例子可以更簡潔直觀的方式來演示函數的用法,比文字描述更直接易懂,特别是作爲一個提醒或快速參考時。一個示例函數也可以方便展示屬於同一個接口的幾種類型或函數直接的關繫,所有的文檔都必須關聯到一個地方,就像一個類型或函數聲明都統一到包一樣。同時,示例函數和註釋併不一樣,示例函數是完整眞實的Go代碼,需要接受編譯器的編譯時檢査,這樣可以保證示例代碼不會腐爛成不能使用的舊代碼。 +示例函数有三个用处。最主要的一个是作为文档:一个包的例子可以更简洁直观的方式来演示函数的用法,比文字描述更直接易懂,特别是作为一个提醒或快速参考时。一个示例函数也可以方便展示属于同一个接口的几种类型或函数直接的关系,所有的文档都必须关联到一个地方,就像一个类型或函数声明都统一到包一样。同时,示例函数和注释并不一样,示例函数是完整真实的Go代码,需要接受编译器的编译时检查,这样可以保证示例代码不会腐烂成不能使用的旧代码。 -根據示例函數的後綴名部分,godoc的web文檔會將一個示例函數關聯到某個具體函數或包本身,因此ExampleIsPalindrome示例函數將是IsPalindrome函數文檔的一部分,Example示例函數將是包文檔的一部分。 +根据示例函数的后缀名部分,godoc的web文档会将一个示例函数关联到某个具体函数或包本身,因此ExampleIsPalindrome示例函数将是IsPalindrome函数文档的一部分,Example示例函数将是包文档的一部分。 -示例文檔的第二個用處是在`go test`執行測試的時候也運行示例函數測試。如果示例函數內含有類似上面例子中的`// Output:`格式的註釋,那麽測試工具會執行這個示例函數,然後檢測這個示例函數的標準輸出和註釋是否匹配。 +示例文档的第二个用处是在`go test`执行测试的时候也运行示例函数测试。如果示例函数内含有类似上面例子中的`// Output:`格式的注释,那么测试工具会执行这个示例函数,然后检测这个示例函数的标准输出和注释是否匹配。 -示例函數的第三個目的提供一個眞實的演練場。 http://golang.org 就是由godoc提供的文檔服務,它使用了Go Playground提高的技術讓用戶可以在瀏覽器中在線編輯和運行每個示例函數,就像圖11.4所示的那樣。這通常是學習函數使用或Go語言特性最快捷的方式。 +示例函数的第三个目的提供一个真实的演练场。 http://golang.org 就是由godoc提供的文档服务,它使用了Go Playground提高的技术让用户可以在浏览器中在线编辑和运行每个示例函数,就像图11.4所示的那样。这通常是学习函数使用或Go语言特性最快捷的方式。 ![](../images/ch11-04.png) -本書最後的兩掌是討論reflect和unsafe包,一般的Go用戶很少直接使用它們。因此,如果你還沒有寫過任何眞實的Go程序的話,現在可以忽略剩餘部分而直接編碼了。 +本书最后的两掌是讨论reflect和unsafe包,一般的Go用户很少直接使用它们。因此,如果你还没有写过任何真实的Go程序的话,现在可以忽略剩余部分而直接编码了。 diff --git a/ch11/ch11.md b/ch11/ch11.md index c8da1b61..a5e0a79f 100644 --- a/ch11/ch11.md +++ b/ch11/ch11.md @@ -1,13 +1,13 @@ -# 第十一章 測試 +# 第十一章 测试 -Maurice Wilkes,第一個存儲程序計算機EDSAC的設計者,1949年他在實驗室爬樓梯時有一個頓悟。在《計算機先驅迴憶録》(Memoirs of a Computer Pioneer)里,他迴憶到:“忽然間有一種醍醐灌頂的感覺,我整個後半生的美好時光都將在尋找程序BUG中度過了”。肯定從那之後的大部分正常的碼農都會同情Wilkes過份悲觀的想法,雖然也許不是沒有人睏惑於他對軟件開發的難度的天眞看法。 +Maurice Wilkes,第一个存储程序计算机EDSAC的设计者,1949年他在实验室爬楼梯时有一个顿悟。在《计算机先驱回忆录》(Memoirs of a Computer Pioneer)里,他回忆到:“忽然间有一种醍醐灌顶的感觉,我整个后半生的美好时光都将在寻找程序BUG中度过了”。肯定从那之后的大部分正常的码农都会同情Wilkes过份悲观的想法,虽然也许不是没有人困惑于他对软件开发的难度的天真看法。 -現在的程序已經遠比Wilkes時代的更大也更複雜,也有許多技術可以讓軟件的複雜性可得到控製。其中有兩種技術在實踐中證明是比較有效的。第一種是代碼在被正式部署前需要進行代碼評審。第二種則是測試,也就是本章的討論主題。 +现在的程序已经远比Wilkes时代的更大也更复杂,也有许多技术可以让软件的复杂性可得到控制。其中有两种技术在实践中证明是比较有效的。第一种是代码在被正式部署前需要进行代码评审。第二种则是测试,也就是本章的讨论主题。 -我們説測試的時候一般是指自動化測試,也就是寫一些小的程序用來檢測被測試代碼(産品代碼)的行爲和預期的一樣,這些通常都是精心設計的執行某些特定的功能或者是通過隨機性的輸入要驗證邊界的處理。 +我们说测试的时候一般是指自动化测试,也就是写一些小的程序用来检测被测试代码(产品代码)的行为和预期的一样,这些通常都是精心设计的执行某些特定的功能或者是通过随机性的输入要验证边界的处理。 -軟件測試是一個鉅大的領域。測試的任務可能已經占據了一些程序員的部分時間和另一些程序員的全部時間。和軟件測試技術相關的圖書或博客文章有成韆上萬之多。對於每一種主流的編程語言,都會有一打的用於測試的軟件包,同時也有大量的測試相關的理論,而且每種都吸引了大量技術先驅和追隨者。這些都足以説服那些想要編寫有效測試的程序員重新學習一套全新的技能。 +软件测试是一个巨大的领域。测试的任务可能已经占据了一些程序员的部分时间和另一些程序员的全部时间。和软件测试技术相关的图书或博客文章有成千上万之多。对于每一种主流的编程语言,都会有一打的用于测试的软件包,同时也有大量的测试相关的理论,而且每种都吸引了大量技术先驱和追随者。这些都足以说服那些想要编写有效测试的程序员重新学习一套全新的技能。 -Go語言的測試技術是相對低級的。它依賴一個go test測試命令和一組按照約定方式編寫的測試函數,測試命令可以運行這些測試函數。編寫相對輕量級的純測試代碼是有效的,而且它很容易延伸到基準測試和示例文檔。 +Go语言的测试技术是相对低级的。它依赖一个go test测试命令和一组按照约定方式编写的测试函数,测试命令可以运行这些测试函数。编写相对轻量级的纯测试代码是有效的,而且它很容易延伸到基准测试和示例文档。 -在實踐中,編寫測試代碼和編寫程序本身併沒有多大區别。我們編寫的每一個函數也是針對每個具體的任務。我們必須小心處理邊界條件,思考合適的數據結構,推斷合適的輸入應該産生什麽樣的結果輸出。編程測試代碼和編寫普通的Go代碼過程是類似的;它併不需要學習新的符號、規則和工具。 +在实践中,编写测试代码和编写程序本身并没有多大区别。我们编写的每一个函数也是针对每个具体的任务。我们必须小心处理边界条件,思考合适的数据结构,推断合适的输入应该产生什么样的结果输出。编程测试代码和编写普通的Go代码过程是类似的;它并不需要学习新的符号、规则和工具。 diff --git a/ch12/ch12-01.md b/ch12/ch12-01.md index ec54f942..129a8239 100644 --- a/ch12/ch12-01.md +++ b/ch12/ch12-01.md @@ -1,10 +1,10 @@ -## 12.1. 爲何需要反射? +## 12.1. 为何需要反射? -有時候我們需要編寫一個函數能夠處理一類併不滿足普通公共接口的類型的值,也可能是因爲它們併沒有確定的表示方式,或者是在我們設計該函數的時候還這些類型可能還不存在,各種情況都有可能。 +有时候我们需要编写一个函数能够处理一类并不满足普通公共接口的类型的值,也可能是因为它们并没有确定的表示方式,或者是在我们设计该函数的时候还这些类型可能还不存在,各种情况都有可能。 -一個大家熟悉的例子是fmt.Fprintf函數提供的字符串格式化處理邏輯,它可以用例對任意類型的值格式化併打印,甚至支持用戶自定義的類型。讓我們也來嚐試實現一個類似功能的函數。爲了簡單起見,我們的函數隻接收一個參數,然後返迴和fmt.Sprint類似的格式化後的字符串。我們實現的函數名也叫Sprint。 +一个大家熟悉的例子是fmt.Fprintf函数提供的字符串格式化处理逻辑,它可以用例对任意类型的值格式化并打印,甚至支持用户自定义的类型。让我们也来尝试实现一个类似功能的函数。为了简单起见,我们的函数只接收一个参数,然后返回和fmt.Sprint类似的格式化后的字符串。我们实现的函数名也叫Sprint。 -我們使用了switch類型分支首先來測試輸入參數是否實現了String方法,如果是的話就使用該方法。然後繼續增加類型測試分支,檢査是否是每個基於string、int、bool等基礎類型的動態類型,併在每種情況下執行相應的格式化操作。 +我们使用了switch类型分支首先来测试输入参数是否实现了String方法,如果是的话就使用该方法。然后继续增加类型测试分支,检查是否是每个基于string、int、bool等基础类型的动态类型,并在每种情况下执行相应的格式化操作。 ```Go func Sprint(x interface{}) string { @@ -31,6 +31,6 @@ func Sprint(x interface{}) string { } ``` -但是我們如何處理其它類似[]float64、map[string][]string等類型呢?我們當然可以添加更多的測試分支,但是這些組合類型的數目基本是無窮的。還有如何處理url.Values等命名的類型呢?雖然類型分支可以識别出底層的基礎類型是map[string][]string,但是它併不匹配url.Values類型,因爲它們是兩種不同的類型,而且switch類型分支也不可能包含每個類似url.Values的類型,這會導致對這些庫的循環依賴。 +但是我们如何处理其它类似[]float64、map[string][]string等类型呢?我们当然可以添加更多的测试分支,但是这些组合类型的数目基本是无穷的。还有如何处理url.Values等命名的类型呢?虽然类型分支可以识别出底层的基础类型是map[string][]string,但是它并不匹配url.Values类型,因为它们是两种不同的类型,而且switch类型分支也不可能包含每个类似url.Values的类型,这会导致对这些库的循环依赖。 -沒有一種方法來檢査未知類型的表示方式,我們被卡住了。這就是我們爲何需要反射的原因。 +没有一种方法来检查未知类型的表示方式,我们被卡住了。这就是我们为何需要反射的原因。 diff --git a/ch12/ch12-02.md b/ch12/ch12-02.md index 03d34f79..c9eeb1c3 100644 --- a/ch12/ch12-02.md +++ b/ch12/ch12-02.md @@ -1,8 +1,8 @@ ## 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 對應的引用類似; 接口類型; 還有表示空值的無效類型. (空的 reflect.Value 對應 Invalid 無效類型.) +我们使用 reflect.Value 的 Kind 方法来替代之前的类型 switch. 虽然还是有无穷多的类型, 但是它们的kinds类型却是有限的: Bool, String 和 所有数字类型的基础类型; Array 和 Struct 对应的聚合类型; Chan, Func, Ptr, Slice, 和 Map 对应的引用类似; 接口类型; 还有表示空值的无效类型. (空的 reflect.Value 对应 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 31169f49..b283d048 100644 --- a/ch12/ch12-03.md +++ b/ch12/ch12-03.md @@ -1,13 +1,13 @@ -## 12.3. Display遞歸打印 +## 12.3. Display递归打印 -接下來,讓我們看看如何改善聚合數據類型的顯示。我們併不想完全剋隆一個fmt.Sprint函數,我們隻是像構建一個用於調式用的Display函數,給定一個聚合類型x,打印這個值對應的完整的結構,同時記録每個發現的每個元素的路徑。讓我們從一個例子開始。 +接下来,让我们看看如何改善聚合数据类型的显示。我们并不想完全克隆一个fmt.Sprint函数,我们只是像构建一个用于调式用的Display函数,给定一个聚合类型x,打印这个值对应的完整的结构,同时记录每个发现的每个元素的路径。让我们从一个例子开始。 ```Go e, _ := eval.Parse("sqrt(A / pi)") Display("e", e) ``` -在上面的調用中,傳入Display函數的參數是在7.9節一個表達式求值函數返迴的語法樹。Display函數的輸出如下: +在上面的调用中,传入Display函数的参数是在7.9节一个表达式求值函数返回的语法树。Display函数的输出如下: ```Go Display e (eval.call): @@ -20,7 +20,7 @@ e.args[0].value.y.type = eval.Var e.args[0].value.y.value = "pi" ``` -在可能的情況下,你應該避免在一個包中暴露和反射相關的接口。我們將定義一個未導出的display函數用於遞歸處理工作,導出的是Display函數,它隻是display函數簡單的包裝以接受interface{}類型的參數: +在可能的情况下,你应该避免在一个包中暴露和反射相关的接口。我们将定义一个未导出的display函数用于递归处理工作,导出的是Display函数,它只是display函数简单的包装以接受interface{}类型的参数: gopl.io/ch12/display ```Go @@ -30,9 +30,9 @@ func Display(name string, x interface{}) { } ``` -在display函數中,我們使用了前面定義的打印基礎類型——基本類型、函數和chan等——元素值的formatAtom函數,但是我們會使用reflect.Value的方法來遞歸顯示聚合類型的每一個成員或元素。在遞歸下降過程中,path字符串,從最開始傳入的起始值(這里是“e”),將逐步增長以表示如何達到當前值(例如“e.args[0].value”)。 +在display函数中,我们使用了前面定义的打印基础类型——基本类型、函数和chan等——元素值的formatAtom函数,但是我们会使用reflect.Value的方法来递归显示聚合类型的每一个成员或元素。在递归下降过程中,path字符串,从最开始传入的起始值(这里是“e”),将逐步增长以表示如何达到当前值(例如“e.args[0].value”)。 -因爲我們不再模擬fmt.Sprint函數,我們將直接使用fmt包來簡化我們的例子實現。 +因为我们不再模拟fmt.Sprint函数,我们将直接使用fmt包来简化我们的例子实现。 ```Go func display(path string, v reflect.Value) { @@ -72,21 +72,21 @@ func display(path string, v reflect.Value) { } ``` -讓我們針對不同類型分别討論。 +让我们针对不同类型分别讨论。 -**Slice和數組:** 兩種的處理邏輯是一樣的。Len方法返迴slice或數組值中的元素個數,Index(i)活動索引i對應的元素,返迴的也是一個reflect.Value類型的值;如果索引i超出范圍的話將導致panic異常,這些行爲和數組或slice類型內建的len(a)和a[i]等操作類似。display針對序列中的每個元素遞歸調用自身處理,我們通過在遞歸處理時向path附加“[i]”來表示訪問路徑。 +**Slice和数组:** 两种的处理逻辑是一样的。Len方法返回slice或数组值中的元素个数,Index(i)活动索引i对应的元素,返回的也是一个reflect.Value类型的值;如果索引i超出范围的话将导致panic异常,这些行为和数组或slice类型内建的len(a)和a[i]等操作类似。display针对序列中的每个元素递归调用自身处理,我们通过在递归处理时向path附加“[i]”来表示访问路径。 -雖然reflect.Value類型帶有很多方法,但是隻有少數的方法對任意值都是可以安全調用的。例如,Index方法隻能對Slice、數組或字符串類型的值調用,其它類型如果調用將導致panic異常。 +虽然reflect.Value类型带有很多方法,但是只有少数的方法对任意值都是可以安全调用的。例如,Index方法只能对Slice、数组或字符串类型的值调用,其它类型如果调用将导致panic异常。 -**結構體:** NumField方法報告結構體中成員的數量,Field(i)以reflect.Value類型返迴第i個成員的值。成員列表包含了匿名成員在內的全部成員。通過在path添加“.f”來表示成員路徑,我們必須獲得結構體對應的reflect.Type類型信息,包含結構體類型和第i個成員的名字。 +**结构体:** NumField方法报告结构体中成员的数量,Field(i)以reflect.Value类型返回第i个成员的值。成员列表包含了匿名成员在内的全部成员。通过在path添加“.f”来表示成员路径,我们必须获得结构体对应的reflect.Type类型信息,包含结构体类型和第i个成员的名字。 -**Maps:** MapKeys方法返迴一個reflect.Value類型的slice,每一個都對應map的可以。和往常一樣,遍歷map時順序是隨機的。MapIndex(key)返迴map中key對應的value。我們向path添加“[key]”來表示訪問路徑。(我們這里有一個未完成的工作。其實map的key的類型併不局限於formatAtom能完美處理的類型;數組、結構體和接口都可以作爲map的key。針對這種類型,完善key的顯示信息是練習12.1的任務。) +**Maps:** MapKeys方法返回一个reflect.Value类型的slice,每一个都对应map的可以。和往常一样,遍历map时顺序是随机的。MapIndex(key)返回map中key对应的value。我们向path添加“[key]”来表示访问路径。(我们这里有一个未完成的工作。其实map的key的类型并不局限于formatAtom能完美处理的类型;数组、结构体和接口都可以作为map的key。针对这种类型,完善key的显示信息是练习12.1的任务。) -**指針:** Elem方法返迴指針指向的變量,還是reflect.Value類型。技術指針是nil,這個操作也是安全的,在這種情況下指針是Invalid無效類型,但是我們可以用IsNil方法來顯式地測試一個空指針,這樣我們可以打印更合適的信息。我們在path前面添加“*”,併用括弧包含以避免歧義。 +**指针:** Elem方法返回指针指向的变量,还是reflect.Value类型。技术指针是nil,这个操作也是安全的,在这种情况下指针是Invalid无效类型,但是我们可以用IsNil方法来显式地测试一个空指针,这样我们可以打印更合适的信息。我们在path前面添加“*”,并用括弧包含以避免歧义。 -**接口:** 再一次,我們使用IsNil方法來測試接口是否是nil,如果不是,我們可以調用v.Elem()來獲取接口對應的動態值,併且打印對應的類型和值。 +**接口:** 再一次,我们使用IsNil方法来测试接口是否是nil,如果不是,我们可以调用v.Elem()来获取接口对应的动态值,并且打印对应的类型和值。 -現在我們的Display函數總算完工了,讓我們看看它的表現吧。下面的Movie類型是在4.5節的電影類型上演變來的: +现在我们的Display函数总算完工了,让我们看看它的表现吧。下面的Movie类型是在4.5节的电影类型上演变来的: ```Go type Movie struct { @@ -99,7 +99,7 @@ type Movie struct { } ``` -讓我們聲明一個該類型的變量,然後看看Display函數如何顯示它: +让我们声明一个该类型的变量,然后看看Display函数如何显示它: ```Go strangelove := Movie{ @@ -125,7 +125,7 @@ strangelove := Movie{ } ``` -Display("strangelove", strangelove)調用將顯示(strangelove電影對應的中文名是《奇愛博士》): +Display("strangelove", strangelove)调用将显示(strangelove电影对应的中文名是《奇爱博士》): ```Go Display strangelove (display.Movie): @@ -146,7 +146,7 @@ strangelove.Oscars[3] = "Best Picture (Nomin.)" strangelove.Sequel = nil ``` -我們也可以使用Display函數來顯示標準庫中類型的內部結構,例如`*os.File`類型: +我们也可以使用Display函数来显示标准库中类型的内部结构,例如`*os.File`类型: ```Go Display("os.Stderr", os.Stderr) @@ -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): @@ -174,7 +174,7 @@ Display rV (reflect.Value): ... ``` -觀察下面兩個例子的區别: +观察下面两个例子的区别: ```Go var i interface{} = 3 @@ -191,11 +191,11 @@ Display("&i", &i) // (*&i).value = 3 ``` -在第一個例子中,Display函數將調用reflect.ValueOf(i),它返迴一個Int類型的值。正如我們在12.2節中提到的,reflect.ValueOf總是返迴一個值的具體類型,因爲它是從一個接口值提取的內容。 +在第一个例子中,Display函数将调用reflect.ValueOf(i),它返回一个Int类型的值。正如我们在12.2节中提到的,reflect.ValueOf总是返回一个值的具体类型,因为它是从一个接口值提取的内容。 -在第二個例子中,Display函數調用的是reflect.ValueOf(&i),它返迴一個指向i的指針,對應Ptr類型。在switch的Ptr分支中,通過調用Elem來返迴這個值,返迴一個Value來表示i,對應Interface類型。一個間接獲得的Value,就像這一個,可能代表任意類型的值,包括接口類型。內部的display函數遞歸調用自身,這次它將打印接口的動態類型和值。 +在第二个例子中,Display函数调用的是reflect.ValueOf(&i),它返回一个指向i的指针,对应Ptr类型。在switch的Ptr分支中,通过调用Elem来返回这个值,返回一个Value来表示i,对应Interface类型。一个间接获得的Value,就像这一个,可能代表任意类型的值,包括接口类型。内部的display函数递归调用自身,这次它将打印接口的动态类型和值。 -目前的實現,Display如果顯示一個帶環的數據結構將會陷入死循環,例如首位項鏈的鏈表: +目前的实现,Display如果显示一个带环的数据结构将会陷入死循环,例如首位项链的链表: ```Go // a struct that points to itself @@ -205,7 +205,7 @@ c = Cycle{42, &c} Display("c", c) ``` -Display會永遠不停地進行深度遞歸打印: +Display会永远不停地进行深度递归打印: ```Go Display c (display.Cycle): @@ -216,10 +216,10 @@ c.Value = 42 ...ad infinitum... ``` -許多Go語言程序都包含了一些循環的數據結果。Display支持這類帶環的數據結構是比較棘手的,需要增加一個額外的記録訪問的路徑;代價是昂貴的。一般的解決方案是采用不安全的語言特性,我們將在13.3節看到具體的解決方案。 +许多Go语言程序都包含了一些循环的数据结果。Display支持这类带环的数据结构是比较棘手的,需要增加一个额外的记录访问的路径;代价是昂贵的。一般的解决方案是采用不安全的语言特性,我们将在13.3节看到具体的解决方案。 -帶環的數據結構很少會對fmt.Sprint函數造成問題,因爲它很少嚐試打印完整的數據結構。例如,當它遇到一個指針的時候,它隻是簡單第打印指針的數值。雖然,在打印包含自身的slice或map時可能遇到睏難,但是不保證處理這種是罕見情況卻可以避免額外的麻煩。 +带环的数据结构很少会对fmt.Sprint函数造成问题,因为它很少尝试打印完整的数据结构。例如,当它遇到一个指针的时候,它只是简单第打印指针的数值。虽然,在打印包含自身的slice或map时可能遇到困难,但是不保证处理这种是罕见情况却可以避免额外的麻烦。 -**練習 12.1:** 擴展Displayhans,以便它可以顯示包含以結構體或數組作爲map的key類型的值。 +**练习 12.1:** 扩展Displayhans,以便它可以显示包含以结构体或数组作为map的key类型的值。 -**練習 12.2:** 增強display函數的穩健性,通過記録邊界的步數來確保在超出一定限製前放棄遞歸。(在13.3節,我們會看到另一種探測數據結構是否存在環的技術。) +**练习 12.2:** 增强display函数的稳健性,通过记录边界的步数来确保在超出一定限制前放弃递归。(在13.3节,我们会看到另一种探测数据结构是否存在环的技术。) diff --git a/ch12/ch12-04.md b/ch12/ch12-04.md index eeb90eec..e99afbd4 100644 --- a/ch12/ch12-04.md +++ b/ch12/ch12-04.md @@ -1,10 +1,10 @@ -## 12.4. 示例: 編碼S表達式 +## 12.4. 示例: 编码S表达式 -Display是一個用於顯示結構化數據的調試工具,但是它併不能將任意的Go語言對象編碼爲通用消息然後用於進程間通信。 +Display是一个用于显示结构化数据的调试工具,但是它并不能将任意的Go语言对象编码为通用消息然后用于进程间通信。 -正如我們在4.5節中中看到的,Go語言的標準庫支持了包括JSON、XML和ASN.1等多種編碼格式。還有另一種依然被廣泛使用的格式是S表達式格式,采用類似Lisp語言的語法。但是和其他編碼格式不同的是,Go語言自帶的標準庫併不支持S表達式,主要是因爲它沒有一個公認的標準規范。 +正如我们在4.5节中中看到的,Go语言的标准库支持了包括JSON、XML和ASN.1等多种编码格式。还有另一种依然被广泛使用的格式是S表达式格式,采用类似Lisp语言的语法。但是和其他编码格式不同的是,Go语言自带的标准库并不支持S表达式,主要是因为它没有一个公认的标准规范。 -在本節中,我們將定義一個包用於將Go語言的對象編碼爲S表達式格式,它支持以下結構: +在本节中,我们将定义一个包用于将Go语言的对象编码为S表达式格式,它支持以下结构: ``` 42 integer @@ -13,13 +13,13 @@ foo symbol (an unquoted name) (1 2 3) list (zero or more items enclosed in parentheses) ``` -布爾型習慣上使用t符號表示true,空列表或nil符號表示false,但是爲了簡單起見,我們暫時忽略布爾類型。同時忽略的還有chan管道和函數,因爲通過反射併無法知道它們的確切狀態。我們忽略的還浮點數、複數和interface。支持它們是練習12.3的任務。 +布尔型习惯上使用t符号表示true,空列表或nil符号表示false,但是为了简单起见,我们暂时忽略布尔类型。同时忽略的还有chan管道和函数,因为通过反射并无法知道它们的确切状态。我们忽略的还浮点数、复数和interface。支持它们是练习12.3的任务。 -我們將Go語言的類型編碼爲S表達式的方法如下。整數和字符串以自然的方式編碼。Nil值編碼爲nil符號。數組和slice被編碼爲一個列表。 +我们将Go语言的类型编码为S表达式的方法如下。整数和字符串以自然的方式编码。Nil值编码为nil符号。数组和slice被编码为一个列表。 -結構體被編碼爲成員對象的列表,每個成員對象對應一個個僅有兩個元素的子列表,其中子列表的第一個元素是成員的名字,子列表的第二個元素是成員的值。Map被編碼爲鍵值對的列表。傳統上,S表達式使用點狀符號列表(key . value)結構來表示key/value對,而不是用一個含雙元素的列表,不過爲了簡單我們忽略了點狀符號列表。 +结构体被编码为成员对象的列表,每个成员对象对应一个个仅有两个元素的子列表,其中子列表的第一个元素是成员的名字,子列表的第二个元素是成员的值。Map被编码为键值对的列表。传统上,S表达式使用点状符号列表(key . value)结构来表示key/value对,而不是用一个含双元素的列表,不过为了简单我们忽略了点状符号列表。 -編碼是由一個encode遞歸函數完成,如下所示。它的結構本質上和前面的Display函數類似: +编码是由一个encode递归函数完成,如下所示。它的结构本质上和前面的Display函数类似: gopl.io/ch12/sexpr ```Go @@ -93,7 +93,7 @@ func encode(buf *bytes.Buffer, v reflect.Value) error { } ``` -Marshal函數是對encode的保證,以保持和encoding/...下其它包有着相似的API: +Marshal函数是对encode的保证,以保持和encoding/...下其它包有着相似的API: ```Go // Marshal encodes a Go value in S-expression form. @@ -106,7 +106,7 @@ func Marshal(v interface{}) ([]byte, error) { } ``` -下面是Marshal對12.3節的strangelove變量編碼後的結果: +下面是Marshal对12.3节的strangelove变量编码后的结果: ``` ((Title "Dr. Strangelove") (Subtitle "How I Learned to Stop Worrying and Lo @@ -118,7 +118,7 @@ ge C. Scott") ("Brig. Gen. Jack D. Ripper" "Sterling Hayden") ("Maj. T.J. \ omin.)" "Best Picture (Nomin.)")) (Sequel nil)) ``` -整個輸出編碼爲一行中以減少輸出的大小,但是也很難閲讀。這里有一個對S表達式格式化的約定。編寫一個S表達式的格式化函數將作爲一個具有挑戰性的練習任務;不過 http://gopl.io 也提供了一個簡單的版本。 +整个输出编码为一行中以减少输出的大小,但是也很难阅读。这里有一个对S表达式格式化的约定。编写一个S表达式的格式化函数将作为一个具有挑战性的练习任务;不过 http://gopl.io 也提供了一个简单的版本。 ``` ((Title "Dr. Strangelove") @@ -137,16 +137,16 @@ omin.)" "Best Picture (Nomin.)")) (Sequel nil)) (Sequel nil)) ``` -和fmt.Print、json.Marshal、Display函數類似,sexpr.Marshal函數處理帶環的數據結構也會陷入死循環。 +和fmt.Print、json.Marshal、Display函数类似,sexpr.Marshal函数处理带环的数据结构也会陷入死循环。 -在12.6節中,我們將給出S表達式解碼器的實現步驟,但是在那之前,我們還需要先了解如果通過反射技術來更新程序的變量。 +在12.6节中,我们将给出S表达式解码器的实现步骤,但是在那之前,我们还需要先了解如果通过反射技术来更新程序的变量。 -**練習 12.3:** 實現encode函數缺少的分支。將布爾類型編碼爲t和nil,浮點數編碼爲Go語言的格式,複數1+2i編碼爲#C(1.0 2.0)格式。接口編碼爲類型名和值對,例如("[]int" (1 2 3)),但是這個形式可能會造成歧義:reflect.Type.String方法對於不同的類型可能返迴相同的結果。 +**练习 12.3:** 实现encode函数缺少的分支。将布尔类型编码为t和nil,浮点数编码为Go语言的格式,复数1+2i编码为#C(1.0 2.0)格式。接口编码为类型名和值对,例如("[]int" (1 2 3)),但是这个形式可能会造成歧义:reflect.Type.String方法对于不同的类型可能返回相同的结果。 -**練習 12.4:** 脩改encode函數,以上面的格式化形式輸出S表達式。 +**练习 12.4:** 修改encode函数,以上面的格式化形式输出S表达式。 -**練習 12.5:** 脩改encode函數,用JSON格式代替S表達式格式。然後使用標準庫提供的json.Unmarshal解碼器來驗證函數是正確的。 +**练习 12.5:** 修改encode函数,用JSON格式代替S表达式格式。然后使用标准库提供的json.Unmarshal解码器来验证函数是正确的。 -**練習 12.6:** 脩改encode,作爲一個優化,忽略對是零值對象的編碼。 +**练习 12.6:** 修改encode,作为一个优化,忽略对是零值对象的编码。 -**練習 12.7:** 創建一個基於流式的API,用於S表達式的解碼,和json.Decoder(§4.5)函數功能類似。 +**练习 12.7:** 创建一个基于流式的API,用于S表达式的解码,和json.Decoder(§4.5)函数功能类似。 diff --git a/ch12/ch12-05.md b/ch12/ch12-05.md index 995ffcf4..71efa6ca 100644 --- a/ch12/ch12-05.md +++ b/ch12/ch12-05.md @@ -1,10 +1,10 @@ -## 12.5. 通過reflect.Value脩改值 +## 12.5. 通过reflect.Value修改值 -到目前爲止,反射還隻是程序中變量的另一種訪問方式。然而,在本節中我們將重點討論如果通過反射機製來脩改變量。 +到目前为止,反射还只是程序中变量的另一种访问方式。然而,在本节中我们将重点讨论如果通过反射机制来修改变量。 -迴想一下,Go語言中類似x、x.f[1]和*p形式的表達式都可以表示變量,但是其它如x + 1和f(2)則不是變量。一個變量就是一個可尋址的內存空間,里面存儲了一個值,併且存儲的值可以通過內存地址來更新。 +回想一下,Go语言中类似x、x.f[1]和*p形式的表达式都可以表示变量,但是其它如x + 1和f(2)则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。 -對於reflect.Values也有類似的區别。有一些reflect.Values是可取地址的;其它一些則不可以。考慮以下的聲明語句: +对于reflect.Values也有类似的区别。有一些reflect.Values是可取地址的;其它一些则不可以。考虑以下的声明语句: ```Go x := 2 // value type variable? @@ -14,9 +14,9 @@ c := reflect.ValueOf(&x) // &x *int no d := c.Elem() // 2 int yes (x) ``` -其中a對應的變量則不可取地址。因爲a中的值僅僅是整數2的拷貝副本。b中的值也同樣不可取地址。c中的值還是不可取地址,它隻是一個指針`&x`的拷貝。實際上,所有通過reflect.ValueOf(x)返迴的reflect.Value都是不可取地址的。但是對於d,它是c的解引用方式生成的,指向另一個變量,因此是可取地址的。我們可以通過調用reflect.ValueOf(&x).Elem(),來獲取任意變量x對應的可取地址的Value。 +其中a对应的变量则不可取地址。因为a中的值仅仅是整数2的拷贝副本。b中的值也同样不可取地址。c中的值还是不可取地址,它只是一个指针`&x`的拷贝。实际上,所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的。但是对于d,它是c的解引用方式生成的,指向另一个变量,因此是可取地址的。我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的Value。 -我們可以通過調用reflect.Value的CanAddr方法來判斷其是否可以被取地址: +我们可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址: ```Go fmt.Println(a.CanAddr()) // "false" @@ -25,9 +25,9 @@ fmt.Println(c.CanAddr()) // "false" fmt.Println(d.CanAddr()) // "true" ``` -每當我們通過指針間接地獲取的reflect.Value都是可取地址的,卽使開始的是一個不可取地址的Value。在反射機製中,所有關於是否支持取地址的規則都是類似的。例如,slice的索引表達式e[i]將隱式地包含一個指針,它就是可取地址的,卽使開始的e表達式不支持也沒有關繫。以此類推,reflect.ValueOf(e).Index(i)對於的值也是可取地址的,卽使原始的reflect.ValueOf(e)不支持也沒有關繫。 +每当我们通过指针间接地获取的reflect.Value都是可取地址的,即使开始的是一个不可取地址的Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,slice的索引表达式e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。以此类推,reflect.ValueOf(e).Index(i)对于的值也是可取地址的,即使原始的reflect.ValueOf(e)不支持也没有关系。 -要從變量對應的可取地址的reflect.Value來訪問變量需要三個步驟。第一步是調用Addr()方法,它返迴一個Value,里面保存了指向變量的指針。然後是在Value上調用Interface()方法,也就是返迴一個interface{},里面通用包含指向變量的指針。最後,如果我們知道變量的類型,我們可以使用類型的斷言機製將得到的interface{}類型的接口強製環爲普通的類型指針。這樣我們就可以通過這個普通指針來更新變量了: +要从变量对应的可取地址的reflect.Value来访问变量需要三个步骤。第一步是调用Addr()方法,它返回一个Value,里面保存了指向变量的指针。然后是在Value上调用Interface()方法,也就是返回一个interface{},里面通用包含指向变量的指针。最后,如果我们知道变量的类型,我们可以使用类型的断言机制将得到的interface{}类型的接口强制环为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了: ```Go x := 2 @@ -37,20 +37,20 @@ px := d.Addr().Interface().(*int) // px := &x fmt.Println(x) // "3" ``` -或者,不使用指針,而是通過調用可取地址的reflect.Value的reflect.Value.Set方法來更新對於的值: +或者,不使用指针,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对于的值: ```Go d.Set(reflect.ValueOf(4)) fmt.Println(x) // "4" ``` -Set方法將在運行時執行和編譯時類似的可賦值性約束的檢査。以上代碼,變量和值都是int類型,但是如果變量是int64類型,那麽程序將拋出一個panic異常,所以關鍵問題是要確保改類型的變量可以接受對應的值: +Set方法将在运行时执行和编译时类似的可赋值性约束的检查。以上代码,变量和值都是int类型,但是如果变量是int64类型,那么程序将抛出一个panic异常,所以关键问题是要确保改类型的变量可以接受对应的值: ```Go d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int ``` -通用對一個不可取地址的reflect.Value調用Set方法也會導致panic異常: +通用对一个不可取地址的reflect.Value调用Set方法也会导致panic异常: ```Go x := 2 @@ -58,7 +58,7 @@ b := reflect.ValueOf(x) b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value ``` -這里有很多用於基本數據類型的Set方法:SetInt、SetUint、SetString和SetFloat等。 +这里有很多用于基本数据类型的Set方法:SetInt、SetUint、SetString和SetFloat等。 ```Go d := reflect.ValueOf(&x).Elem() @@ -66,7 +66,7 @@ d.SetInt(3) fmt.Println(x) // "3" ``` -從某種程度上説,這些Set方法總是盡可能地完成任務。以SetInt爲例,隻要變量是某種類型的有符號整數就可以工作,卽使是一些命名的類型,隻要底層數據類型是有符號整數就可以,而且如果對於變量類型值太大的話會被自動截斷。但需要謹慎的是:對於一個引用interface{}類型的reflect.Value調用SetInt會導致panic異常,卽使那個interface{}變量對於整數類型也不行。 +从某种程度上说,这些Set方法总是尽可能地完成任务。以SetInt为例,只要变量是某种类型的有符号整数就可以工作,即使是一些命名的类型,只要底层数据类型是有符号整数就可以,而且如果对于变量类型值太大的话会被自动截断。但需要谨慎的是:对于一个引用interface{}类型的reflect.Value调用SetInt会导致panic异常,即使那个interface{}变量对于整数类型也不行。 ```Go x := 1 @@ -84,7 +84,7 @@ ry.SetString("hello") // panic: SetString called on interface Value ry.Set(reflect.ValueOf("hello")) // OK, y = "hello" ``` -當我們用Display顯示os.Stdout結構時,我們發現反射可以越過Go語言的導出規則的限製讀取結構體中未導出的成員,比如在類Unix繫統上os.File結構體中的fd int成員。然而,利用反射機製併不能脩改這些未導出的成員: +当我们用Display显示os.Stdout结构时,我们发现反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,比如在类Unix系统上os.File结构体中的fd int成员。然而,利用反射机制并不能修改这些未导出的成员: ```Go stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var @@ -94,7 +94,7 @@ fmt.Println(fd.Int()) // "1" fd.SetInt(2) // panic: unexported field ``` -一個可取地址的reflect.Value會記録一個結構體成員是否是未導出成員,如果是的話則拒絶脩改操作。因此,CanAddr方法併不能正確反映一個變量是否是可以被脩改的。另一個相關的方法CanSet是用於檢査對應的reflect.Value是否是可取地址併可被脩改的: +一个可取地址的reflect.Value会记录一个结构体成员是否是未导出成员,如果是的话则拒绝修改操作。因此,CanAddr方法并不能正确反映一个变量是否是可以被修改的。另一个相关的方法CanSet是用于检查对应的reflect.Value是否是可取地址并可被修改的: ```Go fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false" diff --git a/ch12/ch12-06.md b/ch12/ch12-06.md index b6e958f6..b485cfca 100644 --- a/ch12/ch12-06.md +++ b/ch12/ch12-06.md @@ -1,6 +1,6 @@ -## 12.6. 示例: 解碼S表達式 +## 12.6. 示例: 解码S表达式 -標準庫中encoding/...下每個包中提供的Marshal編碼函數都有一個對應的Unmarshal函數用於解碼。例如,我們在4.5節中看到的,要將包含JSON編碼格式的字節slice數據解碼爲我們自己的Movie類型(§12.3),我們可以這樣做: +标准库中encoding/...下每个包中提供的Marshal编码函数都有一个对应的Unmarshal函数用于解码。例如,我们在4.5节中看到的,要将包含JSON编码格式的字节slice数据解码为我们自己的Movie类型(§12.3),我们可以这样做: ```Go data := []byte{/* ... */} @@ -8,13 +8,13 @@ var movie Movie err := json.Unmarshal(data, &movie) ``` -Unmarshal函數使用了反射機製類脩改movie變量的每個成員,根據輸入的內容爲Movie成員創建對應的map、結構體和slice。 +Unmarshal函数使用了反射机制类修改movie变量的每个成员,根据输入的内容为Movie成员创建对应的map、结构体和slice。 -現在讓我們爲S表達式編碼實現一個簡易的Unmarshal,類似於前面的json.Unmarshal標準庫函數,對應我們之前實現的sexpr.Marshal函數的逆操作。我們必須提醒一下,一個健壯的和通用的實現通常需要比例子更多的代碼,爲了便於演示我們采用了精簡的實現。我們隻支持S表達式有限的子集,同時處理錯誤的方式也比較粗暴,代碼的目的是爲了演示反射的用法,而不是構造一個實用的S表達式的解碼器。 +现在让我们为S表达式编码实现一个简易的Unmarshal,类似于前面的json.Unmarshal标准库函数,对应我们之前实现的sexpr.Marshal函数的逆操作。我们必须提醒一下,一个健壮的和通用的实现通常需要比例子更多的代码,为了便于演示我们采用了精简的实现。我们只支持S表达式有限的子集,同时处理错误的方式也比较粗暴,代码的目的是为了演示反射的用法,而不是构造一个实用的S表达式的解码器。 -詞法分析器lexer使用了標準庫中的text/scanner包將輸入流的字節數據解析爲一個個類似註釋、標識符、字符串面值和數字面值之類的標記。輸入掃描器scanner的Scan方法將提前掃描和返迴下一個記號,對於rune類型。大多數記號,比如“(”,對應一個單一rune可表示的Unicode字符,但是text/scanner也可以用小的負數表示記號標識符、字符串等由多個字符組成的記號。調用Scan方法將返迴這些記號的類型,接着調用TokenText方法將返迴記號對應的文本內容。 +词法分析器lexer使用了标准库中的text/scanner包将输入流的字节数据解析为一个个类似注释、标识符、字符串面值和数字面值之类的标记。输入扫描器scanner的Scan方法将提前扫描和返回下一个记号,对于rune类型。大多数记号,比如“(”,对应一个单一rune可表示的Unicode字符,但是text/scanner也可以用小的负数表示记号标识符、字符串等由多个字符组成的记号。调用Scan方法将返回这些记号的类型,接着调用TokenText方法将返回记号对应的文本内容。 -因爲每個解析器可能需要多次使用當前的記號,但是Scan會一直向前掃描,所有我們包裝了一個lexer掃描器輔助類型,用於跟蹤最近由Scan方法返迴的記號。 +因为每个解析器可能需要多次使用当前的记号,但是Scan会一直向前扫描,所有我们包装了一个lexer扫描器辅助类型,用于跟踪最近由Scan方法返回的记号。 gopl.io/ch12/sexpr ```Go @@ -34,7 +34,7 @@ func (lex *lexer) consume(want rune) { } ``` -現在讓我們轉到語法解析器。它主要包含兩個功能。第一個是read函數,用於讀取S表達式的當前標記,然後根據S表達式的當前標記更新可取地址的reflect.Value對應的變量v。 +现在让我们转到语法解析器。它主要包含两个功能。第一个是read函数,用于读取S表达式的当前标记,然后根据S表达式的当前标记更新可取地址的reflect.Value对应的变量v。 ```Go func read(lex *lexer, v reflect.Value) { @@ -67,13 +67,13 @@ func read(lex *lexer, v reflect.Value) { } ``` -我們的S表達式使用標識符區分兩個不同類型,結構體成員名和nil值的指針。read函數值處理nil類型的標識符。當遇到scanner.Ident爲“nil”是,使用reflect.Zero函數將變量v設置爲零值。而其它任何類型的標識符,我們都作爲錯誤處理。後面的readList函數將處理結構體的成員名。 +我们的S表达式使用标识符区分两个不同类型,结构体成员名和nil值的指针。read函数值处理nil类型的标识符。当遇到scanner.Ident为“nil”是,使用reflect.Zero函数将变量v设置为零值。而其它任何类型的标识符,我们都作为错误处理。后面的readList函数将处理结构体的成员名。 -一個“(”標記對應一個列表的開始。第二個函數readList,將一個列表解碼到一個聚合類型中(map、結構體、slice或數組),具體類型依然於傳入待填充變量的類型。每次遇到這種情況,循環繼續解析每個元素直到遇到於開始標記匹配的結束標記“)”,endList函數用於檢測結束標記。 +一个“(”标记对应一个列表的开始。第二个函数readList,将一个列表解码到一个聚合类型中(map、结构体、slice或数组),具体类型依然于传入待填充变量的类型。每次遇到这种情况,循环继续解析每个元素直到遇到于开始标记匹配的结束标记“)”,endList函数用于检测结束标记。 -最有趣的部分是遞歸。最簡單的是對數組類型的處理。直到遇到“)”結束標記,我們使用Index函數來獲取數組每個元素的地址,然後遞歸調用read函數處理。和其它錯誤類似,如果輸入數據導致解碼器的引用超出了數組的范圍,解碼器將拋出panic異常。slice也采用類似方法解析,不同的是我們將爲每個元素創建新的變量,然後將元素添加到slice的末尾。 +最有趣的部分是递归。最简单的是对数组类型的处理。直到遇到“)”结束标记,我们使用Index函数来获取数组每个元素的地址,然后递归调用read函数处理。和其它错误类似,如果输入数据导致解码器的引用超出了数组的范围,解码器将抛出panic异常。slice也采用类似方法解析,不同的是我们将为每个元素创建新的变量,然后将元素添加到slice的末尾。 -在循環處理結構體和map每個元素時必須解碼一個(key value)格式的對應子列表。對於結構體,key部分對於成員的名字。和數組類似,我們使用FieldByName找到結構體對應成員的變量,然後遞歸調用read函數處理。對於map,key可能是任意類型,對元素的處理方式和slice類似,我們創建一個新的變量,然後遞歸填充它,最後將新解析到的key/value對添加到map。 +在循环处理结构体和map每个元素时必须解码一个(key value)格式的对应子列表。对于结构体,key部分对于成员的名字。和数组类似,我们使用FieldByName找到结构体对应成员的变量,然后递归调用read函数处理。对于map,key可能是任意类型,对元素的处理方式和slice类似,我们创建一个新的变量,然后递归填充它,最后将新解析到的key/value对添加到map。 ```Go func readList(lex *lexer, v reflect.Value) { @@ -130,7 +130,7 @@ func endList(lex *lexer) bool { } ``` -最後,我們將解析器包裝爲導出的Unmarshal解碼函數,隱藏了一些初始化和清理等邊緣處理。內部解析器以panic的方式拋出錯誤,但是Unmarshal函數通過在defer語句調用recover函數來捕獲內部panic(§5.10),然後返迴一個對panic對應的錯誤信息。 +最后,我们将解析器包装为导出的Unmarshal解码函数,隐藏了一些初始化和清理等边缘处理。内部解析器以panic的方式抛出错误,但是Unmarshal函数通过在defer语句调用recover函数来捕获内部panic(§5.10),然后返回一个对panic对应的错误信息。 ```Go // Unmarshal parses S-expression data and populates the variable @@ -150,10 +150,10 @@ func Unmarshal(data []byte, out interface{}) (err error) { } ``` -生産實現不應該對任何輸入問題都用panic形式報告,而且應該報告一些錯誤相關的信息,例如出現錯誤輸入的行號和位置等。盡管如此,我們希望通過這個例子來展示類似encoding/json等包底層代碼的實現思路,以及如何使用反射機製來填充數據結構。 +生产实现不应该对任何输入问题都用panic形式报告,而且应该报告一些错误相关的信息,例如出现错误输入的行号和位置等。尽管如此,我们希望通过这个例子来展示类似encoding/json等包底层代码的实现思路,以及如何使用反射机制来填充数据结构。 -**練習 12.8:** sexpr.Unmarshal函數和json.Unmarshal一樣,都要求在解碼前輸入完整的字節slice。定義一個和json.Decoder類似的sexpr.Decoder類型,支持從一個io.Reader流解碼。脩改sexpr.Unmarshal函數,使用這個新的類型實現。 +**练习 12.8:** sexpr.Unmarshal函数和json.Unmarshal一样,都要求在解码前输入完整的字节slice。定义一个和json.Decoder类似的sexpr.Decoder类型,支持从一个io.Reader流解码。修改sexpr.Unmarshal函数,使用这个新的类型实现。 -**練習 12.9:** 編寫一個基於標記的API用於解碼S表達式,參考xml.Decoder(7.14)的風格。你將需要五種類型的標記:Symbol、String、Int、StartList和EndList。 +**练习 12.9:** 编写一个基于标记的API用于解码S表达式,参考xml.Decoder(7.14)的风格。你将需要五种类型的标记:Symbol、String、Int、StartList和EndList。 -**練習 12.10:** 擴展sexpr.Unmarshal函數,支持布爾型、浮點數和interface類型的解碼,使用 **練習 12.3:** 的方案。(提示:要解碼接口,你需要將name映射到每個支持類型的reflect.Type。) +**练习 12.10:** 扩展sexpr.Unmarshal函数,支持布尔型、浮点数和interface类型的解码,使用 **练习 12.3:** 的方案。(提示:要解码接口,你需要将name映射到每个支持类型的reflect.Type。) diff --git a/ch12/ch12-07.md b/ch12/ch12-07.md index ff1fdd8f..c0fe40b0 100644 --- a/ch12/ch12-07.md +++ b/ch12/ch12-07.md @@ -1,10 +1,10 @@ -## 12.7. 獲取結構體字段標識 +## 12.7. 获取结构体字段标识 -在4.5節我們使用構體成員標籤用於設置對應JSON對應的名字。其中json成員標籤讓我們可以選擇成員的名字和抑製零值成員的輸出。在本節,我們將看到如果通過反射機製類獲取成員標籤。 +在4.5节我们使用构体成员标签用于设置对应JSON对应的名字。其中json成员标签让我们可以选择成员的名字和抑制零值成员的输出。在本节,我们将看到如果通过反射机制类获取成员标签。 -對於一個web服務,大部分HTTP處理函數要做的第一件事情就是展開請求中的參數到本地變量中。我們定義了一個工具函數,叫params.Unpack,通過使用結構體成員標籤機製來讓HTTP處理函數解析請求參數更方便。 +对于一个web服务,大部分HTTP处理函数要做的第一件事情就是展开请求中的参数到本地变量中。我们定义了一个工具函数,叫params.Unpack,通过使用结构体成员标签机制来让HTTP处理函数解析请求参数更方便。 -首先,我們看看如何使用它。下面的search函數是一個HTTP請求處理函數。它定義了一個匿名結構體類型的變量,用結構體的每個成員表示HTTP請求的參數。其中結構體成員標籤指明了對於請求參數的名字,爲了減少URL的長度這些參數名通常都是神祕的縮略詞。Unpack將請求參數填充到合適的結構體成員中,這樣我們可以方便地通過合適的類型類來訪問這些參數。 +首先,我们看看如何使用它。下面的search函数是一个HTTP请求处理函数。它定义了一个匿名结构体类型的变量,用结构体的每个成员表示HTTP请求的参数。其中结构体成员标签指明了对于请求参数的名字,为了减少URL的长度这些参数名通常都是神秘的缩略词。Unpack将请求参数填充到合适的结构体成员中,这样我们可以方便地通过合适的类型类来访问这些参数。 gopl.io/ch12/search ```Go @@ -28,9 +28,9 @@ func search(resp http.ResponseWriter, req *http.Request) { } ``` -下面的Unpack函數主要完成三件事情。第一,它調用req.ParseForm()來解析HTTP請求。然後,req.Form將包含所有的請求參數,不管HTTP客戶端使用的是GET還是POST請求方法。 +下面的Unpack函数主要完成三件事情。第一,它调用req.ParseForm()来解析HTTP请求。然后,req.Form将包含所有的请求参数,不管HTTP客户端使用的是GET还是POST请求方法。 -下一步,Unpack函數將構建每個結構體成員有效參數名字到成員變量的映射。如果結構體成員有成員標籤的話,有效參數名字可能和實際的成員名字不相同。reflect.Type的Field方法將返迴一個reflect.StructField,里面含有每個成員的名字、類型和可選的成員標籤等信息。其中成員標籤信息對應reflect.StructTag類型的字符串,併且提供了Get方法用於解析和根據特定key提取的子串,例如這里的http:"..."形式的子串。 +下一步,Unpack函数将构建每个结构体成员有效参数名字到成员变量的映射。如果结构体成员有成员标签的话,有效参数名字可能和实际的成员名字不相同。reflect.Type的Field方法将返回一个reflect.StructField,里面含有每个成员的名字、类型和可选的成员标签等信息。其中成员标签信息对应reflect.StructTag类型的字符串,并且提供了Get方法用于解析和根据特定key提取的子串,例如这里的http:"..."形式的子串。 gopl.io/ch12/params ```Go @@ -78,9 +78,9 @@ func Unpack(req *http.Request, ptr interface{}) error { } ``` -最後,Unpack遍歷HTTP請求的name/valu參數鍵值對,併且根據更新相應的結構體成員。迴想一下,同一個名字的參數可能出現多次。如果發生這種情況,併且對應的結構體成員是一個slice,那麽就將所有的參數添加到slice中。其它情況,對應的成員值將被覆蓋,隻有最後一次出現的參數值才是起作用的。 +最后,Unpack遍历HTTP请求的name/valu参数键值对,并且根据更新相应的结构体成员。回想一下,同一个名字的参数可能出现多次。如果发生这种情况,并且对应的结构体成员是一个slice,那么就将所有的参数添加到slice中。其它情况,对应的成员值将被覆盖,只有最后一次出现的参数值才是起作用的。 -populate函數小心用請求的字符串類型參數值來填充單一的成員v(或者是slice類型成員中的單一的元素)。目前,它僅支持字符串、有符號整數和布爾型。其中其它的類型將留做練習任務。 +populate函数小心用请求的字符串类型参数值来填充单一的成员v(或者是slice类型成员中的单一的元素)。目前,它仅支持字符串、有符号整数和布尔型。其中其它的类型将留做练习任务。 ```Go func populate(v reflect.Value, value string) error { @@ -109,7 +109,7 @@ func populate(v reflect.Value, value string) error { } ``` -如果我們上上面的處理程序添加到一個web服務器,則可以産生以下的會話: +如果我们上上面的处理程序添加到一个web服务器,则可以产生以下的会话: ``` $ go build gopl.io/ch12/search @@ -128,8 +128,8 @@ $ ./fetch 'http://localhost:12345/search?q=hello&max=lots' max: strconv.ParseInt: parsing "lots": invalid syntax ``` -**練習 12.11:** 編寫相應的Pack函數,給定一個結構體值,Pack函數將返迴合併了所有結構體成員和值的URL。 +**练习 12.11:** 编写相应的Pack函数,给定一个结构体值,Pack函数将返回合并了所有结构体成员和值的URL。 -**練習 12.12:** 擴展成員標籤以表示一個請求參數的有效值規則。例如,一個字符串可以是有效的email地址或一個信用卡號碼,還有一個整數可能需要是有效的郵政編碼。脩改Unpack函數以檢査這些規則。 +**练习 12.12:** 扩展成员标签以表示一个请求参数的有效值规则。例如,一个字符串可以是有效的email地址或一个信用卡号码,还有一个整数可能需要是有效的邮政编码。修改Unpack函数以检查这些规则。 -**練習 12.13:** 脩改S表達式的編碼器(§12.4)和解碼器(§12.6),采用和encoding/json包(§4.5)類似的方式使用成員標籤中的sexpr:"..."字串。 +**练习 12.13:** 修改S表达式的编码器(§12.4)和解码器(§12.6),采用和encoding/json包(§4.5)类似的方式使用成员标签中的sexpr:"..."字串。 diff --git a/ch12/ch12-08.md b/ch12/ch12-08.md index e3305eef..48ab5166 100644 --- a/ch12/ch12-08.md +++ b/ch12/ch12-08.md @@ -1,6 +1,6 @@ -## 12.8. 顯示一個類型的方法集 +## 12.8. 显示一个类型的方法集 -我們的最後一個例子是使用reflect.Type來打印任意值的類型和枚舉它的方法: +我们的最后一个例子是使用reflect.Type来打印任意值的类型和枚举它的方法: gopl.io/ch12/methods ```Go @@ -18,9 +18,9 @@ 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`兩個類型的方法: +这是属于time.Duration和`*strings.Replacer`两个类型的方法: ```Go methods.Print(time.Hour) diff --git a/ch12/ch12-09.md b/ch12/ch12-09.md index dd081d24..4d64df92 100644 --- a/ch12/ch12-09.md +++ b/ch12/ch12-09.md @@ -1,20 +1,20 @@ -## 12.9. 幾點忠告 +## 12.9. 几点忠告 -雖然反射提供的API遠多於我們講到的,我們前面的例子主要是給出了一個方向,通過反射可以實現哪些功能。反射是一個強大併富有表達力的工具,但是它應該被小心地使用,原因有三。 +虽然反射提供的API远多于我们讲到的,我们前面的例子主要是给出了一个方向,通过反射可以实现哪些功能。反射是一个强大并富有表达力的工具,但是它应该被小心地使用,原因有三。 -第一個原因是,基於反射的代碼是比較脆弱的。對於每一個會導致編譯器報告類型錯誤的問題,在反射中都有與之相對應的問題,不同的是編譯器會在構建時馬上報告錯誤,而反射則是在眞正運行到的時候才會拋出panic異常,可能是寫完代碼很久之後的時候了,而且程序也可能運行了很長的時間。 +第一个原因是,基于反射的代码是比较脆弱的。对于每一个会导致编译器报告类型错误的问题,在反射中都有与之相对应的问题,不同的是编译器会在构建时马上报告错误,而反射则是在真正运行到的时候才会抛出panic异常,可能是写完代码很久之后的时候了,而且程序也可能运行了很长的时间。 -以前面的readList函數(§12.6)爲例,爲了從輸入讀取字符串併填充int類型的變量而調用的reflect.Value.SetString方法可能導致panic異常。絶大多數使用反射的程序都有類似的風險,需要非常小心地檢査每個reflect.Value的對於值的類型、是否可取地址,還有是否可以被脩改等。 +以前面的readList函数(§12.6)为例,为了从输入读取字符串并填充int类型的变量而调用的reflect.Value.SetString方法可能导致panic异常。绝大多数使用反射的程序都有类似的风险,需要非常小心地检查每个reflect.Value的对于值的类型、是否可取地址,还有是否可以被修改等。 -避免這種因反射而導致的脆弱性的問題的最好方法是將所有的反射相關的使用控製在包的內部,如果可能的話避免在包的API中直接暴露reflect.Value類型,這樣可以限製一些非法輸入。如果無法做到這一點,在每個有風險的操作前指向額外的類型檢査。以標準庫中的代碼爲例,當fmt.Printf收到一個非法的操作數是,它併不會拋出panic異常,而是打印相關的錯誤信息。程序雖然還有BUG,但是會更加容易診斷。 +避免这种因反射而导致的脆弱性的问题的最好方法是将所有的反射相关的使用控制在包的内部,如果可能的话避免在包的API中直接暴露reflect.Value类型,这样可以限制一些非法输入。如果无法做到这一点,在每个有风险的操作前指向额外的类型检查。以标准库中的代码为例,当fmt.Printf收到一个非法的操作数是,它并不会抛出panic异常,而是打印相关的错误信息。程序虽然还有BUG,但是会更加容易诊断。 ```Go fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)" ``` -反射同樣降低了程序的安全性,還影響了自動化重構和分析工具的準確性,因爲它們無法識别運行時才能確認的類型信息。 +反射同样降低了程序的安全性,还影响了自动化重构和分析工具的准确性,因为它们无法识别运行时才能确认的类型信息。 -避免使用反射的第二個原因是,卽使對應類型提供了相同文檔,但是反射的操作不能做靜態類型檢査,而且大量反射的代碼通常難以理解。總是需要小心翼翼地爲每個導出的類型和其它接受interface{}或reflect.Value類型參數的函數維護説明文檔。 +避免使用反射的第二个原因是,即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地为每个导出的类型和其它接受interface{}或reflect.Value类型参数的函数维护说明文档。 -第三個原因,基於反射的代碼通常比正常的代碼運行速度慢一到兩個數量級。對於一個典型的項目,大部分函數的性能和程序的整體性能關繫不大,所以使用反射可能會使程序更加清晰。測試是一個特别適合使用反射的場景,因爲每個測試的數據集都很小。但是對於性能關鍵路徑的函數,最好避免使用反射。 +第三个原因,基于反射的代码通常比正常的代码运行速度慢一到两个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以使用反射可能会使程序更加清晰。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。 diff --git a/ch12/ch12.md b/ch12/ch12.md index 33c9c5a7..1e6c9b40 100644 --- a/ch12/ch12.md +++ b/ch12/ch12.md @@ -1,5 +1,5 @@ # 第十二章 反射 -Go語音提供了一種機製在運行時更新變量和檢査它們的值、調用它們的方法和它們支持的內在操作,但是在編譯時併不知道這些變量的具體類型。這種機製被稱爲反射。反射也可以讓我們將類型本身作爲第一類的值類型處理。 +Go语音提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。 -在本章,我們將探討Go語言的反射特性,看看它可以給語言增加哪些表達力,以及在兩個至關重要的API是如何用反射機製的:一個是fmt包提供的字符串格式功能,另一個是類似encoding/json和encoding/xml提供的針對特定協議的編解碼功能。對於我們在4.6節中看到過的text/template和html/template包,它們的實現也是依賴反射技術的。然後,反射是一個複雜的內省技術,不應該隨意使用,因此,盡管上面這些包內部都是用反射技術實現的,但是它們自己的API都沒有公開反射相關的接口。 +在本章,我们将探讨Go语言的反射特性,看看它可以给语言增加哪些表达力,以及在两个至关重要的API是如何用反射机制的:一个是fmt包提供的字符串格式功能,另一个是类似encoding/json和encoding/xml提供的针对特定协议的编解码功能。对于我们在4.6节中看到过的text/template和html/template包,它们的实现也是依赖反射技术的。然后,反射是一个复杂的内省技术,不应该随意使用,因此,尽管上面这些包内部都是用反射技术实现的,但是它们自己的API都没有公开反射相关的接口。 diff --git a/ch13/ch13-01.md b/ch13/ch13-01.md index 591a2860..559fe208 100644 --- a/ch13/ch13-01.md +++ b/ch13/ch13-01.md @@ -1,33 +1,33 @@ ## 13.1. unsafe.Sizeof, Alignof 和 Offsetof -unsafe.Sizeof函數返迴操作數在內存中的字節大小,參數可以是任意類型的表達式,但是它併不會對表達式進行求值。一個Sizeof函數調用是一個對應uintptr類型的常量表達式,因此返迴的結果可以用作數組類型的長度大小,或者用作計算其他的常量。 +unsafe.Sizeof函数返回操作数在内存中的字节大小,参数可以是任意类型的表达式,但是它并不会对表达式进行求值。一个Sizeof函数调用是一个对应uintptr类型的常量表达式,因此返回的结果可以用作数组类型的长度大小,或者用作计算其他的常量。 ```Go import "unsafe" fmt.Println(unsafe.Sizeof(float64(0))) // "8" ``` -Sizeof函數返迴的大小隻包括數據結構中固定的部分,例如字符串對應結構體中的指針和字符串長度部分,但是併不包含指針指向的字符串的內容。Go語言中非聚合類型通常有一個固定的大小,盡管在不同工具鏈下生成的實際大小可能會有所不同。考慮到可移植性,引用類型或包含引用類型的大小在32位平台上是4個字節,在64位平台上是8個字節。 +Sizeof函数返回的大小只包括数据结构中固定的部分,例如字符串对应结构体中的指针和字符串长度部分,但是并不包含指针指向的字符串的内容。Go语言中非聚合类型通常有一个固定的大小,尽管在不同工具链下生成的实际大小可能会有所不同。考虑到可移植性,引用类型或包含引用类型的大小在32位平台上是4个字节,在64位平台上是8个字节。 -計算機在加載和保存數據時,如果內存地址合理地對齊的將會更有效率。例如2字節大小的int16類型的變量地址應該是偶數,一個4字節大小的rune類型變量的地址應該是4的倍數,一個8字節大小的float64、uint64或64-bit指針類型變量的地址應該是8字節對齊的。但是對於再大的地址對齊倍數則是不需要的,卽使是complex128等較大的數據類型最多也隻是8字節對齊。 +计算机在加载和保存数据时,如果内存地址合理地对齐的将会更有效率。例如2字节大小的int16类型的变量地址应该是偶数,一个4字节大小的rune类型变量的地址应该是4的倍数,一个8字节大小的float64、uint64或64-bit指针类型变量的地址应该是8字节对齐的。但是对于再大的地址对齐倍数则是不需要的,即使是complex128等较大的数据类型最多也只是8字节对齐。 -由於地址對齊這個因素,一個聚合類型(結構體或數組)的大小至少是所有字段或元素大小的總和,或者更大因爲可能存在內存空洞。內存空洞是編譯器自動添加的沒有被使用的內存空間,用於保證後面每個字段或元素的地址相對於結構或數組的開始地址能夠合理地對齊(譯註:內存空洞可能會存在一些隨機數據,可能會對用unsafe包直接操作內存的處理産生影響)。 +由于地址对齐这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐(译注:内存空洞可能会存在一些随机数据,可能会对用unsafe包直接操作内存的处理产生影响)。 -類型 | 大小 +类型 | 大小 ----------------------------- | ---- -bool | 1個字節 -intN, uintN, floatN, complexN | N/8個字節(例如float64是8個字節) -int, uint, uintptr | 1個機器字 -*T | 1個機器字 -string | 2個機器字(data,len) -[]T | 3個機器字(data,len,cap) -map | 1個機器字 -func | 1個機器字 -chan | 1個機器字 -interface | 2個機器字(type,value) - -Go語言的規范併沒有要求一個字段的聲明順序和內存中的順序是一致的,所以理論上一個編譯器可以隨意地重新排列每個字段的內存位置,隨然在寫作本書的時候編譯器還沒有這麽做。下面的三個結構體雖然有着相同的字段,但是第一種寫法比另外的兩個需要多50%的內存。 +bool | 1个字节 +intN, uintN, floatN, complexN | N/8个字节(例如float64是8个字节) +int, uint, uintptr | 1个机器字 +*T | 1个机器字 +string | 2个机器字(data,len) +[]T | 3个机器字(data,len,cap) +map | 1个机器字 +func | 1个机器字 +chan | 1个机器字 +interface | 2个机器字(type,value) + +Go语言的规范并没有要求一个字段的声明顺序和内存中的顺序是一致的,所以理论上一个编译器可以随意地重新排列每个字段的内存位置,随然在写作本书的时候编译器还没有这么做。下面的三个结构体虽然有着相同的字段,但是第一种写法比另外的两个需要多50%的内存。 ```Go // 64-bit 32-bit @@ -36,13 +36,13 @@ struct{ float64; int16; bool } // 2 words 3words struct{ bool; int16; float64 } // 2 words 3words ``` -關於內存地址對齊算法的細節超出了本書的范圍,也不是每一個結構體都需要擔心這個問題,不過有效的包裝可以使數據結構更加緊湊(譯註:未來的Go語言編譯器應該會默認優化結構體的順序,當然用於應該也能夠指定具體的內存布局,相同討論請參考 [Issue10014](https://github.com/golang/go/issues/10014) ),內存使用率和性能都可能會受益。 +关于内存地址对齐算法的细节超出了本书的范围,也不是每一个结构体都需要担心这个问题,不过有效的包装可以使数据结构更加紧凑(译注:未来的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 { @@ -52,11 +52,11 @@ var x struct { } ``` -下面顯示了對x和它的三個字段調用unsafe包相關函數的計算結果: +下面显示了对x和它的三个字段调用unsafe包相关函数的计算结果: ![](../images/ch13-01.png) -32位繫統: +32位系统: ``` Sizeof(x) = 16 Alignof(x) = 4 @@ -65,7 +65,7 @@ Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2 Sizeof(x.c) = 12 Alignof(x.c) = 4 Offsetof(x.c) = 4 ``` -64位繫統: +64位系统: ``` Sizeof(x) = 32 Alignof(x) = 8 @@ -74,5 +74,5 @@ Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2 Sizeof(x.c) = 24 Alignof(x.c) = 8 Offsetof(x.c) = 8 ``` -雖然這幾個函數在不安全的unsafe包,但是這幾個函數調用併不是眞的不安全,特别在需要優化內存空間時它們返迴的結果對於理解原生的內存布局很有幫助。 +虽然这几个函数在不安全的unsafe包,但是这几个函数调用并不是真的不安全,特别在需要优化内存空间时它们返回的结果对于理解原生的内存布局很有帮助。 diff --git a/ch13/ch13-02.md b/ch13/ch13-02.md index b3e9cfd4..7fa3d015 100644 --- a/ch13/ch13-02.md +++ b/ch13/ch13-02.md @@ -1,8 +1,8 @@ ## 13.2. unsafe.Pointer -大多數指針類型會寫成`*T`,表示是“一個指向T類型變量的指針”。unsafe.Pointer是特别定義的一種指針類型(譯註:類似C語言中的`void*`類型的指針),它可以包含任意類型變量的地址。當然,我們不可以直接通過`*p`來獲取unsafe.Pointer指針指向的眞實變量的值,因爲我們併不知道變量的具體類型。和普通指針一樣,unsafe.Pointer指針也是可以比較的,併且支持和nil常量比較判斷是否爲空指針。 +大多数指针类型会写成`*T`,表示是“一个指向T类型变量的指针”。unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的`void*`类型的指针),它可以包含任意类型变量的地址。当然,我们不可以直接通过`*p`来获取unsafe.Pointer指针指向的真实变量的值,因为我们并不知道变量的具体类型。和普通指针一样,unsafe.Pointer指针也是可以比较的,并且支持和nil常量比较判断是否为空指针。 -一個普通的`*T`類型指針可以被轉化爲unsafe.Pointer類型指針,併且一個unsafe.Pointer類型指針也可以被轉迴普通的指針,被轉迴普通的指針類型併不需要和原始的`*T`類型相同。通過將`*float64`類型指針轉化爲`*uint64`類型指針,我們可以査看一個浮點數變量的位模式。 +一个普通的`*T`类型指针可以被转化为unsafe.Pointer类型指针,并且一个unsafe.Pointer类型指针也可以被转回普通的指针,被转回普通的指针类型并不需要和原始的`*T`类型相同。通过将`*float64`类型指针转化为`*uint64`类型指针,我们可以查看一个浮点数变量的位模式。 ```Go package math @@ -12,11 +12,11 @@ func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) } fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000" ``` -通過轉爲新類型指針,我們可以更新浮點數的位模式。通過位模式操作浮點數是可以的,但是更重要的意義是指針轉換語法讓我們可以在不破壞類型繫統的前提下向內存寫入任意的值。 +通过转为新类型指针,我们可以更新浮点数的位模式。通过位模式操作浮点数是可以的,但是更重要的意义是指针转换语法让我们可以在不破坏类型系统的前提下向内存写入任意的值。 -一個unsafe.Pointer指針也可以被轉化爲uintptr類型,然後保存到指針型數值變量中(譯註:這隻是和當前指針相同的一個數字值,併不是一個指針),然後用以做必要的指針數值運算。(第三章內容,uintptr是一個無符號的整型數,足以保存一個地址)這種轉換雖然也是可逆的,但是將uintptr轉爲unsafe.Pointer指針可能會破壞類型繫統,因爲併不是所有的數字都是有效的內存地址。 +一个unsafe.Pointer指针也可以被转化为uintptr类型,然后保存到指针型数值变量中(译注:这只是和当前指针相同的一个数字值,并不是一个指针),然后用以做必要的指针数值运算。(第三章内容,uintptr是一个无符号的整型数,足以保存一个地址)这种转换虽然也是可逆的,但是将uintptr转为unsafe.Pointer指针可能会破坏类型系统,因为并不是所有的数字都是有效的内存地址。 -許多將unsafe.Pointer指針轉爲原生數字,然後再轉迴爲unsafe.Pointer類型指針的操作也是不安全的。比如下面的例子需要將變量x的地址加上b字段地址偏移量轉化爲`*int16`類型指針,然後通過該指針更新x.b: +许多将unsafe.Pointer指针转为原生数字,然后再转回为unsafe.Pointer类型指针的操作也是不安全的。比如下面的例子需要将变量x的地址加上b字段地址偏移量转化为`*int16`类型指针,然后通过该指针更新x.b: gopl.io/ch13/unsafeptr ```Go @@ -26,14 +26,14 @@ var x struct { c []int } -// 和 pb := &x.b 等價 +// 和 pb := &x.b 等价 pb := (*int16)(unsafe.Pointer( uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b))) *pb = 42 fmt.Println(x.b) // "42" ``` -上面的寫法盡管很繁瑣,但在這里併不是一件壞事,因爲這些功能應該很謹慎地使用。不要試圖引入一個uintptr類型的臨時變量,因爲它可能會破壞代碼的安全性(譯註:這是眞正可以體會unsafe包爲何不安全的例子)。下面段代碼是錯誤的: +上面的写法尽管很繁琐,但在这里并不是一件坏事,因为这些功能应该很谨慎地使用。不要试图引入一个uintptr类型的临时变量,因为它可能会破坏代码的安全性(译注:这是真正可以体会unsafe包为何不安全的例子)。下面段代码是错误的: ```Go // NOTE: subtly incorrect! @@ -42,21 +42,21 @@ pb := (*int16)(unsafe.Pointer(tmp)) *pb = 42 ``` -産生錯誤的原因很微妙。有時候垃圾迴收器會移動一些變量以降低內存碎片等問題。這類垃圾迴收器被稱爲移動GC。當一個變量被移動,所有的保存改變量舊地址的指針必須同時被更新爲變量移動後的新地址。從垃圾收集器的視角來看,一個unsafe.Pointer是一個指向變量的指針,因此當變量被移動是對應的指針也必須被更新;但是uintptr類型的臨時變量隻是一個普通的數字,所以其值不應該被改變。上面錯誤的代碼因爲引入一個非指針的臨時變量tmp,導致垃圾收集器無法正確識别這個是一個指向變量x的指針。當第二個語句執行時,變量x可能已經被轉移,這時候臨時變量tmp也就不再是現在的`&x.b`地址。第三個向之前無效地址空間的賦值語句將徹底摧譭整個程序! +产生错误的原因很微妙。有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动,所有的保存改变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被移动是对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp,导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时,变量x可能已经被转移,这时候临时变量tmp也就不再是现在的`&x.b`地址。第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序! -還有很多類似原因導致的錯誤。例如這條語句: +还有很多类似原因导致的错误。例如这条语句: ```Go -pT := uintptr(unsafe.Pointer(new(T))) // 提示: 錯誤! +pT := uintptr(unsafe.Pointer(new(T))) // 提示: 错误! ``` -這里併沒有指針引用`new`新創建的變量,因此該語句執行完成之後,垃圾收集器有權馬上迴收其內存空間,所以返迴的pT將是無效的地址。 +这里并没有指针引用`new`新创建的变量,因此该语句执行完成之后,垃圾收集器有权马上回收其内存空间,所以返回的pT将是无效的地址。 -雖然目前的Go語言實現還沒有使用移動GC(譯註:未來可能實現),但這不該是編寫錯誤代碼僥幸的理由:當前的Go語言實現已經有移動變量的場景。在5.2節我們提到goroutine的棧是根據需要動態增長的。當發送棧動態增長的時候,原來棧中的所以變量可能需要被移動到新的更大的棧中,所以我們併不能確保變量的地址在整個使用週期內是不變的。 +虽然目前的Go语言实现还没有使用移动GC(译注:未来可能实现),但这不该是编写错误代码侥幸的理由:当前的Go语言实现已经有移动变量的场景。在5.2节我们提到goroutine的栈是根据需要动态增长的。当发送栈动态增长的时候,原来栈中的所以变量可能需要被移动到新的更大的栈中,所以我们并不能确保变量的地址在整个使用周期内是不变的。 -在編寫本文時,還沒有清晰的原則來指引Go程序員,什麽樣的unsafe.Pointer和uintptr的轉換是不安全的(參考 [Issue7192](https://github.com/golang/go/issues/7192) ). 譯註: 該問題已經關閉),因此我們強烈建議按照最壞的方式處理。將所有包含變量地址的uintptr類型變量當作BUG處理,同時減少不必要的unsafe.Pointer類型到uintptr類型的轉換。在第一個例子中,有三個轉換——字段偏移量到uintptr的轉換和轉迴unsafe.Pointer類型的操作——所有的轉換全在一個表達式完成。 +在编写本文时,还没有清晰的原则来指引Go程序员,什么样的unsafe.Pointer和uintptr的转换是不安全的(参考 [Issue7192](https://github.com/golang/go/issues/7192) ). 译注: 该问题已经关闭),因此我们强烈建议按照最坏的方式处理。将所有包含变量地址的uintptr类型变量当作BUG处理,同时减少不必要的unsafe.Pointer类型到uintptr类型的转换。在第一个例子中,有三个转换——字段偏移量到uintptr的转换和转回unsafe.Pointer类型的操作——所有的转换全在一个表达式完成。 -當調用一個庫函數,併且返迴的是uintptr類型地址時(譯註:普通方法實現的函數不盡量不要返迴該類型。下面例子是reflect包的函數,reflect包和unsafe包一樣都是采用特殊技術實現的,編譯器可能給它們開了後門),比如下面反射包中的相關函數,返迴的結果應該立卽轉換爲unsafe.Pointer以確保指針指向的是相同的變量。 +当调用一个库函数,并且返回的是uintptr类型地址时(译注:普通方法实现的函数不尽量不要返回该类型。下面例子是reflect包的函数,reflect包和unsafe包一样都是采用特殊技术实现的,编译器可能给它们开了后门),比如下面反射包中的相关函数,返回的结果应该立即转换为unsafe.Pointer以确保指针指向的是相同的变量。 ```Go package reflect diff --git a/ch13/ch13-03.md b/ch13/ch13-03.md index 7d6a87a4..95019312 100644 --- a/ch13/ch13-03.md +++ b/ch13/ch13-03.md @@ -1,6 +1,6 @@ -## 13.3. 示例: 深度相等判斷 +## 13.3. 示例: 深度相等判断 -來自reflect包的DeepEqual函數可以對兩個值進行深度相等判斷。DeepEqual函數使用內建的==比較操作符對基礎類型進行相等判斷,對於複合類型則遞歸該變量的每個基礎類型然後做類似的比較判斷。因爲它可以工作在任意的類型上,甚至對於一些不支持==操作運算符的類型也可以工作,因此在一些測試代碼中廣泛地使用該函數。比如下面的代碼是用DeepEqual函數比較兩個字符串數組是否相等。 +来自reflect包的DeepEqual函数可以对两个值进行深度相等判断。DeepEqual函数使用内建的==比较操作符对基础类型进行相等判断,对于复合类型则递归该变量的每个基础类型然后做类似的比较判断。因为它可以工作在任意的类型上,甚至对于一些不支持==操作运算符的类型也可以工作,因此在一些测试代码中广泛地使用该函数。比如下面的代码是用DeepEqual函数比较两个字符串数组是否相等。 ```Go func TestSplit(t *testing.T) { @@ -10,7 +10,7 @@ func TestSplit(t *testing.T) { } ``` -盡管DeepEqual函數很方便,而且可以支持任意的數據類型,但是它也有不足之處。例如,它將一個nil值的map和非nil值但是空的map視作不相等,同樣nil值的slice 和非nil但是空的slice也視作不相等。 +尽管DeepEqual函数很方便,而且可以支持任意的数据类型,但是它也有不足之处。例如,它将一个nil值的map和非nil值但是空的map视作不相等,同样nil值的slice 和非nil但是空的slice也视作不相等。 ```Go var a, b []string = nil, []string{} @@ -20,7 +20,7 @@ var c, d map[string]int = nil, make(map[string]int) fmt.Println(reflect.DeepEqual(c, d)) // "false" ``` -我們希望在這里實現一個自己的Equal函數,用於比較類型的值。和DeepEqual函數類似的地方是它也是基於slice和map的每個元素進行遞歸比較,不同之處是它將nil值的slice(map類似)和非nil值但是空的slice視作相等的值。基礎部分的比較可以基於reflect包完成,和12.3章的Display函數的實現方法類似。同樣,我們也定義了一個內部函數equal,用於內部的遞歸比較。讀者目前不用關心seen參數的具體含義。對於每一對需要比較的x和y,equal函數首先檢測它們是否都有效(或都無效),然後檢測它們是否是相同的類型。剩下的部分是一個鉅大的switch分支,用於相同基礎類型的元素比較。因爲頁面空間的限製,我們省略了一些相似的分支。 +我们希望在这里实现一个自己的Equal函数,用于比较类型的值。和DeepEqual函数类似的地方是它也是基于slice和map的每个元素进行递归比较,不同之处是它将nil值的slice(map类似)和非nil值但是空的slice视作相等的值。基础部分的比较可以基于reflect包完成,和12.3章的Display函数的实现方法类似。同样,我们也定义了一个内部函数equal,用于内部的递归比较。读者目前不用关心seen参数的具体含义。对于每一对需要比较的x和y,equal函数首先检测它们是否都有效(或都无效),然后检测它们是否是相同的类型。剩下的部分是一个巨大的switch分支,用于相同基础类型的元素比较。因为页面空间的限制,我们省略了一些相似的分支。 gopl.io/ch13/equal ```Go @@ -63,7 +63,7 @@ func equal(x, y reflect.Value, seen map[comparison]bool) bool { } ``` -和前面的建議一樣,我們併不公開reflect包相關的接口,所以導出的函數需要在內部自己將變量轉爲reflect.Value類型。 +和前面的建议一样,我们并不公开reflect包相关的接口,所以导出的函数需要在内部自己将变量转为reflect.Value类型。 ```Go // Equal reports whether x and y are deeply equal. @@ -78,7 +78,7 @@ type comparison struct { } ``` -爲了確保算法對於有環的數據結構也能正常退出,我們必須記録每次已經比較的變量,從而避免進入第二次的比較。Equal函數分配了一組用於比較的結構體,包含每對比較對象的地址(unsafe.Pointer形式保存)和類型。我們要記録類型的原因是,有些不同的變量可能對應相同的地址。例如,如果x和y都是數組類型,那麽x和x[0]將對應相同的地址,y和y[0]也是對應相同的地址,這可以用於區分x與y之間的比較或x[0]與y[0]之間的比較是否進行過了。 +为了确保算法对于有环的数据结构也能正常退出,我们必须记录每次已经比较的变量,从而避免进入第二次的比较。Equal函数分配了一组用于比较的结构体,包含每对比较对象的地址(unsafe.Pointer形式保存)和类型。我们要记录类型的原因是,有些不同的变量可能对应相同的地址。例如,如果x和y都是数组类型,那么x和x[0]将对应相同的地址,y和y[0]也是对应相同的地址,这可以用于区分x与y之间的比较或x[0]与y[0]之间的比较是否进行过了。 ```Go // cycle check @@ -96,7 +96,7 @@ if x.CanAddr() && y.CanAddr() { } ``` -這是Equal函數用法的例子: +这是Equal函数用法的例子: ```Go fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true" @@ -105,7 +105,7 @@ fmt.Println(Equal([]string(nil), []string{})) // "true" fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true" ``` -Equal函數甚至可以處理類似12.3章中導致Display陷入陷入死循環的帶有環的數據。 +Equal函数甚至可以处理类似12.3章中导致Display陷入陷入死循环的带有环的数据。 ```Go // Circular linked lists a -> b -> a and c -> c. @@ -122,6 +122,6 @@ fmt.Println(Equal(a, b)) // "false" fmt.Println(Equal(a, c)) // "false" ``` -**練習 13.1:** 定義一個深比較函數,對於十億以內的數字比較,忽略類型差異。 +**练习 13.1:** 定义一个深比较函数,对于十亿以内的数字比较,忽略类型差异。 -**練習 13.2:** 編寫一個函數,報告其參數是否循環數據結構。 +**练习 13.2:** 编写一个函数,报告其参数是否循环数据结构。 diff --git a/ch13/ch13-04.md b/ch13/ch13-04.md index c071e538..95aa8d96 100644 --- a/ch13/ch13-04.md +++ b/ch13/ch13-04.md @@ -1,10 +1,10 @@ -## 13.4. 通過cgo調用C代碼 +## 13.4. 通过cgo调用C代码 -Go程序可能會遇到要訪問C語言的某些硬件驅動函數的場景,或者是從一個C++語言實現的嵌入式數據庫査詢記録的場景,或者是使用Fortran語言實現的一些線性代數庫的場景。C語言作爲一個通用語言,很多庫會選擇提供一個C兼容的API,然後用其他不同的編程語言實現(譯者:Go語言需要也應該擁抱這些鉅大的代碼遺産)。 +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類型輸入的解壓縮接口。例如: +在标准库的`compress/...`子包有很多流行的压缩算法的编码和解码实现,包括流行的LZW压缩算法(Unix的compress命令用的算法)和DEFLATE压缩算法(GNU gzip命令用的算法)。这些包的API的细节虽然有些差异,但是它们都提供了针对 io.Writer类型输出的压缩接口和提供了针对io.Reader类型输入的解压缩接口。例如: ```Go package gzip // compress/gzip @@ -12,11 +12,11 @@ func NewWriter(w io.Writer) io.WriteCloser func NewReader(r io.Reader) (io.ReadCloser, error) ``` -bzip2壓縮算法,是基於優雅的Burrows-Wheeler變換算法,運行速度比gzip要慢,但是可以提供更高的壓縮比。標準庫的compress/bzip2包目前還沒有提供bzip2壓縮算法的實現。完全從頭開始實現是一個壓縮算法是一件繁瑣的工作,而且 http://bzip.org 已經有現成的libbzip2的開源實現,不僅文檔齊全而且性能又好。 +bzip2压缩算法,是基于优雅的Burrows-Wheeler变换算法,运行速度比gzip要慢,但是可以提供更高的压缩比。标准库的compress/bzip2包目前还没有提供bzip2压缩算法的实现。完全从头开始实现是一个压缩算法是一件繁琐的工作,而且 http://bzip.org 已经有现成的libbzip2的开源实现,不仅文档齐全而且性能又好。 -如果是比較小的C語言庫,我們完全可以用純Go語言重新實現一遍。如果我們對性能也沒有特殊要求的話,我們還可以用os/exec包的方法將C編寫的應用程序作爲一個子進程運行。隻有當你需要使用複雜而且性能更高的底層C接口時,就是使用cgo的場景了(譯註:用os/exec包調用子進程的方法會導致程序運行時依賴那個應用程序)。下面我們將通過一個例子講述cgo的具體用法。 +如果是比较小的C语言库,我们完全可以用纯Go语言重新实现一遍。如果我们对性能也没有特殊要求的话,我们还可以用os/exec包的方法将C编写的应用程序作为一个子进程运行。只有当你需要使用复杂而且性能更高的底层C接口时,就是使用cgo的场景了(译注:用os/exec包调用子进程的方法会导致程序运行时依赖那个应用程序)。下面我们将通过一个例子讲述cgo的具体用法。 -譯註:本章采用的代碼都是最新的。因爲之前已經出版的書中包含的代碼隻能在Go1.5之前使用。從Go1.6開始,Go語言已經明確規定了哪些Go語言指針可以之間傳入C語言函數。新代碼重點是增加了bz2alloc和bz2free的兩個函數,用於bz_stream對象空間的申請和釋放操作。下面是新代碼中增加的註釋,説明這個問題: +译注:本章采用的代码都是最新的。因为之前已经出版的书中包含的代码只能在Go1.5之前使用。从Go1.6开始,Go语言已经明确规定了哪些Go语言指针可以之间传入C语言函数。新代码重点是增加了bz2alloc和bz2free的两个函数,用于bz_stream对象空间的申请和释放操作。下面是新代码中增加的注释,说明这个问题: ```Go // The version of this program that appeared in the first and second @@ -37,9 +37,9 @@ 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代碼,對應一個獨立的文件。 +我们可以在Go代码中直接调用BZ2_bzCompressInit和BZ2_bzCompressEnd,但是对于BZ2_bzCompress,我们将定义一个C语言的包装函数,用它完成真正的工作。下面是C代码,对应一个独立的文件。 gopl.io/ch13/bzip ```C @@ -61,7 +61,7 @@ int bz2compress(bz_stream *s, int action, } ``` -現在讓我們轉到Go語言部分,第一部分如下所示。其中`import "C"`的語句是比較特别的。其實併沒有一個叫C的包,但是這行語句會讓Go編譯程序在編譯之前先運行cgo工具。 +现在让我们转到Go语言部分,第一部分如下所示。其中`import "C"`的语句是比较特别的。其实并没有一个叫C的包,但是这行语句会让Go编译程序在编译之前先运行cgo工具。 ```Go // Package bzip provides a writer that uses bzip2 compression (bzip.org). @@ -101,13 +101,13 @@ func NewWriter(out io.Writer) io.WriteCloser { } ``` -在預處理過程中,cgo工具爲生成一個臨時包用於包含所有在Go語言中訪問的C語言的函數或類型。例如C.bz_stream和C.BZ2_bzCompressInit。cgo工具通過以某種特殊的方式調用本地的C編譯器來發現在Go源文件導入聲明前的註釋中包含的C頭文件中的內容(譯註:`import "C"`語句前僅捱着的註釋是對應cgo的特殊語法,對應必要的構建參數選項和C語言代碼)。 +在预处理过程中,cgo工具为生成一个临时包用于包含所有在Go语言中访问的C语言的函数或类型。例如C.bz_stream和C.BZ2_bzCompressInit。cgo工具通过以某种特殊的方式调用本地的C编译器来发现在Go源文件导入声明前的注释中包含的C头文件中的内容(译注:`import "C"`语句前仅挨着的注释是对应cgo的特殊语法,对应必要的构建参数选项和C语言代码)。 -在cgo註釋中還可以包含#cgo指令,用於給C語言工具鏈指定特殊的參數。例如CFLAGS和LDFLAGS分别對應傳給C語言編譯器的編譯參數和鏈接器參數,使它們可以特定目録找到bzlib.h頭文件和libbz2.a庫文件。這個例子假設你已經在/usr目録成功安裝了bzip2庫。如果bzip2庫是安裝在不同的位置,你需要更新這些參數(譯註:這里有一個從純C代碼生成的cgo綁定,不依賴bzip2靜態庫和操作繫統的具體環境,具體請訪問 https://github.com/chai2010/bzip2 )。 +在cgo注释中还可以包含#cgo指令,用于给C语言工具链指定特殊的参数。例如CFLAGS和LDFLAGS分别对应传给C语言编译器的编译参数和链接器参数,使它们可以特定目录找到bzlib.h头文件和libbz2.a库文件。这个例子假设你已经在/usr目录成功安装了bzip2库。如果bzip2库是安装在不同的位置,你需要更新这些参数(译注:这里有一个从纯C代码生成的cgo绑定,不依赖bzip2静态库和操作系统的具体环境,具体请访问 https://github.com/chai2010/bzip2 )。 -NewWriter函數通過調用C語言的BZ2_bzCompressInit函數來初始化stream中的緩存。在writer結構中還包括了另一個buffer,用於輸出緩存。 +NewWriter函数通过调用C语言的BZ2_bzCompressInit函数来初始化stream中的缓存。在writer结构中还包括了另一个buffer,用于输出缓存。 -下面是Write方法的實現,返迴成功壓縮數據的大小,主體是一個循環中調用C語言的bz2compress函數實現的。從代碼可以看到,Go程序可以訪問C語言的bz_stream、char和uint類型,還可以訪問bz2compress等函數,甚至可以訪問C語言中像BZ_RUN那樣的宏定義,全部都是以C.x語法訪問。其中C.uint類型和Go語言的uint類型併不相同,卽使它們具有相同的大小也是不同的類型。 +下面是Write方法的实现,返回成功压缩数据的大小,主体是一个循环中调用C语言的bz2compress函数实现的。从代码可以看到,Go程序可以访问C语言的bz_stream、char和uint类型,还可以访问bz2compress等函数,甚至可以访问C语言中像BZ_RUN那样的宏定义,全部都是以C.x语法访问。其中C.uint类型和Go语言的uint类型并不相同,即使它们具有相同的大小也是不同的类型。 ```Go func (w *writer) Write(data []byte) (int, error) { @@ -131,9 +131,9 @@ func (w *writer) Write(data []byte) (int, error) { } ``` -在循環的每次迭代中,向bz2compress傳入數據的地址和剩餘部分的長度,還有輸出緩存w.outbuf的地址和容量。這兩個長度信息通過它們的地址傳入而不是值傳入,因爲bz2compress函數可能會根據已經壓縮的數據和壓縮後數據的大小來更新這兩個值。每個塊壓縮後的數據被寫入到底層的io.Writer。 +在循环的每次迭代中,向bz2compress传入数据的地址和剩余部分的长度,还有输出缓存w.outbuf的地址和容量。这两个长度信息通过它们的地址传入而不是值传入,因为bz2compress函数可能会根据已经压缩的数据和压缩后数据的大小来更新这两个值。每个块压缩后的数据被写入到底层的io.Writer。 -Close方法和Write方法有着類似的結構,通過一個循環將剩餘的壓縮數據刷新到輸出緩存。 +Close方法和Write方法有着类似的结构,通过一个循环将剩余的压缩数据刷新到输出缓存。 ```Go // Close flushes the compressed data and closes the stream. @@ -161,11 +161,11 @@ func (w *writer) Close() error { } ``` -壓縮完成後,Close方法用了defer函數確保函數退出前調用C.BZ2_bzCompressEnd和C.bz2free釋放相關的C語言運行時資源。此刻w.stream指針將不再有效,我們將它設置爲nil以保證安全,然後在每個方法中增加了nil檢測,以防止用戶在關閉後依然錯誤使用相關方法。 +压缩完成后,Close方法用了defer函数确保函数退出前调用C.BZ2_bzCompressEnd和C.bz2free释放相关的C语言运行时资源。此刻w.stream指针将不再有效,我们将它设置为nil以保证安全,然后在每个方法中增加了nil检测,以防止用户在关闭后依然错误使用相关方法。 -上面的實現中,不僅僅寫是非併發安全的,甚至併發調用Close和Write方法也可能導致程序的的崩潰。脩複這個問題是練習13.3的內容。 +上面的实现中,不仅仅写是非并发安全的,甚至并发调用Close和Write方法也可能导致程序的的崩溃。修复这个问题是练习13.3的内容。 -下面的bzipper程序,使用我們自己包實現的bzip2壓縮命令。它的行爲和許多Unix繫統的bzip2命令類似。 +下面的bzipper程序,使用我们自己包实现的bzip2压缩命令。它的行为和许多Unix系统的bzip2命令类似。 gopl.io/ch13/bzipper ```Go @@ -190,7 +190,7 @@ func main() { } ``` -在上面的場景中,我們使用bzipper壓縮了/usr/share/dict/words繫統自帶的詞典,從938,848字節壓縮到335,405字節。大約是原始數據大小的三分之一。然後使用繫統自帶的bunzip2命令進行解壓。壓縮前後文件的SHA256哈希碼是相同了,這也説明了我們的壓縮工具是正確的。(如果你的繫統沒有sha256sum命令,那麽請先按照練習4.2實現一個類似的工具) +在上面的场景中,我们使用bzipper压缩了/usr/share/dict/words系统自带的词典,从938,848字节压缩到335,405字节。大约是原始数据大小的三分之一。然后使用系统自带的bunzip2命令进行解压。压缩前后文件的SHA256哈希码是相同了,这也说明了我们的压缩工具是正确的。(如果你的系统没有sha256sum命令,那么请先按照练习4.2实现一个类似的工具) ``` $ go build gopl.io/ch13/bzipper @@ -204,8 +204,8 @@ $ ./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中被併發調用是安全的。 +**练习 13.3:** 使用sync.Mutex以保证bzip2.writer在多个goroutines中被并发调用是安全的。 -**練習 13.4:** 因爲C庫依賴的限製。 使用os/exec包啟動/bin/bzip2命令作爲一個子進程,提供一個純Go的bzip.NewWriter的替代實現(譯註:雖然是純Go實現,但是運行時將依賴/bin/bzip2命令,其他操作繫統可能無法運行)。 +**练习 13.4:** 因为C库依赖的限制。 使用os/exec包启动/bin/bzip2命令作为一个子进程,提供一个纯Go的bzip.NewWriter的替代实现(译注:虽然是纯Go实现,但是运行时将依赖/bin/bzip2命令,其他操作系统可能无法运行)。 diff --git a/ch13/ch13-05.md b/ch13/ch13-05.md index 92f93464..3dbffc0d 100644 --- a/ch13/ch13-05.md +++ b/ch13/ch13-05.md @@ -1,12 +1,12 @@ -## 13.5. 幾點忠告 +## 13.5. 几点忠告 -我們在前一章結尾的時候,我們警告要謹慎使用reflect包。那些警告同樣適用於本章的unsafe包。 +我们在前一章结尾的时候,我们警告要谨慎使用reflect包。那些警告同样适用于本章的unsafe包。 -高級語言使得程序員不用在關心眞正運行程序的指令細節,同時也不再需要關註許多如內存布局之類的實現細節。因爲高級語言這個絶緣的抽象層,我們可以編寫安全健壯的,併且可以運行在不同操作繫統上的具有高度可移植性的程序。 +高级语言使得程序员不用在关心真正运行程序的指令细节,同时也不再需要关注许多如内存布局之类的实现细节。因为高级语言这个绝缘的抽象层,我们可以编写安全健壮的,并且可以运行在不同操作系统上的具有高度可移植性的程序。 -但是unsafe包,它讓程序員可以透過這個絶緣的抽象層直接使用一些必要的功能,雖然可能是爲了獲得更好的性能。但是代價就是犧牲了可移植性和程序安全,因此使用unsafe包是一個危險的行爲。我們對何時以及如何使用unsafe包的建議和我們在11.5節提到的Knuth對過早優化的建議類似。大多數Go程序員可能永遠不會需要直接使用unsafe包。當然,也永遠都會有一些需要使用unsafe包實現會更簡單的場景。如果確實認爲使用unsafe包是最理想的方式,那麽應該盡可能將它限製在較小的范圍,那樣其它代碼就忽略unsafe的影響。 +但是unsafe包,它让程序员可以透过这个绝缘的抽象层直接使用一些必要的功能,虽然可能是为了获得更好的性能。但是代价就是牺牲了可移植性和程序安全,因此使用unsafe包是一个危险的行为。我们对何时以及如何使用unsafe包的建议和我们在11.5节提到的Knuth对过早优化的建议类似。大多数Go程序员可能永远不会需要直接使用unsafe包。当然,也永远都会有一些需要使用unsafe包实现会更简单的场景。如果确实认为使用unsafe包是最理想的方式,那么应该尽可能将它限制在较小的范围,那样其它代码就忽略unsafe的影响。 -現在,趕緊將最後兩章拋入腦後吧。編寫一些實實在在的應用是眞理。請遠離reflect的unsafe包,除非你確實需要它們。 +现在,赶紧将最后两章抛入脑后吧。编写一些实实在在的应用是真理。请远离reflect的unsafe包,除非你确实需要它们。 -最後,用Go快樂地編程。我們希望你能像我們一樣喜歡Go語言。 +最后,用Go快乐地编程。我们希望你能像我们一样喜欢Go语言。 diff --git a/ch13/ch13.md b/ch13/ch13.md index 480d5aa9..5077d386 100644 --- a/ch13/ch13.md +++ b/ch13/ch13.md @@ -1,20 +1,20 @@ -# 第13章 底層編程 +# 第13章 底层编程 -Go語言的設計包含了諸多安全策略,限製了可能導致程序運行出現錯誤的用法。編譯時類型檢査檢査可以發現大多數類型不匹配的操作,例如兩個字符串做減法的錯誤。字符串、map、slice和chan等所有的內置類型,都有嚴格的類型轉換規則。 +Go语言的设计包含了诸多安全策略,限制了可能导致程序运行出现错误的用法。编译时类型检查检查可以发现大多数类型不匹配的操作,例如两个字符串做减法的错误。字符串、map、slice和chan等所有的内置类型,都有严格的类型转换规则。 -對於無法靜態檢測到的錯誤,例如數組訪問越界或使用空指針,運行時動態檢測可以保證程序在遇到問題的時候立卽終止併打印相關的錯誤信息。自動內存管理(垃圾內存自動迴收)可以消除大部分野指針和內存洩漏相關的問題。 +对于无法静态检测到的错误,例如数组访问越界或使用空指针,运行时动态检测可以保证程序在遇到问题的时候立即终止并打印相关的错误信息。自动内存管理(垃圾内存自动回收)可以消除大部分野指针和内存泄漏相关的问题。 -Go語言的實現刻意隱藏了很多底層細節。我們無法知道一個結構體眞實的內存布局,也無法獲取一個運行時函數對應的機器碼,也無法知道當前的goroutine是運行在哪個操作繫統線程之上。事實上,Go語言的調度器會自己決定是否需要將某個goroutine從一個操作繫統線程轉移到另一個操作繫統線程。一個指向變量的指針也併沒有展示變量眞實的地址。因爲垃圾迴收器可能會根據需要移動變量的內存位置,當然變量對應的地址也會被自動更新。 +Go语言的实现刻意隐藏了很多底层细节。我们无法知道一个结构体真实的内存布局,也无法获取一个运行时函数对应的机器码,也无法知道当前的goroutine是运行在哪个操作系统线程之上。事实上,Go语言的调度器会自己决定是否需要将某个goroutine从一个操作系统线程转移到另一个操作系统线程。一个指向变量的指针也并没有展示变量真实的地址。因为垃圾回收器可能会根据需要移动变量的内存位置,当然变量对应的地址也会被自动更新。 -總的來説,Go語言的這些特性使得Go程序相比較低級的C語言來説更容易預測和理解,程序也不容易崩潰。通過隱藏底層的實現細節,也使得Go語言編寫的程序具有高度的可移植性,因爲語言的語義在很大程度上是獨立於任何編譯器實現、操作繫統和CPU繫統結構的(當然也不是完全絶對獨立:例如int等類型就依賴於CPU機器字的大小,某些表達式求值的具體順序,還有編譯器實現的一些額外的限製等)。 +总的来说,Go语言的这些特性使得Go程序相比较低级的C语言来说更容易预测和理解,程序也不容易崩溃。通过隐藏底层的实现细节,也使得Go语言编写的程序具有高度的可移植性,因为语言的语义在很大程度上是独立于任何编译器实现、操作系统和CPU系统结构的(当然也不是完全绝对独立:例如int等类型就依赖于CPU机器字的大小,某些表达式求值的具体顺序,还有编译器实现的一些额外的限制等)。 -有時候我們可能會放棄使用部分語言特性而優先選擇更好具有更好性能的方法,例如需要與其他語言編寫的庫互操作,或者用純Go語言無法實現的某些函數。 +有时候我们可能会放弃使用部分语言特性而优先选择更好具有更好性能的方法,例如需要与其他语言编写的库互操作,或者用纯Go语言无法实现的某些函数。 -在本章,我們將展示如何使用unsafe包來襬脫Go語言規則帶來的限製,講述如何創建C語言函數庫的綁定,以及如何進行繫統調用。 +在本章,我们将展示如何使用unsafe包来摆脱Go语言规则带来的限制,讲述如何创建C语言函数库的绑定,以及如何进行系统调用。 -本章提供的方法不應該輕易使用(譯註:屬於黑魔法,雖然可能功能很強大,但是也容易誤傷到自己)。如果沒有處理好細節,它們可能導致各種不可預測的併且隱晦的錯誤,甚至連有經驗的的C語言程序員也無法理解這些錯誤。使用unsafe包的同時也放棄了Go語言保證與未來版本的兼容性的承諾,因爲它必然會在有意無意中會使用很多實現的細節,而這些實現的細節在未來的Go語言中很可能會被改變。 +本章提供的方法不应该轻易使用(译注:属于黑魔法,虽然可能功能很强大,但是也容易误伤到自己)。如果没有处理好细节,它们可能导致各种不可预测的并且隐晦的错误,甚至连有经验的的C语言程序员也无法理解这些错误。使用unsafe包的同时也放弃了Go语言保证与未来版本的兼容性的承诺,因为它必然会在有意无意中会使用很多实现的细节,而这些实现的细节在未来的Go语言中很可能会被改变。 -要註意的是,unsafe包是一個采用特殊方式實現的包。雖然它可以和普通包一樣的導入和使用,但它實際上是由編譯器實現的。它提供了一些訪問語言內部特性的方法,特别是內存布局相關的細節。將這些特性封裝到一個獨立的包中,是爲在極少數情況下需要使用的時候,同時引起人們的註意(譯註:因爲看包的名字就知道使用unsafe包是不安全的)。此外,有一些環境因爲安全的因素可能限製這個包的使用。 +要注意的是,unsafe包是一个采用特殊方式实现的包。虽然它可以和普通包一样的导入和使用,但它实际上是由编译器实现的。它提供了一些访问语言内部特性的方法,特别是内存布局相关的细节。将这些特性封装到一个独立的包中,是为在极少数情况下需要使用的时候,同时引起人们的注意(译注:因为看包的名字就知道使用unsafe包是不安全的)。此外,有一些环境因为安全的因素可能限制这个包的使用。 -不過unsafe包被廣泛地用於比較低級的包, 例如runtime、os、syscall還有net包等,因爲它們需要和操作繫統密切配合,但是對於普通的程序一般是不需要使用unsafe包的。 +不过unsafe包被广泛地用于比较低级的包, 例如runtime、os、syscall还有net包等,因为它们需要和操作系统密切配合,但是对于普通的程序一般是不需要使用unsafe包的。 diff --git a/ch2/ch2-01.md b/ch2/ch2-01.md index af157707..13b7548c 100644 --- a/ch2/ch2-01.md +++ b/ch2/ch2-01.md @@ -1,8 +1,8 @@ ## 2.1. 命名 -Go語言中的函數名、變量名、常量名、類型名、語句標號和包名等所有的命名,都遵循一個簡單的命名規則:一個名字必須以一個字母(Unicode字母)或下劃線開頭,後面可以跟任意數量的字母、數字或下劃線。大寫字母和小寫字母是不同的:heapSort和Heapsort是兩個不同的名字。 +Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的:heapSort和Heapsort是两个不同的名字。 -Go語言中類似if和switch的關鍵字有25個;關鍵字不能用於自定義名字,隻能在特定語法結構中使用。 +Go语言中类似if和switch的关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。 ``` break default func interface select @@ -12,25 +12,25 @@ const fallthrough if range type continue for import return var ``` -此外,還有大約30多個預定義的名字,比如int和true等,主要對應內建的常量、類型和函數。 +此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。 ``` -內建常量: true false iota nil +内建常量: true false iota nil -內建類型: int int8 int16 int32 int64 +内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error -內建函數: make len cap new append copy close delete +内建函数: make len cap new append copy close delete complex real imag panic recover ``` -這些內部預先定義的名字併不是關鍵字,你可以再定義中重新使用它們。在一些特殊的場景中重新定義它們也是有意義的,但是也要註意避免過度而引起語義混亂。 +这些内部预先定义的名字并不是关键字,你可以再定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。 -如果一個名字是在函數內部定義,那麽它的就隻在函數內部有效。如果是在函數外部定義,那麽將在當前包的所有文件中都可以訪問。名字的開頭字母的大小寫決定了名字在包外的可見性。如果一個名字是大寫字母開頭的(譯註:必須是在函數外部定義的包級名字;包級函數名本身也是包級名字),那麽它將是導出的,也就是説可以被外部的包訪問,例如fmt包的Printf函數就是導出的,可以在fmt包外部訪問。包本身的名字一般總是用小寫字母。 +如果一个名字是在函数内部定义,那么它的就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的(译注:必须是在函数外部定义的包级名字;包级函数名本身也是包级名字),那么它将是导出的,也就是说可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母。 -名字的長度沒有邏輯限製,但是Go語言的風格是盡量使用短小的名字,對於局部變量尤其是這樣;你會經常看到i之類的短名字,而不是冗長的theLoopIndex命名。通常來説,如果一個名字的作用域比較大,生命週期也比較長,那麽用長的名字將會更有意義。 +名字的长度没有逻辑限制,但是Go语言的风格是尽量使用短小的名字,对于局部变量尤其是这样;你会经常看到i之类的短名字,而不是冗长的theLoopIndex命名。通常来说,如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义。 -在習慣上,Go語言程序員推薦使用 **駝峯式** 命名,當名字有幾個單詞組成的時優先使用大小寫分隔,而不是優先用下劃線分隔。因此,在標準庫有QuoteRuneToASCII和parseRequestLine這樣的函數命名,但是一般不會用quote_rune_to_ASCII和parse_request_line這樣的命名。而像ASCII和HTML這樣的縮略詞則避免使用大小寫混合的寫法,它們可能被稱爲htmlEscape、HTMLEscape或escapeHTML,但不會是escapeHtml。 +在习惯上,Go语言程序员推荐使用 **驼峰式** 命名,当名字有几个单词组成的时优先使用大小写分隔,而不是优先用下划线分隔。因此,在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名,但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtml。 diff --git a/ch2/ch2-02.md b/ch2/ch2-02.md index 3366527a..c5e42009 100644 --- a/ch2/ch2-02.md +++ b/ch2/ch2-02.md @@ -1,8 +1,8 @@ -## 2.2. 聲明 +## 2.2. 声明 -聲明語句定義了程序的各種實體對象以及部分或全部的屬性。Go語言主要有四種類型的聲明語句:var、const、type和func,分别對應變量、常量、類型和函數實體對象的聲明。這一章我們重點討論變量和類型的聲明,第三章將討論常量的聲明,第五章將討論函數的聲明。 +声明语句定义了程序的各种实体对象以及部分或全部的属性。Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。这一章我们重点讨论变量和类型的声明,第三章将讨论常量的声明,第五章将讨论函数的声明。 -一個Go語言編寫的程序對應一個或多個以.go爲文件後綴名的源文件中。每個源文件以包的聲明語句開始,説明該源文件是屬於哪個包。包聲明語句之後是import語句導入依賴的其它包,然後是包一級的類型、變量、常量、函數的聲明語句,包一級的各種類型的聲明語句的順序無關緊要(譯註:函數內部的名字則必須先聲明之後才能使用)。例如,下面的例子中聲明了一個常量、一個函數和兩個變量: +一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件中。每个源文件以包的声明语句开始,说明该源文件是属于哪个包。包声明语句之后是import语句导入依赖的其它包,然后是包一级的类型、变量、常量、函数的声明语句,包一级的各种类型的声明语句的顺序无关紧要(译注:函数内部的名字则必须先声明之后才能使用)。例如,下面的例子中声明了一个常量、一个函数和两个变量: gopl.io/ch2/boiling ```Go @@ -22,11 +22,11 @@ func main() { } ``` -其中常量boilingF是在包一級范圍聲明語句聲明的,然後f和c兩個變量是在main函數內部聲明的聲明語句聲明的。在包一級聲明語句聲明的名字可在整個包對應的每個源文件中訪問,而不是僅僅在其聲明語句所在的源文件中訪問。相比之下,局部聲明的名字就隻能在函數內部很小的范圍被訪問。 +其中常量boilingF是在包一级范围声明语句声明的,然后f和c两个变量是在main函数内部声明的声明语句声明的。在包一级声明语句声明的名字可在整个包对应的每个源文件中访问,而不是仅仅在其声明语句所在的源文件中访问。相比之下,局部声明的名字就只能在函数内部很小的范围被访问。 -一個函數的聲明由一個函數名字、參數列表(由函數的調用者提供參數變量的具體值)、一個可選的返迴值列表和包含函數定義的函數體組成。如果函數沒有返迴值,那麽返迴值列表是省略的。執行函數從函數的第一個語句開始,依次順序執行直到遇到renturn返迴語句,如果沒有返迴語句則是執行到函數末尾,然後返迴到函數調用者。 +一个函数的声明由一个函数名字、参数列表(由函数的调用者提供参数变量的具体值)、一个可选的返回值列表和包含函数定义的函数体组成。如果函数没有返回值,那么返回值列表是省略的。执行函数从函数的第一个语句开始,依次顺序执行直到遇到renturn返回语句,如果没有返回语句则是执行到函数末尾,然后返回到函数调用者。 -我們已經看到過很多函數聲明和函數調用的例子了,在第五章將深入討論函數的相關細節,這里隻簡單解釋下。下面的fToC函數封裝了溫度轉換的處理邏輯,這樣它隻需要被定義一次,就可以在多個地方多次被使用。在這個例子中,main函數就調用了兩次fToC函數,分别是使用在局部定義的兩個常量作爲調用函數的參數。 +我们已经看到过很多函数声明和函数调用的例子了,在第五章将深入讨论函数的相关细节,这里只简单解释下。下面的fToC函数封装了温度转换的处理逻辑,这样它只需要被定义一次,就可以在多个地方多次被使用。在这个例子中,main函数就调用了两次fToC函数,分别是使用在局部定义的两个常量作为调用函数的参数。 gopl.io/ch2/ftoc ```Go diff --git a/ch2/ch2-03-1.md b/ch2/ch2-03-1.md index 6b5d60e0..017ec97c 100644 --- a/ch2/ch2-03-1.md +++ b/ch2/ch2-03-1.md @@ -1,6 +1,6 @@ -### 2.3.1. 簡短變量聲明 +### 2.3.1. 简短变量声明 -在函數內部,有一種稱爲簡短變量聲明語句的形式可用於聲明和初始化局部變量。它以“名字 := 表達式”形式聲明變量,變量的類型根據表達式來自動推導。下面是lissajous函數中的三個簡短變量聲明語句(§1.4): +在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部变量。它以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导。下面是lissajous函数中的三个简短变量声明语句(§1.4): ```Go anim := gif.GIF{LoopCount: nframes} @@ -8,7 +8,7 @@ freq := rand.Float64() * 3.0 t := 0.0 ``` -因爲簡潔和靈活的特點,簡短變量聲明被廣泛用於大部分的局部變量的聲明和初始化。var形式的聲明語句往往是用於需要顯式指定變量類型地方,或者因爲變量稍後會被重新賦值而初始值無關緊要的地方。 +因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。var形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。 ```Go i := 100 // an int @@ -18,21 +18,21 @@ var err error var p Point ``` -和var形式聲明變語句一樣,簡短變量聲明語句也可以用來聲明和初始化一組變量: +和var形式声明变语句一样,简短变量声明语句也可以用来声明和初始化一组变量: ```Go i, j := 0, 1 ``` -但是這種同時聲明多個變量的方式應該限製隻在可以提高代碼可讀性的地方使用,比如for語句的循環的初始化語句部分。 +但是这种同时声明多个变量的方式应该限制只在可以提高代码可读性的地方使用,比如for语句的循环的初始化语句部分。 -請記住“:=”是一個變量聲明語句,而“=‘是一個變量賦值操作。也不要混淆多個變量的聲明和元組的多重賦值(§2.4.1),後者是將右邊各個的表達式值賦值給左邊對應位置的各個變量: +请记住“:=”是一个变量声明语句,而“=‘是一个变量赋值操作。也不要混淆多个变量的声明和元组的多重赋值(§2.4.1),后者是将右边各个的表达式值赋值给左边对应位置的各个变量: ```Go -i, j = j, i // 交換 i 和 j 的值 +i, j = j, i // 交换 i 和 j 的值 ``` -和普通var形式的變量聲明語句一樣,簡短變量聲明語句也可以用函數的返迴值來聲明和初始化變量,像下面的os.Open函數調用將返迴兩個值: +和普通var形式的变量声明语句一样,简短变量声明语句也可以用函数的返回值来声明和初始化变量,像下面的os.Open函数调用将返回两个值: ```Go f, err := os.Open(name) @@ -43,9 +43,9 @@ if err != nil { f.Close() ``` -這里有一個比較微妙的地方:簡短變量聲明左邊的變量可能併不是全部都是剛剛聲明的。如果有一些已經在相同的詞法域聲明過了(§2.7),那麽簡短變量聲明語句對這些已經聲明過的變量就隻有賦值行爲了。 +这里有一个比较微妙的地方:简短变量声明左边的变量可能并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了(§2.7),那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了。 -在下面的代碼中,第一個語句聲明了in和err兩個變量。在第二個語句隻聲明了out一個變量,然後對已經聲明的err進行了賦值操作。 +在下面的代码中,第一个语句声明了in和err两个变量。在第二个语句只声明了out一个变量,然后对已经声明的err进行了赋值操作。 ```Go in, err := os.Open(infile) @@ -53,7 +53,7 @@ in, err := os.Open(infile) out, err := os.Create(outfile) ``` -簡短變量聲明語句中必須至少要聲明一個新的變量,下面的代碼將不能編譯通過: +简短变量声明语句中必须至少要声明一个新的变量,下面的代码将不能编译通过: ```Go f, err := os.Open(infile) @@ -61,9 +61,9 @@ f, err := os.Open(infile) f, err := os.Create(outfile) // compile error: no new variables ``` -解決的方法是第二個簡短變量聲明語句改用普通的多重賦值語言。 +解决的方法是第二个简短变量声明语句改用普通的多重赋值语言。 -簡短變量聲明語句隻有對已經在同級詞法域聲明過的變量才和賦值操作語句等價,如果變量是在外部詞法域聲明的,那麽簡短變量聲明語句將會在當前詞法域重新聲明一個新的變量。我們在本章後面將會看到類似的例子。 +简短变量声明语句只有对已经在同级词法域声明过的变量才和赋值操作语句等价,如果变量是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量。我们在本章后面将会看到类似的例子。 diff --git a/ch2/ch2-03-2.md b/ch2/ch2-03-2.md index 56837a91..cf32af6e 100644 --- a/ch2/ch2-03-2.md +++ b/ch2/ch2-03-2.md @@ -1,10 +1,10 @@ -### 2.3.2. 指針 +### 2.3.2. 指针 -一個變量對應一個保存了變量對應類型值的內存空間。普通變量在聲明語句創建時被綁定到一個變量名,比如叫x的變量,但是還有很多變量始終以表達式方式引入,例如x[i]或x.f變量。所有這些表達式一般都是讀取一個變量的值,除非它們是出現在賦值語句的左邊,這種時候是給對應變量賦予一個新的值。 +一个变量对应一个保存了变量对应类型值的内存空间。普通变量在声明语句创建时被绑定到一个变量名,比如叫x的变量,但是还有很多变量始终以表达式方式引入,例如x[i]或x.f变量。所有这些表达式一般都是读取一个变量的值,除非它们是出现在赋值语句的左边,这种时候是给对应变量赋予一个新的值。 -一個指針的值是另一個變量的地址。一個指針對應變量在內存中的存儲位置。併不是每一個值都會有一個內存地址,但是對於每一個變量必然有對應的內存地址。通過指針,我們可以直接讀或更新對應變量的值,而不需要知道該變量的名字(如果變量有名字的話)。 +一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我们可以直接读或更新对应变量的值,而不需要知道该变量的名字(如果变量有名字的话)。 -如果用“var x int”聲明語句聲明一個x變量,那麽&x表達式(取x變量的內存地址)將産生一個指向該整數變量的指針,指針對應的數據類型是`*int`,指針被稱之爲“指向int類型的指針”。如果指針名字爲p,那麽可以説“p指針指向變量x”,或者説“p指針保存了x變量的內存地址”。同時`*p`表達式對應p指針指向的變量的值。一般`*p`表達式讀取指針指向的變量的值,這里爲int類型的值,同時因爲`*p`對應一個變量,所以該表達式也可以出現在賦值語句的左邊,表示更新指針所指向的變量的值。 +如果用“var x int”声明语句声明一个x变量,那么&x表达式(取x变量的内存地址)将产生一个指向该整数变量的指针,指针对应的数据类型是`*int`,指针被称之为“指向int类型的指针”。如果指针名字为p,那么可以说“p指针指向变量x”,或者说“p指针保存了x变量的内存地址”。同时`*p`表达式对应p指针指向的变量的值。一般`*p`表达式读取指针指向的变量的值,这里为int类型的值,同时因为`*p`对应一个变量,所以该表达式也可以出现在赋值语句的左边,表示更新指针所指向的变量的值。 ```Go x := 1 @@ -14,18 +14,18 @@ fmt.Println(*p) // "1" fmt.Println(x) // "2" ``` -對於聚合類型每個成員——比如結構體的每個字段、或者是數組的每個元素——也都是對應一個變量,因此可以被取地址。 +对于聚合类型每个成员——比如结构体的每个字段、或者是数组的每个元素——也都是对应一个变量,因此可以被取地址。 -變量有時候被稱爲可尋址的值。卽使變量由表達式臨時生成,那麽表達式也必須能接受`&`取地址操作。 +变量有时候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必须能接受`&`取地址操作。 -任何類型的指針的零值都是nil。如果`p != nil`測試爲眞,那麽p是指向某個有效變量。指針之間也是可以進行相等測試的,隻有當它們指向同一個變量或全部是nil時才相等。 +任何类型的指针的零值都是nil。如果`p != nil`测试为真,那么p是指向某个有效变量。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。 ```Go var x, y int fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false" ``` -在Go語言中,返迴函數中局部變量的地址也是安全的。例如下面的代碼,調用f函數時創建局部變量v,在局部變量地址被返迴之後依然有效,因爲指針p依然引用這個變量。 +在Go语言中,返回函数中局部变量的地址也是安全的。例如下面的代码,调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。 ```Go var p = f() @@ -36,17 +36,17 @@ func f() *int { } ``` -每次調用f函數都將返迴不同的結果: +每次调用f函数都将返回不同的结果: ```Go fmt.Println(f() == f()) // "false" ``` -因爲指針包含了一個變量的地址,因此如果將指針作爲參數調用函數,那將可以在函數中通過該指針來更新變量的值。例如下面這個例子就是通過指針來更新變量的值,然後返迴更新後的值,可用在一個表達式中(譯註:這是對C語言中`++v`操作的模擬,這里隻是爲了説明指針的用法,incr函數模擬的做法併不推薦): +因为指针包含了一个变量的地址,因此如果将指针作为参数调用函数,那将可以在函数中通过该指针来更新变量的值。例如下面这个例子就是通过指针来更新变量的值,然后返回更新后的值,可用在一个表达式中(译注:这是对C语言中`++v`操作的模拟,这里只是为了说明指针的用法,incr函数模拟的做法并不推荐): ```Go func incr(p *int) int { - *p++ // 非常重要:隻是增加p指向的變量的值,併不改變p指針!!! + *p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!! return *p } @@ -55,9 +55,9 @@ incr(&v) // side effect: v is now 2 fmt.Println(incr(&v)) // "3" (and v is 3) ``` -每次我們對一個變量取地址,或者複製指針,我們都是爲原變量創建了新的别名。例如,`*p`就是是 變量v的别名。指針特别有價值的地方在於我們可以不用名字而訪問一個變量,但是這是一把雙刃劍:要找到一個變量的所有訪問者併不容易,我們必須知道變量全部的别名(譯註:這是Go語言的垃圾迴收器所做的工作)。不僅僅是指針會創建别名,很多其他引用類型也會創建别名,例如slice、map和chan,甚至結構體、數組和接口都會創建所引用變量的别名。 +每次我们对一个变量取地址,或者复制指针,我们都是为原变量创建了新的别名。例如,`*p`就是是 变量v的别名。指针特别有价值的地方在于我们可以不用名字而访问一个变量,但是这是一把双刃剑:要找到一个变量的所有访问者并不容易,我们必须知道变量全部的别名(译注:这是Go语言的垃圾回收器所做的工作)。不仅仅是指针会创建别名,很多其他引用类型也会创建别名,例如slice、map和chan,甚至结构体、数组和接口都会创建所引用变量的别名。 -指針是實現標準庫中flag包的關鍵技術,它使用命令行參數來設置對應變量的值,而這些對應命令行標誌參數的變量可能會零散分布在整個程序中。爲了説明這一點,在早些的echo版本中,就包含了兩個可選的命令行參數:`-n`用於忽略行尾的換行符,`-s sep`用於指定分隔字符(默認是空格)。下面這是第四個版本,對應包路徑爲gopl.io/ch2/echo4。 +指针是实现标准库中flag包的关键技术,它使用命令行参数来设置对应变量的值,而这些对应命令行标志参数的变量可能会零散分布在整个程序中。为了说明这一点,在早些的echo版本中,就包含了两个可选的命令行参数:`-n`用于忽略行尾的换行符,`-s sep`用于指定分隔字符(默认是空格)。下面这是第四个版本,对应包路径为gopl.io/ch2/echo4。 gopl.io/ch2/echo4 ```Go @@ -82,11 +82,11 @@ func main() { } ``` -調用flag.Bool函數會創建一個新的對應布爾型標誌參數的變量。它有三個屬性:第一個是的命令行標誌參數的名字“n”,然後是該標誌參數的默認值(這里是false),最後是該標誌參數對應的描述信息。如果用戶在命令行輸入了一個無效的標誌參數,或者輸入`-h`或`-help`參數,那麽將打印所有標誌參數的名字、默認值和描述信息。類似的,調用flag.String函數將於創建一個對應字符串類型的標誌參數變量,同樣包含命令行標誌參數對應的參數名、默認值、和描述信息。程序中的`sep`和`n`變量分别是指向對應命令行標誌參數變量的指針,因此必須用`*sep`和`*n`形式的指針語法間接引用它們。 +调用flag.Bool函数会创建一个新的对应布尔型标志参数的变量。它有三个属性:第一个是的命令行标志参数的名字“n”,然后是该标志参数的默认值(这里是false),最后是该标志参数对应的描述信息。如果用户在命令行输入了一个无效的标志参数,或者输入`-h`或`-help`参数,那么将打印所有标志参数的名字、默认值和描述信息。类似的,调用flag.String函数将于创建一个对应字符串类型的标志参数变量,同样包含命令行标志参数对应的参数名、默认值、和描述信息。程序中的`sep`和`n`变量分别是指向对应命令行标志参数变量的指针,因此必须用`*sep`和`*n`形式的指针语法间接引用它们。 -當程序運行時,必須在使用標誌參數對應的變量之前調用先flag.Parse函數,用於更新每個標誌參數對應變量的值(之前是默認值)。對於非標誌參數的普通命令行參數可以通過調用flag.Args()函數來訪問,返迴值對應對應一個字符串類型的slice。如果在flag.Parse函數解析命令行參數時遇到錯誤,默認將打印相關的提示信息,然後調用os.Exit(2)終止程序。 +当程序运行时,必须在使用标志参数对应的变量之前调用先flag.Parse函数,用于更新每个标志参数对应变量的值(之前是默认值)。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问,返回值对应对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误,默认将打印相关的提示信息,然后调用os.Exit(2)终止程序。 -讓我們運行一些echo測試用例: +让我们运行一些echo测试用例: ``` $ go build gopl.io/ch2/echo4 diff --git a/ch2/ch2-03-3.md b/ch2/ch2-03-3.md index 87201cf7..42032e3e 100644 --- a/ch2/ch2-03-3.md +++ b/ch2/ch2-03-3.md @@ -1,17 +1,17 @@ -### 2.3.3. new函數 +### 2.3.3. new函数 -另一個創建變量的方法是調用用內建的new函數。表達式new(T)將創建一個T類型的匿名變量,初始化爲T類型的零值,然後返迴變量地址,返迴的指針類型爲`*T`。 +另一个创建变量的方法是调用用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为`*T`。 ```Go -p := new(int) // p, *int 類型, 指向匿名的 int 變量 +p := new(int) // p, *int 类型, 指向匿名的 int 变量 fmt.Println(*p) // "0" -*p = 2 // 設置 int 匿名變量的值爲 2 +*p = 2 // 设置 int 匿名变量的值为 2 fmt.Println(*p) // "2" ``` -用new創建變量和普通變量聲明語句方式創建變量沒有什麽區别,除了不需要聲明一個臨時變量的名字外,我們還可以在表達式中使用new(T)。換言之,new函數類似是一種語法糖,而不是一個新的基礎概念。 +用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要声明一个临时变量的名字外,我们还可以在表达式中使用new(T)。换言之,new函数类似是一种语法糖,而不是一个新的基础概念。 -下面的兩個newInt函數有着相同的行爲: +下面的两个newInt函数有着相同的行为: ```Go func newInt() *int { @@ -24,7 +24,7 @@ func newInt() *int { } ``` -每次調用new函數都是返迴一個新的變量的地址,因此下面兩個地址是不同的: +每次调用new函数都是返回一个新的变量的地址,因此下面两个地址是不同的: ```Go p := new(int) @@ -32,15 +32,15 @@ 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)。 +new函数使用常见相对比较少,因为对应结构体来说,可以直接用字面量语法创建新变量的方法会更灵活(§4.4.1)。 -由於new隻是一個預定義的函數,它併不是一個關鍵字,因此我們可以將new名字重新定義爲别的類型。例如下面的例子: +由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。例如下面的例子: ```Go func delta(old, new int) int { return new - old } ``` -由於new被定義爲int類型的變量名,因此在delta函數內部是無法使用內置的new函數的。 +由于new被定义为int类型的变量名,因此在delta函数内部是无法使用内置的new函数的。 diff --git a/ch2/ch2-03-4.md b/ch2/ch2-03-4.md index 1413cb29..b18da3a4 100644 --- a/ch2/ch2-03-4.md +++ b/ch2/ch2-03-4.md @@ -1,8 +1,8 @@ -### 2.3.4. 變量的生命週期 +### 2.3.4. 变量的生命周期 -變量的生命週期指的是在程序運行期間變量有效存在的時間間隔。對於在包一級聲明的變量來説,它們的生命週期和整個程序的運行週期是一致的。而相比之下,在局部變量的聲明週期則是動態的:從每次創建一個新變量的聲明語句開始,直到該變量不再被引用爲止,然後變量的存儲空間可能被迴收。函數的參數變量和返迴值變量都是局部變量。它們在函數每次被調用的時候創建。 +变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,在局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。 -例如,下面是從1.4節的Lissajous程序摘録的代碼片段: +例如,下面是从1.4节的Lissajous程序摘录的代码片段: ```Go for t := 0.0; t < cycles*2*math.Pi; t += res { @@ -13,7 +13,7 @@ for t := 0.0; t < cycles*2*math.Pi; t += res { } ``` -譯註:函數的有右小括弧也可以另起一行縮進,同時爲了防止編譯器在行尾自動插入分號而導致的編譯錯誤,可以在末尾的參數變量後面顯式插入逗號。像下面這樣: +译注:函数的有右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。像下面这样: ```Go for t := 0.0; t < cycles*2*math.Pi; t += res { @@ -21,18 +21,18 @@ for t := 0.0; t < cycles*2*math.Pi; t += res { y := math.Sin(t*freq + phase) img.SetColorIndex( size+int(x*size+0.5), size+int(y*size+0.5), - blackIndex, // 最後插入的逗號不會導致編譯錯誤,這是Go編譯器的一個特性 - ) // 小括弧另起一行縮進,和大括弧的風格保存一致 + blackIndex, // 最后插入的逗号不会导致编译错误,这是Go编译器的一个特性 + ) // 小括弧另起一行缩进,和大括弧的风格保存一致 } ``` -在每次循環的開始會創建臨時變量t,然後在每次循環迭代中創建臨時變量x和y。 +在每次循环的开始会创建临时变量t,然后在每次循环迭代中创建临时变量x和y。 -那麽垃Go語言的自動圾收集器是如何知道一個變量是何時可以被迴收的呢?這里我們可以避開完整的技術細節,基本的實現思路是,從每個包級的變量和每個當前運行函數的每一個局部變量開始,通過指針或引用的訪問路徑遍歷,是否可以找到該變量。如果不存在這樣的訪問路徑,那麽説明該變量是不可達的,也就是説它是否存在併不會影響程序後續的計算結果。 +那么垃Go语言的自动圾收集器是如何知道一个变量是何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。 -因爲一個變量的有效週期隻取決於是否可達,因此一個循環迭代內部的局部變量的生命週期可能超出其局部作用域。同時,局部變量可能在函數返迴之後依然存在。 +因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。 -編譯器會自動選擇在棧上還是在堆上分配局部變量的存儲空間,但可能令人驚訝的是,這個選擇併不是由用var還是new聲明變量的方式決定的。 +编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。 ```Go var global *int @@ -49,9 +49,9 @@ func g() { } ``` -f函數里的x變量必須在堆上分配,因爲它在函數退出後依然可以通過包一級的global變量找到,雖然它是在函數內部定義的;用Go語言的術語説,這個x局部變量從函數f中逃逸了。相反,當g函數返迴時,變量`*y`將是不可達的,也就是説可以馬上被迴收的。因此,`*y`併沒有從函數g中逃逸,編譯器可以選擇在棧上分配`*y`的存儲空間(譯註:也可以選擇在堆上分配,然後由Go語言的GC迴收這個變量的內存空間),雖然這里用的是new方式。其實在任何時候,你併不需爲了編寫正確的代碼而要考慮變量的逃逸行爲,要記住的是,逃逸的變量需要額外分配內存,同時對性能的優化可能會産生細微的影響。 +f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量`*y`将是不可达的,也就是说可以马上被回收的。因此,`*y`并没有从函数g中逃逸,编译器可以选择在栈上分配`*y`的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。 -Go語言的自動垃圾收集器對編寫正確的代碼是一個鉅大的幫助,但也併不是説你完全不用考慮內存了。你雖然不需要顯式地分配和釋放內存,但是要編寫高效的程序你依然需要了解變量的生命週期。例如,如果將指向短生命週期對象的指針保存到具有長生命週期的對象中,特别是保存到全局變量時,會阻止對短生命週期對象的垃圾迴收(從而可能影響程序的性能)。 +Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。 diff --git a/ch2/ch2-03.md b/ch2/ch2-03.md index 4b587c0e..7a288547 100644 --- a/ch2/ch2-03.md +++ b/ch2/ch2-03.md @@ -1,32 +1,32 @@ -## 2.3. 變量 +## 2.3. 变量 -var聲明語句可以創建一個特定類型的變量,然後給變量附加一個名字,併且設置變量的初始值。變量聲明的一般語法如下: +var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。变量声明的一般语法如下: ```Go -var 變量名字 類型 = 表達式 +var 变量名字 类型 = 表达式 ``` -其中“*類型*”或“*= 表達式*”兩個部分可以省略其中的一個。如果省略的是類型信息,那麽將根據初始化表達式來推導變量的類型信息。如果初始化表達式被省略,那麽將用零值初始化該變量。 數值類型變量對應的零值是0,布爾類型變量對應的零值是false,字符串類型對應的零值是空字符串,接口或引用類型(包括slice、map、chan和函數)變量對應的零值是nil。數組或結構體等聚合類型對應的零值是每個元素或字段都是對應該類型的零值。 +其中“*类型*”或“*= 表达式*”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。 -零值初始化機製可以確保每個聲明的變量總是有一個良好定義的值,因此在Go語言中不存在未初始化的變量。這個特性可以簡化很多代碼,而且可以在沒有增加額外工作的前提下確保邊界條件下的合理行爲。例如: +零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。这个特性可以简化很多代码,而且可以在没有增加额外工作的前提下确保边界条件下的合理行为。例如: ```Go var s string fmt.Println(s) // "" ``` -這段代碼將打印一個空字符串,而不是導致錯誤或産生不可預知的行爲。Go語言程序員應該讓一些聚合類型的零值也具有意義,這樣可以保證不管任何類型的變量總是有一個合理有效的零值狀態。 +这段代码将打印一个空字符串,而不是导致错误或产生不可预知的行为。Go语言程序员应该让一些聚合类型的零值也具有意义,这样可以保证不管任何类型的变量总是有一个合理有效的零值状态。 -也可以在一個聲明語句中同時聲明一組變量,或用一組初始化表達式聲明併初始化一組變量。如果省略每個變量的類型,將可以聲明多個類型不同的變量(類型由初始化表達式推導): +也可以在一个声明语句中同时声明一组变量,或用一组初始化表达式声明并初始化一组变量。如果省略每个变量的类型,将可以声明多个类型不同的变量(类型由初始化表达式推导): ```Go var i, j, k int // int, int, int var b, f, s = true, 2.3, "four" // bool, float64, string ``` -初始化表達式可以是字面量或任意的表達式。在包級别聲明的變量會在main入口函數執行前完成初始化(§2.6.2),局部變量將在聲明語句被執行到的時候完成初始化。 +初始化表达式可以是字面量或任意的表达式。在包级别声明的变量会在main入口函数执行前完成初始化(§2.6.2),局部变量将在声明语句被执行到的时候完成初始化。 -一組變量也可以通過調用一個函數,由函數返迴的多個返迴值初始化: +一组变量也可以通过调用一个函数,由函数返回的多个返回值初始化: ```Go var f, err = os.Open(name) // os.Open returns a file and an error diff --git a/ch2/ch2-04-1.md b/ch2/ch2-04-1.md index 458f437d..ed81ade2 100644 --- a/ch2/ch2-04-1.md +++ b/ch2/ch2-04-1.md @@ -1,6 +1,6 @@ -### 2.4.1. 元組賦值 +### 2.4.1. 元组赋值 -元組賦值是另一種形式的賦值語句,它允許同時更新多個變量的值。在賦值之前,賦值語句右邊的所有表達式將會先進行求值,然後再統一更新左邊對應變量的值。這對於處理有些同時出現在元組賦值語句左右兩邊的變量很有幫助,例如我們可以這樣交換兩個變量的值: +元组赋值是另一种形式的赋值语句,它允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。这对于处理有些同时出现在元组赋值语句左右两边的变量很有帮助,例如我们可以这样交换两个变量的值: ```Go x, y = y, x @@ -8,7 +8,7 @@ x, y = y, x a[i], a[j] = a[j], a[i] ``` -或者是計算兩個整數值的的最大公約數(GCD)(譯註:GCD不是那個敏感字,而是greatest common divisor的縮寫,歐幾里德的GCD是最早的非平凡算法): +或者是计算两个整数值的的最大公约数(GCD)(译注:GCD不是那个敏感字,而是greatest common divisor的缩写,欧几里德的GCD是最早的非平凡算法): ```Go func gcd(x, y int) int { @@ -19,7 +19,7 @@ func gcd(x, y int) int { } ``` -或者是計算斐波納契數列(Fibonacci)的第N個數: +或者是计算斐波纳契数列(Fibonacci)的第N个数: ```Go func fib(n int) int { @@ -31,21 +31,21 @@ func fib(n int) int { } ``` -元組賦值也可以使一繫列瑣碎賦值更加緊湊(譯註: 特别是在for循環的初始化部分), +元组赋值也可以使一系列琐碎赋值更加紧凑(译注: 特别是在for循环的初始化部分), ```Go i, j, k = 2, 3, 5 ``` -但如果表達式太複雜的話,應該盡量避免過度使用元組賦值;因爲每個變量單獨賦值語句的寫法可讀性會更好。 +但如果表达式太复杂的话,应该尽量避免过度使用元组赋值;因为每个变量单独赋值语句的写法可读性会更好。 -有些表達式會産生多個值,比如調用一個有多個返迴值的函數。當這樣一個函數調用出現在元組賦值右邊的表達式中時(譯註:右邊不能再有其它表達式),左邊變量的數目必須和右邊一致。 +有些表达式会产生多个值,比如调用一个有多个返回值的函数。当这样一个函数调用出现在元组赋值右边的表达式中时(译注:右边不能再有其它表达式),左边变量的数目必须和右边一致。 ```Go f, err = os.Open("foo.txt") // function call returns two values ``` -通常,這類函數會用額外的返迴值來表達某種錯誤類型,例如os.Open是用額外的返迴值返迴一個error類型的錯誤,還有一些是用來返迴布爾值,通常被稱爲ok。在稍後我們將看到的三個操作都是類似的用法。如果map査找(§4.3)、類型斷言(§7.10)或通道接收(§8.4.2)出現在賦值語句的右邊,它們都可能會産生兩個結果,有一個額外的布爾結果表示操作是否成功: +通常,这类函数会用额外的返回值来表达某种错误类型,例如os.Open是用额外的返回值返回一个error类型的错误,还有一些是用来返回布尔值,通常被称为ok。在稍后我们将看到的三个操作都是类似的用法。如果map查找(§4.3)、类型断言(§7.10)或通道接收(§8.4.2)出现在赋值语句的右边,它们都可能会产生两个结果,有一个额外的布尔结果表示操作是否成功: ```Go v, ok = m[key] // map lookup @@ -53,22 +53,22 @@ v, ok = x.(T) // type assertion v, ok = <-ch // channel receive ``` -譯註:map査找(§4.3)、類型斷言(§7.10)或通道接收(§8.4.2)出現在賦值語句的右邊時,併不一定是産生兩個結果,也可能隻産生一個結果。對於值産生一個結果的情形,map査找失敗時會返迴零值,類型斷言失敗時會發送運行時panic異常,通道接收失敗時會返迴零值(阻塞不算是失敗)。例如下面的例子: +译注:map查找(§4.3)、类型断言(§7.10)或通道接收(§8.4.2)出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于值产生一个结果的情形,map查找失败时会返回零值,类型断言失败时会发送运行时panic异常,通道接收失败时会返回零值(阻塞不算是失败)。例如下面的例子: ```Go -v = m[key] // map査找,失敗時返迴零值 -v = x.(T) // type斷言,失敗時panic異常 -v = <-ch // 管道接收,失敗時返迴零值(阻塞不算是失敗) +v = m[key] // map查找,失败时返回零值 +v = x.(T) // type断言,失败时panic异常 +v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败) -_, ok = m[key] // map返迴2個值 -_, ok = mm[""], false // map返迴1個值 -_ = mm[""] // map返迴1個值 +_, ok = m[key] // map返回2个值 +_, ok = mm[""], false // map返回1个值 +_ = mm[""] // map返回1个值 ``` -和變量聲明一樣,我們可以用下劃線空白標識符`_`來丟棄不需要的值。 +和变量声明一样,我们可以用下划线空白标识符`_`来丢弃不需要的值。 ```Go -_, err = io.Copy(dst, src) // 丟棄字節數 -_, ok = x.(T) // 隻檢測類型,忽略具體值 +_, err = io.Copy(dst, src) // 丢弃字节数 +_, ok = x.(T) // 只检测类型,忽略具体值 ``` diff --git a/ch2/ch2-04-2.md b/ch2/ch2-04-2.md index cbbd0524..a7f9fdb5 100644 --- a/ch2/ch2-04-2.md +++ b/ch2/ch2-04-2.md @@ -1,12 +1,12 @@ -### 2.4.2. 可賦值性 +### 2.4.2. 可赋值性 -賦值語句是顯式的賦值形式,但是程序中還有很多地方會發生隱式的賦值行爲:函數調用會隱式地將調用參數的值賦值給函數的參數變量,一個返迴語句將隱式地將返迴操作的值賦值給結果變量,一個複合類型的字面量(§4.2)也會産生賦值行爲。例如下面的語句: +赋值语句是显式的赋值形式,但是程序中还有很多地方会发生隐式的赋值行为:函数调用会隐式地将调用参数的值赋值给函数的参数变量,一个返回语句将隐式地将返回操作的值赋值给结果变量,一个复合类型的字面量(§4.2)也会产生赋值行为。例如下面的语句: ```Go medals := []string{"gold", "silver", "bronze"} ``` -隱式地對slice的每個元素進行賦值操作,類似這樣寫的行爲: +隐式地对slice的每个元素进行赋值操作,类似这样写的行为: ```Go medals[0] = "gold" @@ -14,12 +14,12 @@ medals[1] = "silver" medals[2] = "bronze" ``` -map和chan的元素,雖然不是普通的變量,但是也有類似的隱式賦值行爲。 +map和chan的元素,虽然不是普通的变量,但是也有类似的隐式赋值行为。 -不管是隱式還是顯式地賦值,在賦值語句左邊的變量和右邊最終的求到的值必須有相同的數據類型。更直白地説,隻有右邊的值對於左邊的變量是可賦值的,賦值語句才是允許的。 +不管是隐式还是显式地赋值,在赋值语句左边的变量和右边最终的求到的值必须有相同的数据类型。更直白地说,只有右边的值对于左边的变量是可赋值的,赋值语句才是允许的。 -可賦值性的規則對於不同類型有着不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:類型必須完全匹配,nil可以賦值給任何指針或引用類型的變量。常量(§3.6)則有更靈活的賦值規則,因爲這樣可以避免不必要的顯式的類型轉換。 +可赋值性的规则对于不同类型有着不同要求,对每个新类型特殊的地方我们会专门解释。对于目前我们已经讨论过的类型,它的规则是简单的:类型必须完全匹配,nil可以赋值给任何指针或引用类型的变量。常量(§3.6)则有更灵活的赋值规则,因为这样可以避免不必要的显式的类型转换。 -對於兩個值是否可以用`==`或`!=`進行相等比較的能力也和可賦值能力有關繫:對於任何類型的值的相等比較,第二個值必須是對第一個值類型對應的變量是可賦值的,反之依然。和前面一樣,我們會對每個新類型比較特殊的地方做專門的解釋。 +对于两个值是否可以用`==`或`!=`进行相等比较的能力也和可赋值能力有关系:对于任何类型的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之依然。和前面一样,我们会对每个新类型比较特殊的地方做专门的解释。 diff --git a/ch2/ch2-04.md b/ch2/ch2-04.md index d316751f..eb5c7176 100644 --- a/ch2/ch2-04.md +++ b/ch2/ch2-04.md @@ -1,28 +1,28 @@ -## 2.4. 賦值 +## 2.4. 赋值 -使用賦值語句可以更新一個變量的值,最簡單的賦值語句是將要被賦值的變量放在=的左邊,新值的表達式放在=的右邊。 +使用赋值语句可以更新一个变量的值,最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达式放在=的右边。 ```Go -x = 1 // 命名變量的賦值 -*p = true // 通過指針間接賦值 -person.name = "bob" // 結構體字段賦值 -count[x] = count[x] * scale // 數組、slice或map的元素賦值 +x = 1 // 命名变量的赋值 +*p = true // 通过指针间接赋值 +person.name = "bob" // 结构体字段赋值 +count[x] = count[x] * scale // 数组、slice或map的元素赋值 ``` -特定的二元算術運算符和賦值語句的複合操作有一個簡潔形式,例如上面最後的語句可以重寫爲: +特定的二元算术运算符和赋值语句的复合操作有一个简洁形式,例如上面最后的语句可以重写为: ```Go count[x] *= scale ``` -這樣可以省去對變量表達式的重複計算。 +这样可以省去对变量表达式的重复计算。 -數值變量也可以支持`++`遞增和`--`遞減語句(譯註:自增和自減是語句,而不是表達式,因此`x = i++`之類的表達式是錯誤的): +数值变量也可以支持`++`递增和`--`递减语句(译注:自增和自减是语句,而不是表达式,因此`x = i++`之类的表达式是错误的): ```Go v := 1 -v++ // 等價方式 v = v + 1;v 變成 2 -v-- // 等價方式 v = v - 1;v 變成 1 +v++ // 等价方式 v = v + 1;v 变成 2 +v-- // 等价方式 v = v - 1;v 变成 1 ``` {% include "./ch2-04-1.md" %} diff --git a/ch2/ch2-05.md b/ch2/ch2-05.md index 1a82cde6..dca84c4d 100644 --- a/ch2/ch2-05.md +++ b/ch2/ch2-05.md @@ -1,24 +1,24 @@ -## 2.5. 類型 +## 2.5. 类型 -變量或表達式的類型定義了對應存儲值的屬性特徵,例如數值在內存的存儲大小(或者是元素的bit個數),它們在內部是如何表達的,是否支持一些操作符,以及它們自己關聯的方法集等。 +变量或表达式的类型定义了对应存储值的属性特征,例如数值在内存的存储大小(或者是元素的bit个数),它们在内部是如何表达的,是否支持一些操作符,以及它们自己关联的方法集等。 -在任何程序中都會存在一些變量有着相同的內部結構,但是卻表示完全不同的概念。例如,一個int類型的變量可以用來表示一個循環的迭代索引、或者一個時間戳、或者一個文件描述符、或者一個月份;一個float64類型的變量可以用來表示每秒移動幾米的速度、或者是不同溫度單位下的溫度;一個字符串可以用來表示一個密碼或者一個顔色的名稱。 +在任何程序中都会存在一些变量有着相同的内部结构,但是却表示完全不同的概念。例如,一个int类型的变量可以用来表示一个循环的迭代索引、或者一个时间戳、或者一个文件描述符、或者一个月份;一个float64类型的变量可以用来表示每秒移动几米的速度、或者是不同温度单位下的温度;一个字符串可以用来表示一个密码或者一个颜色的名称。 -一個類型聲明語句創建了一個新的類型名稱,和現有類型具有相同的底層結構。新命名的類型提供了一個方法,用來分隔不同概念的類型,這樣卽使它們底層類型相同也是不兼容的。 +一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。新命名的类型提供了一个方法,用来分隔不同概念的类型,这样即使它们底层类型相同也是不兼容的。 ```Go -type 類型名字 底層類型 +type 类型名字 底层类型 ``` -類型聲明語句一般出現在包一級,因此如果新創建的類型名字的首字符大寫,則在外部包也可以使用。 +类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部包也可以使用。 -譯註:對於中文漢字,Unicode標誌都作爲小寫字母處理,因此中文的命名默認不能導出;不過国內的用戶針對該問題提出了不同的看法,根據RobPike的迴複,在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複: +译注:对于中文汉字,Unicode标志都作为小写字母处理,因此中文的命名默认不能导出;不过国内的用户针对该问题提出了不同的看法,根据RobPike的回复,在Go2中有可能会将中日韩等字符当作大写字母处理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的回复: > A solution that's been kicking around for a while: > -> For Go 2 (can't do it before then): Change the definition to “lower case letters and _ are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本語 for an exported name and _日本語 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same. +> For Go 2 (can't do it before then): Change the definition to “lower case letters and _ are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本语 for an exported name and _日本语 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same. -爲了説明類型聲明,我們將不同溫度單位分别定義爲不同的類型: +为了说明类型声明,我们将不同温度单位分别定义为不同的类型: gopl.io/ch2/tempconv0 ```Go @@ -27,13 +27,13 @@ package tempconv import "fmt" -type Celsius float64 // 攝氏溫度 -type Fahrenheit float64 // 華氏溫度 +type Celsius float64 // 摄氏温度 +type Fahrenheit float64 // 华氏温度 const ( - AbsoluteZeroC Celsius = -273.15 // 絶對零度 - FreezingC Celsius = 0 // 結冰點溫度 - BoilingC Celsius = 100 // 沸水溫度 + AbsoluteZeroC Celsius = -273.15 // 绝对零度 + FreezingC Celsius = 0 // 结冰点温度 + BoilingC Celsius = 100 // 沸水温度 ) func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } @@ -41,13 +41,13 @@ func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) } ``` -我們在這個包聲明了兩種類型:Celsius和Fahrenheit分别對應不同的溫度單位。它們雖然有着相同的底層類型float64,但是它們是不同的數據類型,因此它們不可以被相互比較或混在一個表達式運算。刻意區分類型,可以避免一些像無意中使用不同單位的溫度混合計算導致的錯誤;因此需要一個類似Celsius(t)或Fahrenheit(t)形式的顯式轉型操作才能將float64轉爲對應的類型。Celsius(t)和Fahrenheit(t)是類型轉換操作,它們併不是函數調用。類型轉換不會改變值本身,但是會使它們的語義發生變化。另一方面,CToF和FToC兩個函數則是對不同溫度單位下的溫度進行換算,它們會返迴不同的值。 +我们在这个包声明了两种类型:Celsius和Fahrenheit分别对应不同的温度单位。它们虽然有着相同的底层类型float64,但是它们是不同的数据类型,因此它们不可以被相互比较或混在一个表达式运算。刻意区分类型,可以避免一些像无意中使用不同单位的温度混合计算导致的错误;因此需要一个类似Celsius(t)或Fahrenheit(t)形式的显式转型操作才能将float64转为对应的类型。Celsius(t)和Fahrenheit(t)是类型转换操作,它们并不是函数调用。类型转换不会改变值本身,但是会使它们的语义发生变化。另一方面,CToF和FToC两个函数则是对不同温度单位下的温度进行换算,它们会返回不同的值。 -對於每一個類型T,都有一個對應的類型轉換操作T(x),用於將x轉爲T類型(譯註:如果T是指針類型,可能會需要用小括弧包裝T,比如`(*int)(0)`)。隻有當兩個類型的底層基礎類型相同時,才允許這種轉型操作,或者是兩者都是指向相同底層結構的指針類型,這些轉換隻改變類型而不會影響值本身。如果x是可以賦值給T類型的值,那麽x必然也可以被轉爲T類型,但是一般沒有這個必要。 +对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型(译注:如果T是指针类型,可能会需要用小括弧包装T,比如`(*int)(0)`)。只有当两个类型的底层基础类型相同时,才允许这种转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。如果x是可以赋值给T类型的值,那么x必然也可以被转为T类型,但是一般没有这个必要。 -數值類型之間的轉型也是允許的,併且在字符串和一些特定類型的slice之間也是可以轉換的,在下一章我們會看到這樣的例子。這類轉換可能改變值的表現。例如,將一個浮點數轉爲整數將丟棄小數部分,將一個字符串轉爲`[]byte`類型的slice將拷貝一個字符串數據的副本。在任何情況下,運行時不會發生轉換失敗的錯誤(譯註: 錯誤隻會發生在編譯階段)。 +数值类型之间的转型也是允许的,并且在字符串和一些特定类型的slice之间也是可以转换的,在下一章我们会看到这样的例子。这类转换可能改变值的表现。例如,将一个浮点数转为整数将丢弃小数部分,将一个字符串转为`[]byte`类型的slice将拷贝一个字符串数据的副本。在任何情况下,运行时不会发生转换失败的错误(译注: 错误只会发生在编译阶段)。 -底層數據類型決定了內部結構和表達方式,也決定是否可以像底層類型一樣對內置運算符的支持。這意味着,Celsius和Fahrenheit類型的算術運算行爲和底層的float64類型是一樣的,正如我們所期望的那樣。 +底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持。这意味着,Celsius和Fahrenheit类型的算术运算行为和底层的float64类型是一样的,正如我们所期望的那样。 ```Go fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C @@ -56,7 +56,7 @@ fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch ``` -比較運算符`==`和`<`也可以用來比較一個命名類型的變量和另一個有相同類型的變量,或有着相同底層類型的未命名類型的值之間做比較。但是如果兩個值有着不同的類型,則不能直接進行比較: +比较运算符`==`和`<`也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间做比较。但是如果两个值有着不同的类型,则不能直接进行比较: ```Go var c Celsius @@ -67,19 +67,19 @@ fmt.Println(c == f) // compile error: type mismatch fmt.Println(c == Celsius(f)) // "true"! ``` -註意最後那個語句。盡管看起來想函數調用,但是Celsius(f)是類型轉換操作,它併不會改變值,僅僅是改變值的類型而已。測試爲眞的原因是因爲c和g都是零值。 +注意最后那个语句。尽管看起来想函数调用,但是Celsius(f)是类型转换操作,它并不会改变值,仅仅是改变值的类型而已。测试为真的原因是因为c和g都是零值。 -一個命名的類型可以提供書寫方便,特别是可以避免一遍又一遍地書寫複雜類型(譯註:例如用匿名的結構體定義變量)。雖然對於像float64這種簡單的底層類型沒有簡潔很多,但是如果是複雜的類型將會簡潔很多,特别是我們卽將討論的結構體類型。 +一个命名的类型可以提供书写方便,特别是可以避免一遍又一遍地书写复杂类型(译注:例如用匿名的结构体定义变量)。虽然对于像float64这种简单的底层类型没有简洁很多,但是如果是复杂的类型将会简洁很多,特别是我们即将讨论的结构体类型。 -命名類型還可以爲該類型的值定義新的行爲。這些行爲表示爲一組關聯到該類型的函數集合,我們稱爲類型的方法集。我們將在第六章中討論方法的細節,這里值説寫簡單用法。 +命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。我们将在第六章中讨论方法的细节,这里值说写简单用法。 -下面的聲明語句,Celsius類型的參數c出現在了函數名的前面,表示聲明的是Celsius類型的一個叫名叫String的方法,該方法返迴該類型對象c帶着°C溫度單位的字符串: +下面的声明语句,Celsius类型的参数c出现在了函数名的前面,表示声明的是Celsius类型的一个叫名叫String的方法,该方法返回该类型对象c带着°C温度单位的字符串: ```Go func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } ``` -許多類型都會定義一個String方法,因爲當使用fmt包的打印方法時,將會優先使用該類型對應的String方法返迴的結果打印,我們將在7.1節講述。 +许多类型都会定义一个String方法,因为当使用fmt包的打印方法时,将会优先使用该类型对应的String方法返回的结果打印,我们将在7.1节讲述。 ```Go c := FToC(212.0) diff --git a/ch2/ch2-06-1.md b/ch2/ch2-06-1.md index 377d1022..da6f0209 100644 --- a/ch2/ch2-06-1.md +++ b/ch2/ch2-06-1.md @@ -1,10 +1,10 @@ -### 2.6.1. 導入包 +### 2.6.1. 导入包 -在Go語言程序中,每個包都是有一個全局唯一的導入路徑。導入語句中類似"gopl.io/ch2/tempconv"的字符串對應包的導入路徑。Go語言的規范併沒有定義這些字符串的具體含義或包來自哪里,它們是由構建工具來解釋的。當使用Go語言自帶的go工具箱時(第十章),一個導入路徑代表一個目録中的一個或多個Go源文件。 +在Go语言程序中,每个包都是有一个全局唯一的导入路径。导入语句中类似"gopl.io/ch2/tempconv"的字符串对应包的导入路径。Go语言的规范并没有定义这些字符串的具体含义或包来自哪里,它们是由构建工具来解释的。当使用Go语言自带的go工具箱时(第十章),一个导入路径代表一个目录中的一个或多个Go源文件。 -除了包的導入路徑,每個包還有一個包名,包名一般是短小的名字(併不要求包名是唯一的),包名在包的聲明處指定。按照慣例,一個包的名字和包的導入路徑的最後一個字段相同,例如gopl.io/ch2/tempconv包的名字一般是tempconv。 +除了包的导入路径,每个包还有一个包名,包名一般是短小的名字(并不要求包名是唯一的),包名在包的声明处指定。按照惯例,一个包的名字和包的导入路径的最后一个字段相同,例如gopl.io/ch2/tempconv包的名字一般是tempconv。 -要使用gopl.io/ch2/tempconv包,需要先導入: +要使用gopl.io/ch2/tempconv包,需要先导入: gopl.io/ch2/cf ```Go @@ -34,9 +34,9 @@ func main() { } ``` -導入語句將導入的包綁定到一個短小的名字,然後通過該短小的名字就可以引用包中導出的全部內容。上面的導入聲明將允許我們以tempconv.CToF的形式來訪問gopl.io/ch2/tempconv包中的內容。在默認情況下,導入的包綁定到tempconv名字(譯註:這包聲明語句指定的名字),但是我們也可以綁定到另一個名稱,以避免名字衝突(§10.4)。 +导入语句将导入的包绑定到一个短小的名字,然后通过该短小的名字就可以引用包中导出的全部内容。上面的导入声明将允许我们以tempconv.CToF的形式来访问gopl.io/ch2/tempconv包中的内容。在默认情况下,导入的包绑定到tempconv名字(译注:这包声明语句指定的名字),但是我们也可以绑定到另一个名称,以避免名字冲突(§10.4)。 -cf程序將命令行輸入的一個溫度在Celsius和Fahrenheit溫度單位之間轉換: +cf程序将命令行输入的一个温度在Celsius和Fahrenheit温度单位之间转换: ``` $ go build gopl.io/ch2/cf @@ -48,8 +48,8 @@ $ ./cf -40 -40°F = -40°C, -40°C = -40°F ``` -如果導入了一個包,但是又沒有使用該包將被當作一個編譯錯誤處理。這種強製規則可以有效減少不必要的依賴,雖然在調試期間可能會讓人討厭,因爲刪除一個類似log.Print("got here!")的打印語句可能導致需要同時刪除log包導入聲明,否則,編譯器將會發出一個錯誤。在這種情況下,我們需要將不必要的導入刪除或註釋掉。 +如果导入了一个包,但是又没有使用该包将被当作一个编译错误处理。这种强制规则可以有效减少不必要的依赖,虽然在调试期间可能会让人讨厌,因为删除一个类似log.Print("got here!")的打印语句可能导致需要同时删除log包导入声明,否则,编译器将会发出一个错误。在这种情况下,我们需要将不必要的导入删除或注释掉。 -不過有更好的解決方案,我們可以使用golang.org/x/tools/cmd/goimports導入工具,它可以根據需要自動添加或刪除導入的包;許多編輯器都可以集成goimports工具,然後在保存文件的時候自動運行。類似的還有gofmt工具,可以用來格式化Go源文件。 +不过有更好的解决方案,我们可以使用golang.org/x/tools/cmd/goimports导入工具,它可以根据需要自动添加或删除导入的包;许多编辑器都可以集成goimports工具,然后在保存文件的时候自动运行。类似的还有gofmt工具,可以用来格式化Go源文件。 -**練習 2.2:** 寫一個通用的單位轉換程序,用類似cf程序的方式從命令行讀取參數,如果缺省的話則是從標準輸入讀取參數,然後做類似Celsius和Fahrenheit的單位轉換,長度單位可以對應英尺和米,重量單位可以對應磅和公斤等。 +**练习 2.2:** 写一个通用的单位转换程序,用类似cf程序的方式从命令行读取参数,如果缺省的话则是从标准输入读取参数,然后做类似Celsius和Fahrenheit的单位转换,长度单位可以对应英尺和米,重量单位可以对应磅和公斤等。 diff --git a/ch2/ch2-06-2.md b/ch2/ch2-06-2.md index 84689d72..b676c4df 100644 --- a/ch2/ch2-06-2.md +++ b/ch2/ch2-06-2.md @@ -1,28 +1,28 @@ ### 2.6.2. 包的初始化 -包的初始化首先是解決包級變量的依賴順序,然後安照包級變量聲明出現的順序依次初始化: +包的初始化首先是解决包级变量的依赖顺序,然后安照包级变量声明出现的顺序依次初始化: ```Go -var a = b + c // a 第三個初始化, 爲 3 -var b = f() // b 第二個初始化, 爲 2, 通過調用 f (依賴c) -var c = 1 // c 第一個初始化, 爲 1 +var a = b + c // a 第三个初始化, 为 3 +var b = f() // b 第二个初始化, 为 2, 通过调用 f (依赖c) +var c = 1 // c 第一个初始化, 为 1 func f() int { return c + 1 } ``` -如果包中含有多個.go源文件,它們將按照發給編譯器的順序進行初始化,Go語言的構建工具首先會將.go文件根據文件名排序,然後依次調用編譯器編譯。 +如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译。 -對於在包級别聲明的變量,如果有初始化表達式則用表達式初始化,還有一些沒有初始化表達式的,例如某些表格數據初始化併不是一個簡單的賦值過程。在這種情況下,我們可以用一個特殊的init初始化函數來簡化初始化工作。每個文件都可以包含多個init初始化函數 +对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,还有一些没有初始化表达式的,例如某些表格数据初始化并不是一个简单的赋值过程。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数 ```Go func init() { /* ... */ } ``` -這樣的init初始化函數除了不能被調用或引用外,其他行爲和普通函數類似。在每個文件中的init初始化函數,在程序開始執行時按照它們聲明的順序被自動調用。 +这样的init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。 -每個包在解決依賴的前提下,以導入聲明的順序初始化,每個包隻會被初始化一次。因此,如果一個p包導入了q包,那麽在p包初始化的時候可以認爲q包必然已經初始化過了。初始化工作是自下而上進行的,main包最後被初始化。以這種方式,可以確保在main函數執行之前,所有依然的包都已經完成初始化工作了。 +每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依然的包都已经完成初始化工作了。 -下面的代碼定義了一個PopCount函數,用於返迴一個數字中含二進製1bit的個數。它使用init初始化函數來生成輔助表格pc,pc表格用於處理每個8bit寬度的數字含二進製的1bit的bit個數,這樣的話在處理64bit寬度的數字時就沒有必要循環64次,隻需要8次査表就可以了。(這併不是最快的統計1bit數目的算法,但是它可以方便演示init函數的用法,併且演示了如果預生成輔助表格,這是編程中常用的技術)。 +下面的代码定义了一个PopCount函数,用于返回一个数字中含二进制1bit的个数。它使用init初始化函数来生成辅助表格pc,pc表格用于处理每个8bit宽度的数字含二进制的1bit的bit个数,这样的话在处理64bit宽度的数字时就没有必要循环64次,只需要8次查表就可以了。(这并不是最快的统计1bit数目的算法,但是它可以方便演示init函数的用法,并且演示了如果预生成辅助表格,这是编程中常用的技术)。 gopl.io/ch2/popcount ```Go @@ -50,7 +50,7 @@ func PopCount(x uint64) int { } ``` -譯註:對於pc這類需要複雜處理的初始化,可以通過將初始化邏輯包裝爲一個匿名函數處理,像下面這樣: +译注:对于pc这类需要复杂处理的初始化,可以通过将初始化逻辑包装为一个匿名函数处理,像下面这样: ```Go // pc[i] is the population count of i. @@ -62,16 +62,16 @@ var pc [256]byte = func() (pc [256]byte) { }() ``` -要註意的是在init函數中,range循環隻使用了索引,省略了沒有用到的值部分。循環也可以這樣寫: +要注意的是在init函数中,range循环只使用了索引,省略了没有用到的值部分。循环也可以这样写: ```Go for i, _ := range pc { ``` -我們在下一節和10.5節還將看到其它使用init函數的地方。 +我们在下一节和10.5节还将看到其它使用init函数的地方。 -**練習 2.3:** 重寫PopCount函數,用一個循環代替單一的表達式。比較兩個版本的性能。(11.4節將展示如何繫統地比較兩個不同實現的性能。) +**练习 2.3:** 重写PopCount函数,用一个循环代替单一的表达式。比较两个版本的性能。(11.4节将展示如何系统地比较两个不同实现的性能。) -**練習 2.4:** 用移位算法重寫PopCount函數,每次測試最右邊的1bit,然後統計總數。比較和査表算法的性能差異。 +**练习 2.4:** 用移位算法重写PopCount函数,每次测试最右边的1bit,然后统计总数。比较和查表算法的性能差异。 -**練習 2.5:** 表達式`x&(x-1)`用於將x的最低的一個非零的bit位清零。使用這個算法重寫PopCount函數,然後比較性能。 +**练习 2.5:** 表达式`x&(x-1)`用于将x的最低的一个非零的bit位清零。使用这个算法重写PopCount函数,然后比较性能。 diff --git a/ch2/ch2-06.md b/ch2/ch2-06.md index f8fc1c20..6a75e8f4 100644 --- a/ch2/ch2-06.md +++ b/ch2/ch2-06.md @@ -1,16 +1,16 @@ ## 2.6. 包和文件 -Go語言中的包和其他語言的庫或模塊的概念類似,目的都是爲了支持模塊化、封裝、單獨編譯和代碼重用。一個包的源代碼保存在一個或多個以.go爲文件後綴名的源文件中,通常一個包所在目録路徑的後綴是包的導入路徑;例如包gopl.io/ch1/helloworld對應的目録路徑是$GOPATH/src/gopl.io/ch1/helloworld。 +Go语言中的包和其他语言的库或模块的概念类似,目的都是为了支持模块化、封装、单独编译和代码重用。一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中,通常一个包所在目录路径的后缀是包的导入路径;例如包gopl.io/ch1/helloworld对应的目录路径是$GOPATH/src/gopl.io/ch1/helloworld。 -每個包都對應一個獨立的名字空間。例如,在image包中的Decode函數和在unicode/utf16包中的 Decode函數是不同的。要在外部引用該函數,必須顯式使用image.Decode或utf16.Decode形式訪問。 +每个包都对应一个独立的名字空间。例如,在image包中的Decode函数和在unicode/utf16包中的 Decode函数是不同的。要在外部引用该函数,必须显式使用image.Decode或utf16.Decode形式访问。 -包還可以讓我們通過控製哪些名字是外部可見的來隱藏內部實現信息。在Go語言中,一個簡單的規則是:如果一個名字是大寫字母開頭的,那麽該名字是導出的(譯註:因爲漢字不區分大小寫,因此漢字開頭的名字是沒有導出的)。 +包还可以让我们通过控制哪些名字是外部可见的来隐藏内部实现信息。在Go语言中,一个简单的规则是:如果一个名字是大写字母开头的,那么该名字是导出的(译注:因为汉字不区分大小写,因此汉字开头的名字是没有导出的)。 -爲了演示包基本的用法,先假設我們的溫度轉換軟件已經很流行,我們希望到Go語言社區也能使用這個包。我們該如何做呢? +为了演示包基本的用法,先假设我们的温度转换软件已经很流行,我们希望到Go语言社区也能使用这个包。我们该如何做呢? -讓我們創建一個名爲gopl.io/ch2/tempconv的包,這是前面例子的一個改進版本。(我們約定我們的例子都是以章節順序來編號的,這樣的路徑更容易閲讀)包代碼存儲在兩個源文件中,用來演示如何在一個源文件聲明然後在其他的源文件訪問;雖然在現實中,這樣小的包一般隻需要一個文件。 +让我们创建一个名为gopl.io/ch2/tempconv的包,这是前面例子的一个改进版本。(我们约定我们的例子都是以章节顺序来编号的,这样的路径更容易阅读)包代码存储在两个源文件中,用来演示如何在一个源文件声明然后在其他的源文件访问;虽然在现实中,这样小的包一般只需要一个文件。 -我們把變量的聲明、對應的常量,還有方法都放到tempconv.go源文件中: +我们把变量的声明、对应的常量,还有方法都放到tempconv.go源文件中: gopl.io/ch2/tempconv ```Go @@ -32,7 +32,7 @@ func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) } ``` -轉換函數則放在另一個conv.go源文件中: +转换函数则放在另一个conv.go源文件中: ```Go package tempconv @@ -44,23 +44,23 @@ func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) } ``` -每個源文件都是以包的聲明語句開始,用來指名包的名字。當包被導入的時候,包內的成員將通過類似tempconv.CToF的形式訪問。而包級别的名字,例如在一個文件聲明的類型和常量,在同一個包的其他源文件也是可以直接訪問的,就好像所有代碼都在一個文件一樣。要註意的是tempconv.go源文件導入了fmt包,但是conv.go源文件併沒有,因爲這個源文件中的代碼併沒有用到fmt包。 +每个源文件都是以包的声明语句开始,用来指名包的名字。当包被导入的时候,包内的成员将通过类似tempconv.CToF的形式访问。而包级别的名字,例如在一个文件声明的类型和常量,在同一个包的其他源文件也是可以直接访问的,就好像所有代码都在一个文件一样。要注意的是tempconv.go源文件导入了fmt包,但是conv.go源文件并没有,因为这个源文件中的代码并没有用到fmt包。 -因爲包級别的常量名都是以大寫字母開頭,它們可以像tempconv.AbsoluteZeroC這樣被外部代碼訪問: +因为包级别的常量名都是以大写字母开头,它们可以像tempconv.AbsoluteZeroC这样被外部代码访问: ```Go fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C" ``` -要將攝氏溫度轉換爲華氏溫度,需要先用import語句導入gopl.io/ch2/tempconv包,然後就可以使用下面的代碼進行轉換了: +要将摄氏温度转换为华氏温度,需要先用import语句导入gopl.io/ch2/tempconv包,然后就可以使用下面的代码进行转换了: ```Go fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F" ``` -在每個源文件的包聲明前僅跟着的註釋是包註釋(§10.7.4)。通常,包註釋的第一句應該先是包的功能概要説明。一個包通常隻有一個源文件有包註釋(譯註:如果有多個包註釋,目前的文檔工具會根據源文件名的先後順序將它們鏈接爲一個包註釋)。如果包註釋很大,通常會放到一個獨立的doc.go文件中。 +在每个源文件的包声明前仅跟着的注释是包注释(§10.7.4)。通常,包注释的第一句应该先是包的功能概要说明。一个包通常只有一个源文件有包注释(译注:如果有多个包注释,目前的文档工具会根据源文件名的先后顺序将它们链接为一个包注释)。如果包注释很大,通常会放到一个独立的doc.go文件中。 -**練習 2.1:** 向tempconv包添加類型、常量和函數用來處理Kelvin絶對溫度的轉換,Kelvin 絶對零度是−273.15°C,Kelvin絶對溫度1K和攝氏度1°C的單位間隔是一樣的。 +**练习 2.1:** 向tempconv包添加类型、常量和函数用来处理Kelvin绝对温度的转换,Kelvin 绝对零度是−273.15°C,Kelvin绝对温度1K和摄氏度1°C的单位间隔是一样的。 {% include "./ch2-06-1.md" %} diff --git a/ch2/ch2-07.md b/ch2/ch2-07.md index 8c255ea1..bf26c05a 100644 --- a/ch2/ch2-07.md +++ b/ch2/ch2-07.md @@ -1,18 +1,18 @@ ## 2.7. 作用域 -一個聲明語句將程序中的實體和一個名字關聯,比如一個函數或一個變量。聲明語句的作用域是指源代碼中可以有效使用這個名字的范圍。 +一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中可以有效使用这个名字的范围。 -不要將作用域和生命週期混爲一談。聲明語句的作用域對應的是一個源代碼的文本區域;它是一個編譯時的屬性。一個變量的生命週期是指程序運行時變量存在的有效時間段,在此時間區域內它可以被程序的其他部分引用;是一個運行時的概念。 +不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。 -語法塊是由花括弧所包含的一繫列語句,就像函數體或循環體花括弧對應的語法塊那樣。語法塊內部聲明的名字是無法被外部語法塊訪問的。語法決定了內部聲明的名字的作用域范圍。我們可以這樣理解,語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼,我們稱之爲語法塊。有一個語法塊爲整個源代碼,稱爲全局語法塊;然後是每個包的包語法決;每個for、if和switch語句的語法決;每個switch或select的分支也有獨立的語法決;當然也包括顯式書寫的語法塊(花括弧包含的語句)。 +语法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧对应的语法块那样。语法块内部声明的名字是无法被外部语法块访问的。语法决定了内部声明的名字的作用域范围。我们可以这样理解,语法块可以包含其他类似组批量声明等没有用花括弧包含的代码,我们称之为语法块。有一个语法块为整个源代码,称为全局语法块;然后是每个包的包语法决;每个for、if和switch语句的语法决;每个switch或select的分支也有独立的语法决;当然也包括显式书写的语法块(花括弧包含的语句)。 -聲明語句對應的詞法域決定了作用域范圍的大小。對於內置的類型、函數和常量,比如int、len和true等是在全局作用域的,因此可以在整個程序中直接使用。任何在在函數外部(也就是包級語法域)聲明的名字可以在同一個包的任何源文件中訪問的。對於導入的包,例如tempconv導入的fmt包,則是對應源文件級的作用域,因此隻能在當前的文件中訪問導入的fmt包,當前包的其它源文件無法訪問在當前源文件導入的包。還有許多聲明語句,比如tempconv.CToF函數中的變量c,則是局部作用域的,它隻能在函數內部(甚至隻能是局部的某些部分)訪問。 +声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。任何在在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域的,它只能在函数内部(甚至只能是局部的某些部分)访问。 -控製流標號,就是break、continue或goto語句後面跟着的那種標號,則是函數級的作用域。 +控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。 -一個程序可能包含多個同名的聲明,隻要它們在不同的詞法域就沒有關繫。例如,你可以聲明一個局部變量,和包級的變量同名。或者是像2.3.3節的例子那樣,你可以將一個函數參數的名字聲明爲new,雖然內置的new是全局作用域的。但是物極必反,如果濫用不同詞法域可重名的特性的話,可能導致程序很難閲讀。 +一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量,和包级的变量同名。或者是像2.3.3节的例子那样,你可以将一个函数参数的名字声明为new,虽然内置的new是全局作用域的。但是物极必反,如果滥用不同词法域可重名的特性的话,可能导致程序很难阅读。 -當編譯器遇到一個名字引用時,如果它看起來像一個聲明,它首先從最內層的詞法域向全局的作用域査找。如果査找失敗,則報告“未聲明的名字”這樣的錯誤。如果該名字在內部和外部的塊分别聲明過,則內部塊的聲明首先被找到。在這種情況下,內部聲明屏蔽了外部同名的聲明,讓外部的聲明的名字無法被訪問: +当编译器遇到一个名字引用时,如果它看起来像一个声明,它首先从最内层的词法域向全局的作用域查找。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问: ```Go func f() {} @@ -27,7 +27,7 @@ func main() { } ``` -在函數中詞法域可以深度嵌套,因此內部的一個聲明可能屏蔽外部的聲明。還有許多語法塊是if或for等控製流語句構造的。下面的代碼有三個不同的變量x,因爲它們是定義在不同的詞法域(這個例子隻是爲了演示作用域規則,但不是好的編程風格)。 +在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。还有许多语法块是if或for等控制流语句构造的。下面的代码有三个不同的变量x,因为它们是定义在不同的词法域(这个例子只是为了演示作用域规则,但不是好的编程风格)。 ```Go func main() { @@ -42,11 +42,11 @@ func main() { } ``` -在`x[i]`和`x + 'A' - 'a'`聲明語句的初始化的表達式中都引用了外部作用域聲明的x變量,稍後我們會解釋這個。(註意,後面的表達式與unicode.ToUpper併不等價。) +在`x[i]`和`x + 'A' - 'a'`声明语句的初始化的表达式中都引用了外部作用域声明的x变量,稍后我们会解释这个。(注意,后面的表达式与unicode.ToUpper并不等价。) -正如上面例子所示,併不是所有的詞法域都顯式地對應到由花括弧包含的語句;還有一些隱含的規則。上面的for語句創建了兩個詞法域:花括弧包含的是顯式的部分是for的循環體部分詞法域,另外一個隱式的部分則是循環的初始化部分,比如用於迭代變量i的初始化。隱式的詞法域部分的作用域還包含條件測試部分和循環後的迭代部分(`i++`),當然也包含循環體詞法域。 +正如上面例子所示,并不是所有的词法域都显式地对应到由花括弧包含的语句;还有一些隐含的规则。上面的for语句创建了两个词法域:花括弧包含的是显式的部分是for的循环体部分词法域,另外一个隐式的部分则是循环的初始化部分,比如用于迭代变量i的初始化。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分(`i++`),当然也包含循环体词法域。 -下面的例子同樣有三個不同的x變量,每個聲明在不同的詞法域,一個在函數體詞法域,一個在for隱式的初始化詞法域,一個在for循環體詞法域;隻有兩個塊是顯式創建的: +下面的例子同样有三个不同的x变量,每个声明在不同的词法域,一个在函数体词法域,一个在for隐式的初始化词法域,一个在for循环体词法域;只有两个块是显式创建的: ```Go func main() { @@ -58,7 +58,7 @@ func main() { } ``` -和for循環類似,if和switch語句也會在條件部分創建隱式詞法域,還有它們對應的執行體詞法域。下面的if-else測試鏈演示了x和y的有效作用域范圍: +和for循环类似,if和switch语句也会在条件部分创建隐式词法域,还有它们对应的执行体词法域。下面的if-else测试链演示了x和y的有效作用域范围: ```Go if x := f(); x == 0 { @@ -71,11 +71,11 @@ if x := f(); x == 0 { fmt.Println(x, y) // compile error: x and y are not visible here ``` -第二個if語句嵌套在第一個內部,因此第一個if語句條件初始化詞法域聲明的變量在第二個if中也可以訪問。switch語句的每個分支也有類似的詞法域規則:條件部分爲一個隱式詞法域,然後每個是每個分支的詞法域。 +第二个if语句嵌套在第一个内部,因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则:条件部分为一个隐式词法域,然后每个是每个分支的词法域。 -在包級别,聲明的順序併不會影響作用域范圍,因此一個先聲明的可以引用它自身或者是引用後面的一個聲明,這可以讓我們定義一些相互嵌套或遞歸的類型或函數。但是如果一個變量或常量遞歸引用了自身,則會産生編譯錯誤。 +在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。 -在這個程序中: +在这个程序中: ```Go if f, err := os.Open(fname); err != nil { // compile error: unused: f @@ -85,9 +85,9 @@ f.ReadByte() // compile error: undefined f f.Close() // compile error: undefined f ``` -變量f的作用域隻有在if語句內,因此後面的語句將無法引入它,這將導致編譯錯誤。你可能會收到一個局部變量f沒有聲明的錯誤提示,具體錯誤信息依賴編譯器的實現。 +变量f的作用域只有在if语句内,因此后面的语句将无法引入它,这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示,具体错误信息依赖编译器的实现。 -通常需要在if之前聲明變量,這樣可以確保後面的語句依然可以訪問變量: +通常需要在if之前声明变量,这样可以确保后面的语句依然可以访问变量: ```Go f, err := os.Open(fname) @@ -98,7 +98,7 @@ f.ReadByte() f.Close() ``` -你可能會考慮通過將ReadByte和Close移動到if的else塊來解決這個問題: +你可能会考虑通过将ReadByte和Close移动到if的else块来解决这个问题: ```Go if f, err := os.Open(fname); err != nil { @@ -110,9 +110,9 @@ if f, err := os.Open(fname); err != nil { } ``` -但這不是Go語言推薦的做法,Go語言的習慣是在if中處理錯誤然後直接返迴,這樣可以確保正常執行的語句不需要代碼縮進。 +但这不是Go语言推荐的做法,Go语言的习惯是在if中处理错误然后直接返回,这样可以确保正常执行的语句不需要代码缩进。 -要特别註意短變量聲明語句的作用域范圍,考慮下面的程序,它的目的是獲取當前的工作目録然後保存到一個包級的變量中。這可以本來通過直接調用os.Getwd完成,但是將這個從主邏輯中分離出來可能會更好,特别是在需要處理錯誤的時候。函數log.Fatalf用於打印日誌信息,然後調用os.Exit(1)終止程序。 +要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级的变量中。这可以本来通过直接调用os.Getwd完成,但是将这个从主逻辑中分离出来可能会更好,特别是在需要处理错误的时候。函数log.Fatalf用于打印日志信息,然后调用os.Exit(1)终止程序。 ```Go var cwd string @@ -125,9 +125,9 @@ func init() { } ``` -雖然cwd在外部已經聲明過,但是`:=`語句還是將cwd和err重新聲明爲新的局部變量。因爲內部聲明的cwd將屏蔽外部的聲明,因此上面的代碼併不會正確更新包級聲明的cwd變量。 +虽然cwd在外部已经声明过,但是`:=`语句还是将cwd和err重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级声明的cwd变量。 -由於當前的編譯器會檢測到局部聲明的cwd併沒有本使用,然後報告這可能是一個錯誤,但是這種檢測併不可靠。因爲一些小的代碼變更,例如增加一個局部cwd的打印語句,就可能導致這種檢測失效。 +由于当前的编译器会检测到局部声明的cwd并没有本使用,然后报告这可能是一个错误,但是这种检测并不可靠。因为一些小的代码变更,例如增加一个局部cwd的打印语句,就可能导致这种检测失效。 ```Go var cwd string @@ -141,9 +141,9 @@ func init() { } ``` -全局的cwd變量依然是沒有被正確初始化的,而且看似正常的日誌輸出更是讓這個BUG更加隱晦。 +全局的cwd变量依然是没有被正确初始化的,而且看似正常的日志输出更是让这个BUG更加隐晦。 -有許多方式可以避免出現類似潛在的問題。最直接的方法是通過單獨聲明err變量,來避免使用`:=`的簡短聲明方式: +有许多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明err变量,来避免使用`:=`的简短声明方式: ```Go var cwd string @@ -157,5 +157,5 @@ func init() { } ``` -我們已經看到包、文件、聲明和語句如何來表達一個程序結構。在下面的兩個章節,我們將探討數據的結構。 +我们已经看到包、文件、声明和语句如何来表达一个程序结构。在下面的两个章节,我们将探讨数据的结构。 diff --git a/ch2/ch2.md b/ch2/ch2.md index 32d42732..9d653a7f 100644 --- a/ch2/ch2.md +++ b/ch2/ch2.md @@ -1,5 +1,5 @@ -# 第2章 程序結構 +# 第2章 程序结构 -Go語言和其他編程語言一樣,一個大的程序是由很多小的基礎構件組成的。變量保存值,簡單的加法和減法運算被組合成較複雜的表達式。基礎類型被聚合爲數組或結構體等更複雜的數據結構。然後使用if和for之類的控製語句來組織和控製表達式的執行流程。然後多個語句被組織到一個個函數中,以便代碼的隔離和複用。函數以源文件和包的方式被組織。 +Go语言和其他编程语言一样,一个大的程序是由很多小的基础构件组成的。变量保存值,简单的加法和减法运算被组合成较复杂的表达式。基础类型被聚合为数组或结构体等更复杂的数据结构。然后使用if和for之类的控制语句来组织和控制表达式的执行流程。然后多个语句被组织到一个个函数中,以便代码的隔离和复用。函数以源文件和包的方式被组织。 -我們已經在前面章節的例子中看到了很多例子。在本章中,我們將深入討論Go程序基礎結構方面的一些細節。每個示例程序都是刻意寫的簡單,這樣我們可以減少複雜的算法或數據結構等不相關的問題帶來的榦擾,從而可以專註於Go語言本身的學習。 +我们已经在前面章节的例子中看到了很多例子。在本章中,我们将深入讨论Go程序基础结构方面的一些细节。每个示例程序都是刻意写的简单,这样我们可以减少复杂的算法或数据结构等不相关的问题带来的干扰,从而可以专注于Go语言本身的学习。 diff --git a/ch3/ch3-01.md b/ch3/ch3-01.md index a5d9caa1..a7882d4f 100644 --- a/ch3/ch3-01.md +++ b/ch3/ch3-01.md @@ -1,20 +1,20 @@ ## 3.1. 整型 -Go語言的數值類型包括幾種不同大小的整形數、浮點數和複數。每種數值類型都決定了對應的大小范圍和是否支持正負符號。讓我們先從整形數類型開始介紹。 +Go语言的数值类型包括几种不同大小的整形数、浮点数和复数。每种数值类型都决定了对应的大小范围和是否支持正负符号。让我们先从整形数类型开始介绍。 -Go語言同時提供了有符號和無符號類型的整數運算。這里有int8、int16、int32和int64四種截然不同大小的有符號整形數類型,分别對應8、16、32、64bit大小的有符號整形數,與此對應的是uint8、uint16、uint32和uint64四種無符號整形數類型。 +Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整形数类型,分别对应8、16、32、64bit大小的有符号整形数,与此对应的是uint8、uint16、uint32和uint64四种无符号整形数类型。 -這里還有兩種一般對應特定CPU平台機器字大小的有符號和無符號整數int和uint;其中int是應用最廣泛的數值類型。這兩種類型都有同樣的大小,32或64bit,但是我們不能對此做任何的假設;因爲不同的編譯器卽使在相同的硬件平台上可能産生不同的大小。 +这里还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型。这两种类型都有同样的大小,32或64bit,但是我们不能对此做任何的假设;因为不同的编译器即使在相同的硬件平台上可能产生不同的大小。 -Unicode字符rune類型是和int32等價的類型,通常用於表示一個Unicode碼點。這兩個名稱可以互換使用。同樣byte也是uint8類型的等價類型,byte類型一般用於強調數值是一個原始的數據而不是一個小的整數。 +Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。 -最後,還有一種無符號的整數類型uintptr,沒有指定具體的bit大小但是足以容納指針。uintptr類型隻有在底層編程是才需要,特别是Go語言和C語言函數庫或操作繫統接口相交互的地方。我們將在第十三章的unsafe包相關部分看到類似的例子。 +最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。我们将在第十三章的unsafe包相关部分看到类似的例子。 -不管它們的具體大小,int、uint和uintptr是不同類型的兄弟類型。其中int和int32也是不同的類型,卽使int的大小也是32bit,在需要將int當作int32類型的地方需要一個顯式的類型轉換操作,反之亦然。 +不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是不同的类型,即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,反之亦然。 -其中有符號整數采用2的補碼形式表示,也就是最高bit位用作表示符號位,一個n-bit的有符號數的值域是從$$-2^{n-1}$$到$$2^{n-1}-1$$。無符號整數的所有bit位都用於表示非負數,值域是0到$$2^n-1$$。例如,int8類型整數的值域是從-128到127,而uint8類型整數的值域是從0到255。 +其中有符号整数采用2的补码形式表示,也就是最高bit位用作表示符号位,一个n-bit的有符号数的值域是从$$-2^{n-1}$$到$$2^{n-1}-1$$。无符号整数的所有bit位都用于表示非负数,值域是0到$$2^n-1$$。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。 -下面是Go語言中關於算術運算、邏輯運算和比較運算的二元運算符,它們按照先級遞減的順序的排列: +下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照先级递减的顺序的排列: ``` * / % << >> & &^ @@ -24,13 +24,13 @@ Unicode字符rune類型是和int32等價的類型,通常用於表示一個Unic || ``` -二元運算符有五種優先級。在同一個優先級,使用左優先結合規則,但是使用括號可以明確優先順序,使用括號也可以用於提陞優先級,例如`mask & (1 << 28)`。 +二元运算符有五种优先级。在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序,使用括号也可以用于提升优先级,例如`mask & (1 << 28)`。 -對於上表中前兩行的運算符,例如+運算符還有一個與賦值相結合的對應運算符+=,可以用於簡化賦值語句。 +对于上表中前两行的运算符,例如+运算符还有一个与赋值相结合的对应运算符+=,可以用于简化赋值语句。 -算術運算符+、-、`*`和`/`可以適用與於整數、浮點數和複數,但是取模運算符%僅用於整數間的運算。對於不同編程語言,%取模運算的行爲可能併不相同。在Go語言中,%取模運算符的符號和被取模數的符號總是一致的,因此`-5%3`和`-5%-3`結果都是-2。除法運算符`/`的行爲則依賴於操作數是否爲全爲整數,比如`5.0/4.0`的結果是1.25,但是5/4的結果是1,因爲整數除法會向着0方向截斷餘數。 +算术运算符+、-、`*`和`/`可以适用与于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3`和`-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否为全为整数,比如`5.0/4.0`的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。 -如果一個算術運算的結果,不管是有符號或者是無符號的,如果需要更多的bit位才能正確表示的話,就説明計算結果是溢出了。超出的高位的bit位部分將被丟棄。如果原始的數值是有符號類型,而且最左邊的bit爲是1的話,那麽最終結果可能是負的,例如int8的例子: +如果一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit为是1的话,那么最终结果可能是负的,例如int8的例子: ```Go var u uint8 = 255 @@ -40,7 +40,7 @@ var i int8 = 127 fmt.Println(i, i+1, i*i) // "127 -128 1" ``` -兩個相同的整數類型可以使用下面的二元比較運算符進行比較;比較表達式的結果是布爾類型。 +两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型。 ``` == equal to @@ -51,31 +51,31 @@ fmt.Println(i, i+1, i*i) // "127 -128 1" >= greater than or equal to ``` -事實上,布爾型、數字類型和字符串等基本類型都是可比較的,也就是説兩個相同類型的值可以用==和!=進行比較。此外,整數、浮點數和字符串可以根據比較結果排序。許多其它類型的值可能是不可比較的,因此也就可能是不可排序的。對於我們遇到的每種類型,我們需要保證規則的一致性。 +事实上,布尔型、数字类型和字符串等基本类型都是可比较的,也就是说两个相同类型的值可以用==和!=进行比较。此外,整数、浮点数和字符串可以根据比较结果排序。许多其它类型的值可能是不可比较的,因此也就可能是不可排序的。对于我们遇到的每种类型,我们需要保证规则的一致性。 -這里是一元的加法和減法運算符: +这里是一元的加法和减法运算符: ``` -+ 一元加法 (無效果) -- 負數 ++ 一元加法 (无效果) +- 负数 ``` -對於整數,+x是0+x的簡寫,-x則是0-x的簡寫;對於浮點數和複數,+x就是x,-x則是x 的負數。 +对于整数,+x是0+x的简写,-x则是0-x的简写;对于浮点数和复数,+x就是x,-x则是x 的负数。 -Go語言還提供了以下的bit位操作運算符,前面4個操作運算符併不區分是有符號還是無符號數: +Go语言还提供了以下的bit位操作运算符,前面4个操作运算符并不区分是有符号还是无符号数: ``` -& 位運算 AND -| 位運算 OR -^ 位運算 XOR +& 位运算 AND +| 位运算 OR +^ 位运算 XOR &^ 位清空 (AND NOT) << 左移 >> 右移 ``` -位操作運算符`^`作爲二元運算符時是按位異或(XOR),當用作一元運算符時表示按位取反;也就是説,它返迴一個每個bit位都取反的數。位操作運算符`&^`用於按位置零(AND NOT):表達式`z = x &^ y`結果z的bit位爲0,如果對應y中bit位爲1的話,否則對應的bit位等於x相應的bit位的值。 +位操作运算符`^`作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符`&^`用于按位置零(AND NOT):表达式`z = x &^ y`结果z的bit位为0,如果对应y中bit位为1的话,否则对应的bit位等于x相应的bit位的值。 -下面的代碼演示了如何使用位操作解釋uint8類型值的8個獨立的bit位。它使用了Printf函數的%b參數打印二進製格式的數字;其中%08b中08表示打印至少8個字符寬度,不足的前綴部分用0填充。 +下面的代码演示了如何使用位操作解释uint8类型值的8个独立的bit位。它使用了Printf函数的%b参数打印二进制格式的数字;其中%08b中08表示打印至少8个字符宽度,不足的前缀部分用0填充。 ```Go var x uint8 = 1<<1 | 1<<5 @@ -99,13 +99,13 @@ fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6} fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4} ``` -(6.5節給出了一個可以遠大於一個字節的整數集的實現。) +(6.5节给出了一个可以远大于一个字节的整数集的实现。) -在`x<>n`移位運算中,決定了移位操作bit數部分必須是無符號數;被操作的x數可以是有符號或無符號數。算術上,一個`x<>n`右移運算等價於除以$$2^n$$。 +在`x<>n`移位运算中,决定了移位操作bit数部分必须是无符号数;被操作的x数可以是有符号或无符号数。算术上,一个`x<>n`右移运算等价于除以$$2^n$$。 -左移運算用零填充右邊空缺的bit位,無符號數的右移運算也是用0填充左邊空缺的bit位,但是有符號數的右移運算會用符號位的值填充左邊空缺的bit位。因爲這個原因,最好用無符號運算,這樣你可以將整數完全當作一個bit位模式處理。 +左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。 -盡管Go語言提供了無符號數和運算,卽使數值本身不可能出現負數我們還是傾向於使用有符號的int類型,就像數組的長度那樣,雖然使用uint無符號類型似乎是一個更合理的選擇。事實上,內置的len函數返迴一個有符號的int,我們可以像下面例子那樣處理逆序循環。 +尽管Go语言提供了无符号数和运算,即使数值本身不可能出现负数我们还是倾向于使用有符号的int类型,就像数组的长度那样,虽然使用uint无符号类型似乎是一个更合理的选择。事实上,内置的len函数返回一个有符号的int,我们可以像下面例子那样处理逆序循环。 ```Go medals := []string{"gold", "silver", "bronze"} @@ -114,13 +114,13 @@ for i := len(medals) - 1; i >= 0; i-- { } ``` -另一個選擇對於上面的例子來説將是災難性的。如果len函數返迴一個無符號數,那麽i也將是無符號的uint類型,然後條件`i >= 0`則永遠爲眞。在三次迭代之後,也就是`i == 0`時,i--語句將不會産生-1,而是變成一個uint類型的最大值(可能是$$2^64-1$$),然後medals[i]表達式將發生運行時panic異常(§5.9),也就是試圖訪問一個slice范圍以外的元素。 +另一个选择对于上面的例子来说将是灾难性的。如果len函数返回一个无符号数,那么i也将是无符号的uint类型,然后条件`i >= 0`则永远为真。在三次迭代之后,也就是`i == 0`时,i--语句将不会产生-1,而是变成一个uint类型的最大值(可能是$$2^64-1$$),然后medals[i]表达式将发生运行时panic异常(§5.9),也就是试图访问一个slice范围以外的元素。 -出於這個原因,無符號數往往隻有在位運算或其它特殊的運算場景才會使用,就像bit集合、分析二進製文件格式或者是哈希和加密操作等。它們通常併不用於僅僅是表達非負數量的場合。 +出于这个原因,无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。 -一般來説,需要一個顯式的轉換將一個值從一種類型轉化位另一種類型,併且算術和邏輯運算的二元操作中必須是相同的類型。雖然這偶爾會導致需要很長的表達式,但是它消除了所有和類型相關的問題,而且也使得程序容易理解。 +一般来说,需要一个显式的转换将一个值从一种类型转化位另一种类型,并且算术和逻辑运算的二元操作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题,而且也使得程序容易理解。 -在很多場景,會遇到類似下面的代碼通用的錯誤: +在很多场景,会遇到类似下面的代码通用的错误: ```Go var apples int32 = 1 @@ -128,19 +128,19 @@ var oranges int16 = 2 var compote int = apples + oranges // compile error ``` -當嚐試編譯這三個語句時,將産生一個錯誤信息: +当尝试编译这三个语句时,将产生一个错误信息: ``` invalid operation: apples + oranges (mismatched types int32 and int16) ``` -這種類型不匹配的問題可以有幾種不同的方法脩複,最常見方法是將它們都顯式轉型爲一個常見類型: +这种类型不匹配的问题可以有几种不同的方法修复,最常见方法是将它们都显式转型为一个常见类型: ```Go var compote = int(apples) + int(oranges) ``` -如2.5節所述,對於每種類型T,如果轉換允許的話,類型轉換操作T(x)將x轉換爲T類型。許多整形數之間的相互轉換併不會改變數值;它們隻是告訴編譯器如何解釋這個值。但是對於將一個大尺寸的整數類型轉爲一個小尺寸的整數類型,或者是將一個浮點數轉爲整數,可能會改變數值或丟失精度: +如2.5节所述,对于每种类型T,如果转换允许的话,类型转换操作T(x)将x转换为T类型。许多整形数之间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大尺寸的整数类型转为一个小尺寸的整数类型,或者是将一个浮点数转为整数,可能会改变数值或丢失精度: ```Go f := 3.141 // a float64 @@ -150,16 +150,16 @@ f = 1.99 fmt.Println(int(f)) // "1" ``` -浮點數到整數的轉換將丟失任何小數部分,然後向數軸零方向截斷。你應該避免對可能會超出目標類型表示范圍的數值類型轉換,因爲截斷的行爲可能依賴於具體的實現: +浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。你应该避免对可能会超出目标类型表示范围的数值类型转换,因为截断的行为可能依赖于具体的实现: ```Go f := 1e100 // a float64 -i := int(f) // 結果依賴於具體實現 +i := int(f) // 结果依赖于具体实现 ``` -任何大小的整數字面值都可以用以0開始的八進製格式書寫,例如0666;或用以0x或0X開頭的十六進製格式書寫,例如0xdeadbeef。十六進製數字可以用大寫或小寫字母。如今八進製數據通常用於POSIX操作繫統上的文件訪問權限標誌,十六進製數字則更強調數字值的bit位模式。 +任何大小的整数字面值都可以用以0开始的八进制格式书写,例如0666;或用以0x或0X开头的十六进制格式书写,例如0xdeadbeef。十六进制数字可以用大写或小写字母。如今八进制数据通常用于POSIX操作系统上的文件访问权限标志,十六进制数字则更强调数字值的bit位模式。 -當使用fmt包打印一個數值時,我們可以用%d、%o或%x參數控製輸出的進製格式,就像下面的例子: +当使用fmt包打印一个数值时,我们可以用%d、%o或%x参数控制输出的进制格式,就像下面的例子: ```Go o := 0666 @@ -170,11 +170,11 @@ fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x) // 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF ``` -請註意fmt的兩個使用技巧。通常Printf格式化字符串包含多個%參數時將會包含對應相同數量的額外操作數,但是%之後的`[1]`副詞告訴Printf函數再次使用第一個操作數。第二,%後的`#`副詞告訴Printf在用%o、%x或%X輸出時生成0、0x或0X前綴。 +请注意fmt的两个使用技巧。通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的`[1]`副词告诉Printf函数再次使用第一个操作数。第二,%后的`#`副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。 -字符面值通過一對單引號直接包含對應字符。最簡單的例子是ASCII中類似'a'寫法的字符面值,但是我們也可以通過轉義的數值來表示任意的Unicode碼點對應的字符,馬上將會看到這樣的例子。 +字符面值通过一对单引号直接包含对应字符。最简单的例子是ASCII中类似'a'写法的字符面值,但是我们也可以通过转义的数值来表示任意的Unicode码点对应的字符,马上将会看到这样的例子。 -字符使用`%c`參數打印,或者是用`%q`參數打印帶單引號的字符: +字符使用`%c`参数打印,或者是用`%q`参数打印带单引号的字符: ```Go ascii := 'a' diff --git a/ch3/ch3-02.md b/ch3/ch3-02.md index 5fbdb7f6..f89fd679 100644 --- a/ch3/ch3-02.md +++ b/ch3/ch3-02.md @@ -1,30 +1,30 @@ -## 3.2. 浮點數 +## 3.2. 浮点数 -Go語言提供了兩種精度的浮點數,float32和float64。它們的算術規范由IEEE754浮點數国際標準定義,該浮點數規范被所有現代的CPU支持。 +Go语言提供了两种精度的浮点数,float32和float64。它们的算术规范由IEEE754浮点数国际标准定义,该浮点数规范被所有现代的CPU支持。 -這些浮點數類型的取值范圍可以從很微小到很鉅大。浮點數的范圍極限值可以在math包找到。常量math.MaxFloat32表示float32能表示的最大數值,大約是 3.4e38;對應的math.MaxFloat64常量大約是1.8e308。它們分别能表示的最小值近似爲1.4e-45和4.9e-324。 +这些浮点数类型的取值范围可以从很微小到很巨大。浮点数的范围极限值可以在math包找到。常量math.MaxFloat32表示float32能表示的最大数值,大约是 3.4e38;对应的math.MaxFloat64常量大约是1.8e308。它们分别能表示的最小值近似为1.4e-45和4.9e-324。 -一個float32類型的浮點數可以提供大約6個十進製數的精度,而float64則可以提供約15個十進製數的精度;通常應該優先使用float64類型,因爲float32類型的纍計計算誤差很容易擴散,併且float32能精確表示的正整數併不是很大(譯註:因爲float32的有效bit位隻有23個,其它的bit位用於指數和符號;當整數大於23bit能表達的范圍時,float32的表示將出現誤差): +一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度;通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大(译注:因为float32的有效bit位只有23个,其它的bit位用于指数和符号;当整数大于23bit能表达的范围时,float32的表示将出现误差): ```Go var f float32 = 16777216 // 1 << 24 fmt.Println(f == f+1) // "true"! ``` -浮點數的字面值可以直接寫小數部分,像這樣: +浮点数的字面值可以直接写小数部分,像这样: ```Go const e = 2.71828 // (approximately) ``` -小數點前面或後面的數字都可能被省略(例如.707或1.)。很小或很大的數最好用科學計數法書寫,通過e或E來指定指數部分: +小数点前面或后面的数字都可能被省略(例如.707或1.)。很小或很大的数最好用科学计数法书写,通过e或E来指定指数部分: ```Go -const Avogadro = 6.02214129e23 // 阿伏伽德羅常數 -const Planck = 6.62606957e-34 // 普朗剋常數 +const Avogadro = 6.02214129e23 // 阿伏伽德罗常数 +const Planck = 6.62606957e-34 // 普朗克常数 ``` -用Printf函數的%g參數打印浮點數,將采用更緊湊的表示形式打印,併提供足夠的精度,但是對應表格的數據,使用%e(帶指數)或%f的形式打印可能更合適。所有的這三個打印形式都可以指定打印的寬度和控製打印精度。 +用Printf函数的%g参数打印浮点数,将采用更紧凑的表示形式打印,并提供足够的精度,但是对应表格的数据,使用%e(带指数)或%f的形式打印可能更合适。所有的这三个打印形式都可以指定打印的宽度和控制打印精度。 ```Go for x := 0; x < 8; x++ { @@ -32,7 +32,7 @@ for x := 0; x < 8; x++ { } ``` -上面代碼打印e的冪,打印精度是小數點後三個小數精度和8個字符寬度: +上面代码打印e的幂,打印精度是小数点后三个小数精度和8个字符宽度: ``` x = 0 e^x = 1.000 @@ -45,21 +45,21 @@ x = 6 e^x = 403.429 x = 7 e^x = 1096.633 ``` -math包中除了提供大量常用的數學函數外,還提供了IEEE754浮點數標準中定義的特殊值的創建和測試:正無窮大和負無窮大,分别用於表示太大溢出的數字和除零的結果;還有NaN非數,一般用於表示無效的除法操作結果0/0或Sqrt(-1). +math包中除了提供大量常用的数学函数外,还提供了IEEE754浮点数标准中定义的特殊值的创建和测试:正无穷大和负无穷大,分别用于表示太大溢出的数字和除零的结果;还有NaN非数,一般用于表示无效的除法操作结果0/0或Sqrt(-1). ```Go var z float64 fmt.Println(z, -z, 1/z, -1/z, z/z) // "0 -0 +Inf -Inf NaN" ``` -函數math.IsNaN用於測試一個數是否是非數NaN,math.NaN則返迴非數對應的值。雖然可以用math.NaN來表示一個非法的結果,但是測試一個結果是否是非數NaN則是充滿風險的,因爲NaN和任何數都是不相等的(譯註:在浮點數中,NaN、正無窮大和負無窮大都不是唯一的,每個都有非常多種的bit模式表示): +函数math.IsNaN用于测试一个数是否是非数NaN,math.NaN则返回非数对应的值。虽然可以用math.NaN来表示一个非法的结果,但是测试一个结果是否是非数NaN则是充满风险的,因为NaN和任何数都是不相等的(译注:在浮点数中,NaN、正无穷大和负无穷大都不是唯一的,每个都有非常多种的bit模式表示): ```Go nan := math.NaN() fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false" ``` -如果一個函數返迴的浮點數結果可能失敗,最好的做法是用單獨的標誌報告失敗,像這樣: +如果一个函数返回的浮点数结果可能失败,最好的做法是用单独的标志报告失败,像这样: ```Go func compute() (value float64, ok bool) { @@ -71,7 +71,7 @@ func compute() (value float64, ok bool) { } ``` -接下來的程序演示了通過浮點計算生成的圖形。它是帶有兩個參數的z = f(x, y)函數的三維形式,使用了可縮放矢量圖形(SVG)格式輸出,SVG是一個用於矢量線繪製的XML標準。圖3.1顯示了sin(r)/r函數的輸出圖形,其中r是sqrt(x*x+y*y)。 +接下来的程序演示了通过浮点计算生成的图形。它是带有两个参数的z = f(x, y)函数的三维形式,使用了可缩放矢量图形(SVG)格式输出,SVG是一个用于矢量线绘制的XML标准。图3.1显示了sin(r)/r函数的输出图形,其中r是sqrt(x*x+y*y)。 ![](../images/ch3-01.png) @@ -133,30 +133,30 @@ func f(x, y float64) float64 { } ``` -要註意的是corner函數返迴了兩個結果,分别對應每個網格頂點的坐標參數。 +要注意的是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)的值。 +第二个坐标系是一个三维的网格浮点坐标(x,y,z),其中x和y是i和j的线性函数,通过平移转换位网格单元的中心,然后用xyrange系数缩放。高度z是函数f(x,y)的值。 -第三個坐標繫是一個二維的畵布,起點(0,0)在左上角。畵布中點的坐標用(sx, sy)表示。我們使用等角投影將三維點 +第三个坐标系是一个二维的画布,起点(0,0)在左上角。画布中点的坐标用(sx, sy)表示。我们使用等角投影将三维点 ![](../images/ch3-02.png) -(x,y,z)投影到二維的畵布中。畵布中從遠處到右邊的點對應較大的x值和較大的y值。併且畵布中x和y值越大,則對應的z值越小。x和y的垂直和水平縮放繫數來自30度角的正絃和餘絃值。z的縮放繫數0.4,是一個任意選擇的參數。 +(x,y,z)投影到二维的画布中。画布中从远处到右边的点对应较大的x值和较大的y值。并且画布中x和y值越大,则对应的z值越小。x和y的垂直和水平缩放系数来自30度角的正弦和余弦值。z的缩放系数0.4,是一个任意选择的参数。 -對於二維網格中的每一個網格單元,main函數計算單元的四個頂點在畵布中對應多邊形ABCD的頂點,其中B對應(i,j)頂點位置,A、C和D是其它相鄰的頂點,然後輸出SVG的繪製指令。 +对于二维网格中的每一个网格单元,main函数计算单元的四个顶点在画布中对应多边形ABCD的顶点,其中B对应(i,j)顶点位置,A、C和D是其它相邻的顶点,然后输出SVG的绘制指令。 -**練習 3.1:** 如果f函數返迴的是無限製的float64值,那麽SVG文件可能輸出無效的多邊形元素(雖然許多SVG渲染器會妥善處理這類問題)。脩改程序跳過無效的多邊形。 +**练习 3.1:** 如果f函数返回的是无限制的float64值,那么SVG文件可能输出无效的多边形元素(虽然许多SVG渲染器会妥善处理这类问题)。修改程序跳过无效的多边形。 -**練習 3.2:** 試驗math包中其他函數的渲染圖形。你是否能輸出一個egg box、moguls或a saddle圖案? +**练习 3.2:** 试验math包中其他函数的渲染图形。你是否能输出一个egg box、moguls或a saddle图案? -**練習 3.3:** 根據高度給每個多邊形上色,那樣峯值部將是紅色(#ff0000),谷部將是藍色(#0000ff)。 +**练习 3.3:** 根据高度给每个多边形上色,那样峰值部将是红色(#ff0000),谷部将是蓝色(#0000ff)。 -**練習 3.4:** 參考1.7節Lissajous例子的函數,構造一個web服務器,用於計算函數麴面然後返迴SVG數據給客戶端。服務器必須設置Content-Type頭部: +**练习 3.4:** 参考1.7节Lissajous例子的函数,构造一个web服务器,用于计算函数曲面然后返回SVG数据给客户端。服务器必须设置Content-Type头部: ```Go w.Header().Set("Content-Type", "image/svg+xml") ``` -(這一步在Lissajous例子中不是必須的,因爲服務器使用標準的PNG圖像格式,可以根據前面的512個字節自動輸出對應的頭部。)允許客戶端通過HTTP請求參數設置高度、寬度和顔色等參數。 +(这一步在Lissajous例子中不是必须的,因为服务器使用标准的PNG图像格式,可以根据前面的512个字节自动输出对应的头部。)允许客户端通过HTTP请求参数设置高度、宽度和颜色等参数。 diff --git a/ch3/ch3-03.md b/ch3/ch3-03.md index 297336dc..5c0c9de2 100644 --- a/ch3/ch3-03.md +++ b/ch3/ch3-03.md @@ -1,6 +1,6 @@ -## 3.3. 複數 +## 3.3. 复数 -Go語言提供了兩種精度的複數類型:complex64和complex128,分别對應float32和float64兩種浮點數精度。內置的complex函數用於構建複數,內建的real和imag函數分别返迴複數的實部和虛部: +Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部: ```Go var x complex128 = complex(1, 2) // 1+2i @@ -10,28 +10,28 @@ fmt.Println(real(x*y)) // "-5" fmt.Println(imag(x*y)) // "10" ``` -如果一個浮點數面值或一個十進製整數面值後面跟着一個i,例如3.141592i或2i,它將構成一個複數的虛部,複數的實部是0: +如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0: ```Go fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1 ``` -在常量算術規則下,一個複數常量可以加到另一個普通數值常量(整數或浮點數、實部或虛部),我們可以用自然的方式書寫複數,就像1+2i或與之等價的寫法2i+1。上面x和y的聲明語句還可以簡化: +在常量算术规则下,一个复数常量可以加到另一个普通数值常量(整数或浮点数、实部或虚部),我们可以用自然的方式书写复数,就像1+2i或与之等价的写法2i+1。上面x和y的声明语句还可以简化: ```Go x := 1 + 2i y := 3 + 4i ``` -複數也可以用==和!=進行相等比較。隻有兩個複數的實部和虛部都相等的時候它們才是相等的(譯註:浮點數的相等比較是危險的,需要特别小心處理精度問題)。 +复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的(译注:浮点数的相等比较是危险的,需要特别小心处理精度问题)。 -math/cmplx包提供了複數處理的許多函數,例如求複數的平方根函數和求冪函數。 +math/cmplx包提供了复数处理的许多函数,例如求复数的平方根函数和求幂函数。 ```Go fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)" ``` -下面的程序使用complex128複數算法來生成一個Mandelbrot圖像。 +下面的程序使用complex128复数算法来生成一个Mandelbrot图像。 gopl.io/ch3/mandelbrot ```Go @@ -81,16 +81,16 @@ func mandelbrot(z complex128) color.Color { } ``` -用於遍歷1024x1024圖像每個點的兩個嵌套的循環對應-2到+2區間的複數平面。程序反複測試每個點對應複數值平方值加一個增量值對應的點是否超出半徑爲2的圓。如果超過了,通過根據預設置的逃逸迭代次數對應的灰度顔色來代替。如果不是,那麽該點屬於Mandelbrot集合,使用黑色顔色標記。最終程序將生成的PNG格式分形圖像圖像輸出到標準輸出,如圖3.3所示。 +用于遍历1024x1024图像每个点的两个嵌套的循环对应-2到+2区间的复数平面。程序反复测试每个点对应复数值平方值加一个增量值对应的点是否超出半径为2的圆。如果超过了,通过根据预设置的逃逸迭代次数对应的灰度颜色来代替。如果不是,那么该点属于Mandelbrot集合,使用黑色颜色标记。最终程序将生成的PNG格式分形图像图像输出到标准输出,如图3.3所示。 ![](../images/ch3-03.png) -**練習 3.5:** 實現一個綵色的Mandelbrot圖像,使用image.NewRGBA創建圖像,使用color.RGBA或color.YCbCr生成顔色。 +**练习 3.5:** 实现一个彩色的Mandelbrot图像,使用image.NewRGBA创建图像,使用color.RGBA或color.YCbCr生成颜色。 -**練習 3.6:** 陞采樣技術可以降低每個像素對計算顔色值和平均值的影響。簡單的方法是將每個像素分層四個子像素,實現它。 +**练习 3.6:** 升采样技术可以降低每个像素对计算颜色值和平均值的影响。简单的方法是将每个像素分层四个子像素,实现它。 -**練習 3.7:** 另一個生成分形圖像的方式是使用牛頓法來求解一個複數方程,例如$$z^4-1=0$$。每個起點到四個根的迭代次數對應陰影的灰度。方程根對應的點用顔色表示。 +**练习 3.7:** 另一个生成分形图像的方式是使用牛顿法来求解一个复数方程,例如$$z^4-1=0$$。每个起点到四个根的迭代次数对应阴影的灰度。方程根对应的点用颜色表示。 -**練習 3.8:** 通過提高精度來生成更多級别的分形。使用四種不同精度類型的數字實現相同的分形:complex64、complex128、big.Float和big.Rat。(後面兩種類型在math/big包聲明。Float是有指定限精度的浮點數;Rat是無效精度的有理數。)它們間的性能和內存使用對比如何?當渲染圖可見時縮放的級别是多少? +**练习 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 4b5a7019..2e143f1e 100644 --- a/ch3/ch3-04.md +++ b/ch3/ch3-04.md @@ -1,16 +1,16 @@ -## 3.4. 布爾型 +## 3.4. 布尔型 -一個布爾類型的值隻有兩種:true和false。if和for語句的條件部分都是布爾類型的值,併且==和<等比較操作也會産生布爾型的值。一元操作符`!`對應邏輯非操作,因此`!true`的值爲`false`,更羅嗦的説法是`(!true==false)==true`,雖然表達方式不一樣,不過我們一般會采用簡潔的布爾表達式,就像用x來表示`x==true`。 +一个布尔类型的值只有两种:true和false。if和for语句的条件部分都是布尔类型的值,并且==和<等比较操作也会产生布尔型的值。一元操作符`!`对应逻辑非操作,因此`!true`的值为`false`,更罗嗦的说法是`(!true==false)==true`,虽然表达方式不一样,不过我们一般会采用简洁的布尔表达式,就像用x来表示`x==true`。 -布爾值可以和&&(AND)和||(OR)操作符結合,併且可能會有短路行爲:如果運算符左邊值已經可以確定整個布爾表達式的值,那麽運算符右邊的值將不在被求值,因此下面的表達式總是安全的: +布尔值可以和&&(AND)和||(OR)操作符结合,并且可能会有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不在被求值,因此下面的表达式总是安全的: ```Go s != "" && s[0] == 'x' ``` -其中s[0]操作如果應用於空字符串將會導致panic異常。 +其中s[0]操作如果应用于空字符串将会导致panic异常。 -因爲`&&`的優先級比`||`高(助記:`&&`對應邏輯乘法,`||`對應邏輯加法,乘法比加法優先級要高),下面形式的布爾表達式是不需要加小括弧的: +因为`&&`的优先级比`||`高(助记:`&&`对应逻辑乘法,`||`对应逻辑加法,乘法比加法优先级要高),下面形式的布尔表达式是不需要加小括弧的: ```Go if 'a' <= c && c <= 'z' || @@ -20,7 +20,7 @@ if 'a' <= c && c <= 'z' || } ``` -布爾值併不會隱式轉換爲數字值0或1,反之亦然。必須使用一個顯式的if語句輔助轉換: +布尔值并不会隐式转换为数字值0或1,反之亦然。必须使用一个显式的if语句辅助转换: ```Go i := 0 @@ -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/ch3/ch3-05-1.md b/ch3/ch3-05-1.md index c6a56da5..3ab3d581 100644 --- a/ch3/ch3-05-1.md +++ b/ch3/ch3-05-1.md @@ -1,6 +1,6 @@ ### 3.5.1. 字符串面值 -字符串值也可以用字符串面值方式編寫,隻要將一繫列字節序列包含在雙引號卽可: +字符串值也可以用字符串面值方式编写,只要将一系列字节序列包含在双引号即可: ``` "Hello, 世界" @@ -8,28 +8,28 @@ ![](../images/ch3-04.png) -因爲Go語言源文件總是用UTF8編碼,併且Go語言的文本字符串也以UTF8編碼的方式處理,因此我們可以將Unicode碼點也寫到字符串面值中。 +因为Go语言源文件总是用UTF8编码,并且Go语言的文本字符串也以UTF8编码的方式处理,因此我们可以将Unicode码点也写到字符串面值中。 -在一個雙引號包含的字符串面值中,可以用以反斜槓`\`開頭的轉義序列插入任意的數據。下面的換行、迴車和製表符等是常見的ASCII控製代碼的轉義方式: +在一个双引号包含的字符串面值中,可以用以反斜杠`\`开头的转义序列插入任意的数据。下面的换行、回车和制表符等是常见的ASCII控制代码的转义方式: ``` -\a 響鈴 +\a 响铃 \b 退格 -\f 換頁 -\n 換行 -\r 迴車 -\t 製表符 -\v 垂直製表符 -\' 單引號 (隻用在 '\'' 形式的rune符號面值中) -\" 雙引號 (隻用在 "..." 形式的字符串面值中) -\\ 反斜槓 +\f 换页 +\n 换行 +\r 回车 +\t 制表符 +\v 垂直制表符 +\' 单引号 (只用在 '\'' 形式的rune符号面值中) +\" 双引号 (只用在 "..." 形式的字符串面值中) +\\ 反斜杠 ``` -可以通過十六進製或八進製轉義在字符串面值包含任意的字節。一個十六進製的轉義形式是\xhh,其中兩個h表示十六進製數字(大寫或小寫都可以)。一個八進製轉義形式是\ooo,包含三個八進製的o數字(0到7),但是不能超過`\377`(譯註:對應一個字節的范圍,十進製爲255)。每一個單一的字節表達一個特定的值。稍後我們將看到如何將一個Unicode碼點寫到字符串面值中。 +可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转义形式是\xhh,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的o数字(0到7),但是不能超过`\377`(译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。 -一個原生的字符串面值形式是`...`,使用反引號```代替雙引號。在原生的字符串面值中,沒有轉義操作;全部的內容都是字面的意思,包含退格和換行,因此一個程序中的原生字符串面值可能跨越多行(譯註:在原生字符串面值內部是無法直接寫```字符的,可以用八進製或十六進製轉義或+"```"鏈接字符串常量完成)。唯一的特殊處理是會刪除迴車以保證在所有平台上的值都是一樣的,包括那些把迴車也放入文本文件的繫統(譯註:Windows繫統會把迴車和換行一起放入文本文件中)。 +一个原生的字符串面值形式是`...`,使用反引号```代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行(译注:在原生字符串面值内部是无法直接写```字符的,可以用八进制或十六进制转义或+"```"链接字符串常量完成)。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的,包括那些把回车也放入文本文件的系统(译注:Windows系统会把回车和换行一起放入文本文件中)。 -原生字符串面值用於編寫正則表達式會很方便,因爲正則表達式往往會包含很多反斜槓。原生字符串面值同時被廣泛應用於HTML模闆、JSON面值、命令行提示信息以及那些需要擴展到多行的場景。 +原生字符串面值用于编写正则表达式会很方便,因为正则表达式往往会包含很多反斜杠。原生字符串面值同时被广泛应用于HTML模板、JSON面值、命令行提示信息以及那些需要扩展到多行的场景。 ```Go const GoUsage = `Go is a tool for managing Go source code. diff --git a/ch3/ch3-05-2.md b/ch3/ch3-05-2.md index 6b238d88..4e52df68 100644 --- a/ch3/ch3-05-2.md +++ b/ch3/ch3-05-2.md @@ -1,12 +1,12 @@ ### 3.5.2. Unicode -在很久以前,世界還是比較簡單的,起碼計算機世界就隻有一個ASCII字符集:美国信息交換標準代碼。ASCII,更準確地説是美国的ASCII,使用7bit來表示128個字符:包含英文字母的大小寫、數字、各種標點符號和設置控製符。對於早期的計算機程序來説,這些就足夠了,但是這也導致了世界上很多其他地區的用戶無法直接使用自己的符號繫統。隨着互聯網的發展,混合多種語言的數據變得很常見(譯註:比如本身的英文原文或中文翻譯都包含了ASCII、中文、日文等多種語言字符)。如何有效處理這些包含了各種語言的豐富多樣的文本數據呢? +在很久以前,世界还是比较简单的,起码计算机世界就只有一个ASCII字符集:美国信息交换标准代码。ASCII,更准确地说是美国的ASCII,使用7bit来表示128个字符:包含英文字母的大小写、数字、各种标点符号和设置控制符。对于早期的计算机程序来说,这些就足够了,但是这也导致了世界上很多其他地区的用户无法直接使用自己的符号系统。随着互联网的发展,混合多种语言的数据变得很常见(译注:比如本身的英文原文或中文翻译都包含了ASCII、中文、日文等多种语言字符)。如何有效处理这些包含了各种语言的丰富多样的文本数据呢? -答案就是使用Unicode( http://unicode.org ),它收集了這個世界上所有的符號繫統,包括重音符號和其它變音符號,製表符和迴車符,還有很多神祕的符號,每個符號都分配一個唯一的Unicode碼點,Unicode碼點對應Go語言中的rune整數類型(譯註:rune是int32等價類型)。 +答案就是使用Unicode( http://unicode.org ),它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型(译注:rune是int32等价类型)。 -在第八版本的Unicode標準收集了超過120,000個字符,涵蓋超過100多種語言。這些在計算機程序和數據中是如何體現的呢?通用的表示一個Unicode碼點的數據類型是int32,也就是Go語言中rune對應的類型;它的同義詞rune符文正是這個意思。 +在第八版本的Unicode标准收集了超过120,000个字符,涵盖超过100多种语言。这些在计算机程序和数据中是如何体现的呢?通用的表示一个Unicode码点的数据类型是int32,也就是Go语言中rune对应的类型;它的同义词rune符文正是这个意思。 -我們可以將一個符文序列表示爲一個int32序列。這種編碼方式叫UTF-32或UCS-4,每個Unicode碼點都使用同樣的大小32bit來表示。這種方式比較簡單統一,但是它會浪費很多存儲空間,因爲大數據計算機可讀的文本是ASCII字符,本來每個ASCII字符隻需要8bit或1字節就能表示。而且卽使是常用的字符也遠少於65,536個,也就是説用16bit編碼方式就能表達常用字符。但是,還有其它更好的編碼方法嗎? +我们可以将一个符文序列表示为一个int32序列。这种编码方式叫UTF-32或UCS-4,每个Unicode码点都使用同样的大小32bit来表示。这种方式比较简单统一,但是它会浪费很多存储空间,因为大数据计算机可读的文本是ASCII字符,本来每个ASCII字符只需要8bit或1字节就能表示。而且即使是常用的字符也远少于65,536个,也就是说用16bit编码方式就能表达常用字符。但是,还有其它更好的编码方法吗? diff --git a/ch3/ch3-05-3.md b/ch3/ch3-05-3.md index b19cdad3..3031c6b7 100644 --- a/ch3/ch3-05-3.md +++ b/ch3/ch3-05-3.md @@ -1,6 +1,6 @@ ### 3.5.3. UTF-8 -UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼。UTF8編碼由Go語言之父Ken Thompson和Rob Pike共同發明的,現在已經是Unicode的標準。UTF8編碼使用1到4個字節來表示每個Unicode碼點,ASCII部分字符隻使用1個字節,常用字符部分使用2或3個字節表示。每個符號編碼後第一個字節的高端bit位用於表示總共有多少編碼個字節。如果第一個字節的高端bit爲0,則表示對應7bit的ASCII字符,ASCII字符每個字符依然是一個字節,和傳統的ASCII編碼兼容。如果第一個字節的高端bit是110,則説明需要2個字節;後續的每個高端bit都以10開頭。更大的Unicode碼點也是采用類似的策略處理。 +UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码由Go语言之父Ken Thompson和Rob Pike共同发明的,现在已经是Unicode的标准。UTF8编码使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode码点也是采用类似的策略处理。 ``` 0xxxxxxx runes 0-127 (ASCII) @@ -9,11 +9,11 @@ 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码,并且可以自动同步:它可以通过向前回朔最多2个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。 -Go語言的源文件采用UTF8編碼,併且Go語言處理UTF8編碼的文本也很出色。unicode包提供了諸多處理rune字符相關功能的函數(比如區分字母和數組,或者是字母的大寫和小寫轉換等),unicode/utf8包則提供了用於rune字符序列的UTF8編碼和解碼的功能。 +Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数组,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。 -有很多Unicode字符很難直接從鍵盤輸入,併且還有很多字符有着相似的結構;有一些甚至是不可見的字符(譯註:中文和日文就有很多相似但不同的字)。Go語言字符串面值中的Unicode轉義字符讓我們可以通過Unicode碼點輸入特殊的字符。有兩種形式:\uhhhh對應16bit的碼點值,\Uhhhhhhhh對應32bit的碼點值,其中h是一個十六進製數字;一般很少需要使用32bit的形式。每一個對應碼點的UTF8編碼。例如:下面的字母串面值都表示相同的值: +有很多Unicode字符很难直接从键盘输入,并且还有很多字符有着相似的结构;有一些甚至是不可见的字符(译注:中文和日文就有很多相似但不同的字)。Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:\uhhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。例如:下面的字母串面值都表示相同的值: ``` "世界" @@ -22,17 +22,17 @@ Go語言的源文件采用UTF8編碼,併且Go語言處理UTF8編碼的文本 "\U00004e16\U0000754c" ``` -上面三個轉義序列都爲第一個字符串提供替代寫法,但是它們的值都是相同的。 +上面三个转义序列都为第一个字符串提供替代写法,但是它们的值都是相同的。 -Unicode轉義也可以使用在rune字符中。下面三個字符是等價的: +Unicode转义也可以使用在rune字符中。下面三个字符是等价的: ``` '世' '\u4e16' '\U00004e16' ``` -對於小於256碼點值可以寫在一個十六進製轉義字節中,例如'\x41'對應字符'A',但是對於更大的碼點則必須使用\u或\U轉義形式。因此,'\xe4\xb8\x96'併不是一個合法的rune字符,雖然這三個字節對應一個有效的UTF8編碼的碼點。 +对于小于256码点值可以写在一个十六进制转义字节中,例如'\x41'对应字符'A',但是对于更大的码点则必须使用\u或\U转义形式。因此,'\xe4\xb8\x96'并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的码点。 -得益於UTF8編碼優良的設計,諸多字符串操作都不需要解碼操作。我們可以不用解碼直接測試一個字符串是否是另一個字符串的前綴: +得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀: ```Go func HasPrefix(s, prefix string) bool { @@ -40,7 +40,7 @@ func HasPrefix(s, prefix string) bool { } ``` -或者是後綴測試: +或者是后缀测试: ```Go func HasSuffix(s, suffix string) bool { @@ -48,7 +48,7 @@ func HasSuffix(s, suffix string) bool { } ``` -或者是包含子串測試: +或者是包含子串测试: ```Go func Contains(s, substr string) bool { @@ -61,9 +61,9 @@ func Contains(s, substr string) bool { } ``` -對於UTF8編碼後文本的處理和原始的字節處理邏輯是一樣的。但是對應很多其它編碼則併不是這樣的。(上面的函數都來自strings字符串處理包,眞實的代碼包含了一個用哈希技術優化的Contains 實現。) +对于UTF8编码后文本的处理和原始的字节处理逻辑是一样的。但是对应很多其它编码则并不是这样的。(上面的函数都来自strings字符串处理包,真实的代码包含了一个用哈希技术优化的Contains 实现。) -另一方面,如果我們眞的關心每個Unicode字符,我們可以使用其它處理方式。考慮前面的第一個例子中的字符串,它包混合了中西兩種字符。圖3.5展示了它的內存表示形式。字符串包含13個字節,以UTF8形式編碼,但是隻對應9個Unicode字符: +另一方面,如果我们真的关心每个Unicode字符,我们可以使用其它处理方式。考虑前面的第一个例子中的字符串,它包混合了中西两种字符。图3.5展示了它的内存表示形式。字符串包含13个字节,以UTF8形式编码,但是只对应9个Unicode字符: ```Go import "unicode/utf8" @@ -73,7 +73,7 @@ fmt.Println(len(s)) // "13" fmt.Println(utf8.RuneCountInString(s)) // "9" ``` -爲了處理這些眞實的字符,我們需要一個UTF8解碼器。unicode/utf8包提供了該功能,我們可以這樣使用: +为了处理这些真实的字符,我们需要一个UTF8解码器。unicode/utf8包提供了该功能,我们可以这样使用: ```Go for i := 0; i < len(s); { @@ -83,7 +83,7 @@ for i := 0; i < len(s); { } ``` -每一次調用DecodeRuneInString函數都返迴一個r和長度,r對應字符本身,長度對應r采用UTF8編碼後的編碼字節數目。長度可以用於更新第i個字符在字符串中的字節索引位置。但是這種編碼方式是笨拙的,我們需要更簡潔的語法。幸運的是,Go語言的range循環在處理字符串的時候,會自動隱式解碼UTF8字符串。下面的循環運行如圖3.5所示;需要註意的是對於非ASCII,索引更新的步長將超過1個字節。 +每一次调用DecodeRuneInString函数都返回一个r和长度,r对应字符本身,长度对应r采用UTF8编码后的编码字节数目。长度可以用于更新第i个字符在字符串中的字节索引位置。但是这种编码方式是笨拙的,我们需要更简洁的语法。幸运的是,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。下面的循环运行如图3.5所示;需要注意的是对于非ASCII,索引更新的步长将超过1个字节。 ![](../images/ch3-05.png) @@ -93,7 +93,7 @@ for i, r := range "Hello, 世界" { } ``` -我們可以使用一個簡單的循環來統計字符串中字符的數目,像這樣: +我们可以使用一个简单的循环来统计字符串中字符的数目,像这样: ```Go n := 0 @@ -102,7 +102,7 @@ for _, _ = range s { } ``` -像其它形式的循環那樣,我們也可以忽略不需要的變量: +像其它形式的循环那样,我们也可以忽略不需要的变量: ```Go n := 0 @@ -111,15 +111,15 @@ for range s { } ``` -或者我們可以直接調用utf8.RuneCountInString(s)函數。 +或者我们可以直接调用utf8.RuneCountInString(s)函数。 -正如我們前面提到的,文本字符串采用UTF8編碼隻是一種慣例,但是對於循環的眞正字符串併不是一個慣例,這是正確的。如果用於循環的字符串隻是一個普通的二進製數據,或者是含有錯誤編碼的UTF8數據,將會發送什麽呢? +正如我们前面提到的,文本字符串采用UTF8编码只是一种惯例,但是对于循环的真正字符串并不是一个惯例,这是正确的。如果用于循环的字符串只是一个普通的二进制数据,或者是含有错误编码的UTF8数据,将会发送什么呢? -每一個UTF8字符解碼,不管是顯式地調用utf8.DecodeRuneInString解碼或是在range循環中隱式地解碼,如果遇到一個錯誤的UTF8編碼輸入,將生成一個特别的Unicode字符'\uFFFD',在印刷中這個符號通常是一個黑色六角或鑽石形狀,里面包含一個白色的問號"�"。當程序遇到這樣的一個字符,通常是一個危險信號,説明輸入併不是一個完美沒有錯誤的UTF8字符串。 +每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符'\uFFFD',在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号"�"。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。 -UTF8字符串作爲交換格式是非常方便的,但是在程序內部采用rune序列可能更方便,因爲rune大小一致,支持數組索引和方便切割。 +UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,因为rune大小一致,支持数组索引和方便切割。 -string接受到[]rune的類型轉換,可以將一個UTF8編碼的字符串解碼爲Unicode字符序列: +string接受到[]rune的类型转换,可以将一个UTF8编码的字符串解码为Unicode字符序列: ```Go // "program" in Japanese katakana @@ -129,22 +129,22 @@ r := []rune(s) fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]" ``` -(在第一個Printf中的`% x`參數用於在每個十六進製數字前插入一個空格。) +(在第一个Printf中的`% x`参数用于在每个十六进制数字前插入一个空格。) -如果是將一個[]rune類型的Unicode字符slice或數組轉爲string,則對它們進行UTF8編碼: +如果是将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码: ```Go fmt.Println(string(r)) // "プログラム" ``` -將一個整數轉型爲字符串意思是生成以隻包含對應Unicode碼點字符的UTF8字符串: +将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串: ```Go fmt.Println(string(65)) // "A", not "65" fmt.Println(string(0x4eac)) // "京" ``` -如果對應碼點的字符是無效的,則用'\uFFFD'無效字符作爲替換: +如果对应码点的字符是无效的,则用'\uFFFD'无效字符作为替换: ```Go fmt.Println(string(1234567)) // "�" diff --git a/ch3/ch3-05-4.md b/ch3/ch3-05-4.md index 4391a4b6..c5916409 100644 --- a/ch3/ch3-05-4.md +++ b/ch3/ch3-05-4.md @@ -1,14 +1,14 @@ ### 3.5.4. 字符串和Byte切片 -標準庫中有四個包對字符串處理尤爲重要:bytes、strings、strconv和unicode包。strings包提供了許多如字符串的査詢、替換、比較、截斷、拆分和合併等功能。 +标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。 -bytes包也提供了很多類似功能的函數,但是針對和字符串有着相同結構的[]byte類型。因爲字符串是隻讀的,因此逐步構建字符串會導致很多分配和複製。在這種情況下,使用bytes.Buffer類型將會更有效,稍後我們將展示。 +bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效,稍后我们将展示。 -strconv包提供了布爾型、整型數、浮點數和對應字符串的相互轉換,還提供了雙引號轉義相關的轉換。 +strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。 -unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等類似功能,它們用於給字符分類。每個函數有一個單一的rune類型的參數,然後返迴一個布爾值。而像ToUpper和ToLower之類的轉換函數將用於rune字符的大小寫轉換。所有的這些函數都是遵循Unicode標準定義的字母、數字等分類規范。strings包也有類似的函數,它們是ToUpper和ToLower,將原始字符串的每個字符都做相應的轉換,然後返迴新的字符串。 +unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。 -下面例子的basename函數靈感於Unix shell的同名工具。在我們實現的版本中,basename(s)將看起來像是繫統路徑的前綴刪除,同時將看似文件類型的後綴名部分刪除: +下面例子的basename函数灵感于Unix shell的同名工具。在我们实现的版本中,basename(s)将看起来像是系统路径的前缀删除,同时将看似文件类型的后缀名部分删除: ```Go fmt.Println(basename("a/b/c.go")) // "c" @@ -16,7 +16,7 @@ fmt.Println(basename("c.d.go")) // "c.d" fmt.Println(basename("abc")) // "abc" ``` -第一個版本併沒有使用任何庫,全部手工硬編碼實現: +第一个版本并没有使用任何库,全部手工硬编码实现: gopl.io/ch3/basename1 ```Go @@ -41,7 +41,7 @@ func basename(s string) string { } ``` -簡化個版本使用了strings.LastIndex庫函數: +简化个版本使用了strings.LastIndex库函数: gopl.io/ch3/basename2 ```Go @@ -55,9 +55,9 @@ func basename(s string) string { } ``` -path和path/filepath包提供了關於文件路徑名更一般的函數操作。使用斜槓分隔路徑可以在任何操作繫統上工作。斜槓本身不應該用於文件名,但是在其他一些領域可能會用於文件名,例如URL路徑組件。相比之下,path/filepath包則使用操作繫統本身的路徑規則,例如POSIX繫統使用/foo/bar,而Microsoft Windows使用c:\foo\bar等。 +path和path/filepath包提供了关于文件路径名更一般的函数操作。使用斜杠分隔路径可以在任何操作系统上工作。斜杠本身不应该用于文件名,但是在其他一些领域可能会用于文件名,例如URL路径组件。相比之下,path/filepath包则使用操作系统本身的路径规则,例如POSIX系统使用/foo/bar,而Microsoft Windows使用c:\foo\bar等。 -讓我們繼續另一個字符串的例子。函數的功能是將一個表示整值的字符串,每隔三個字符插入一個逗號分隔符,例如“12345”處理後成爲“12,345”。這個版本隻適用於整數類型;支持浮點數類型的支持留作練習。 +让我们继续另一个字符串的例子。函数的功能是将一个表示整值的字符串,每隔三个字符插入一个逗号分隔符,例如“12345”处理后成为“12,345”。这个版本只适用于整数类型;支持浮点数类型的支持留作练习。 gopl.io/ch3/comma ```Go @@ -71,11 +71,11 @@ func comma(s string) string { } ``` -輸入comma函數的參數是一個字符串。如果輸入字符串的長度小於或等於3的話,則不需要插入逗分隔符。否則,comma函數將在最後三個字符前位置將字符串切割爲兩個兩個子串併插入逗號分隔符,然後通過遞歸調用自身來出前面的子串。 +输入comma函数的参数是一个字符串。如果输入字符串的长度小于或等于3的话,则不需要插入逗分隔符。否则,comma函数将在最后三个字符前位置将字符串切割为两个两个子串并插入逗号分隔符,然后通过递归调用自身来出前面的子串。 -一個字符串是包含的隻讀字節數組,一旦創建,是不可變的。相比之下,一個字節slice的元素則可以自由地脩改。 +一个字符串是包含的只读字节数组,一旦创建,是不可变的。相比之下,一个字节slice的元素则可以自由地修改。 -字符串和字節slice之間可以相互轉換: +字符串和字节slice之间可以相互转换: ```Go s := "abc" @@ -83,9 +83,9 @@ b := []byte(s) s2 := string(b) ``` -從概念上講,一個[]byte(s)轉換是分配了一個新的字節數組用於保存字符串數據的拷貝,然後引用這個底層的字節數組。編譯器的優化可以避免在一些場景下分配和複製字符串數據,但總的來説需要確保在變量b被脩改的情況下,原始的s字符串也不會改變。將一個字節slice轉到字符串的string(b)操作則是構造一個字符串拷貝,以確保s2字符串是隻讀的。 +从概念上讲,一个[]byte(s)转换是分配了一个新的字节数组用于保存字符串数据的拷贝,然后引用这个底层的字节数组。编译器的优化可以避免在一些场景下分配和复制字符串数据,但总的来说需要确保在变量b被修改的情况下,原始的s字符串也不会改变。将一个字节slice转到字符串的string(b)操作则是构造一个字符串拷贝,以确保s2字符串是只读的。 -爲了避免轉換中不必要的內存分配,bytes包和strings同時提供了許多實用函數。下面是strings包中的六個函數: +为了避免转换中不必要的内存分配,bytes包和strings同时提供了许多实用函数。下面是strings包中的六个函数: ```Go func Contains(s, substr string) bool @@ -96,7 +96,7 @@ func Index(s, sep string) int func Join(a []string, sep string) string ``` -bytes包中也對應的六個函數: +bytes包中也对应的六个函数: ```Go func Contains(b, subslice []byte) bool @@ -107,9 +107,9 @@ func Index(s, sep []byte) int func Join(s [][]byte, sep []byte) []byte ``` -它們之間唯一的區别是字符串類型參數被替換成了字節slice類型的參數。 +它们之间唯一的区别是字符串类型参数被替换成了字节slice类型的参数。 -bytes包還提供了Buffer類型用於字節slice的緩存。一個Buffer開始是空的,但是隨着string、byte或[]byte等類型數據的寫入可以動態增長,一個bytes.Buffer變量併不需要處理化,因爲零值也是有效的: +bytes包还提供了Buffer类型用于字节slice的缓存。一个Buffer开始是空的,但是随着string、byte或[]byte等类型数据的写入可以动态增长,一个bytes.Buffer变量并不需要处理化,因为零值也是有效的: gopl.io/ch3/printints ```Go @@ -132,12 +132,12 @@ func main() { } ``` -當向bytes.Buffer添加任意字符的UTF8編碼時,最好使用bytes.Buffer的WriteRune方法,但是WriteByte方法對於寫入類似'['和']'等ASCII字符則會更加有效。 +当向bytes.Buffer添加任意字符的UTF8编码时,最好使用bytes.Buffer的WriteRune方法,但是WriteByte方法对于写入类似'['和']'等ASCII字符则会更加有效。 -bytes.Buffer類型有着很多實用的功能,我們在第七章討論接口時將會涉及到,我們將看看如何將它用作一個I/O的輸入和輸出對象,例如當做Fprintf的io.Writer輸出對象,或者當作io.Reader類型的輸入源對象。 +bytes.Buffer类型有着很多实用的功能,我们在第七章讨论接口时将会涉及到,我们将看看如何将它用作一个I/O的输入和输出对象,例如当做Fprintf的io.Writer输出对象,或者当作io.Reader类型的输入源对象。 -**練習 3.10:** 編寫一個非遞歸版本的comma函數,使用bytes.Buffer代替字符串鏈接操作。 +**练习 3.10:** 编写一个非递归版本的comma函数,使用bytes.Buffer代替字符串链接操作。 -**練習 3.11:** 完善comma函數,以支持浮點數處理和一個可選的正負號的處理。 +**练习 3.11:** 完善comma函数,以支持浮点数处理和一个可选的正负号的处理。 -**練習 3.12:** 編寫一個函數,判斷兩個字符串是否是是相互打亂的,也就是説它們有着相同的字符,但是對應不同的順序。 +**练习 3.12:** 编写一个函数,判断两个字符串是否是是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。 diff --git a/ch3/ch3-05-5.md b/ch3/ch3-05-5.md index 545a57f5..c7301a5c 100644 --- a/ch3/ch3-05-5.md +++ b/ch3/ch3-05-5.md @@ -1,8 +1,8 @@ -### 3.5.5. 字符串和數字的轉換 +### 3.5.5. 字符串和数字的转换 -除了字符串、字符、字節之間的轉換,字符串和數值之間的轉換也比較常見。由strconv包提供這類轉換功能。 +除了字符串、字符、字节之间的转换,字符串和数值之间的转换也比较常见。由strconv包提供这类转换功能。 -將一個整數轉爲字符串,一種方法是用fmt.Sprintf返迴一個格式化的字符串;另一個方法是用strconv.Itoa(“整數到ASCII”): +将一个整数转为字符串,一种方法是用fmt.Sprintf返回一个格式化的字符串;另一个方法是用strconv.Itoa(“整数到ASCII”): ```Go x := 123 @@ -10,28 +10,28 @@ y := fmt.Sprintf("%d", x) fmt.Println(y, strconv.Itoa(x)) // "123 123" ``` -FormatInt和FormatUint函數可以用不同的進製來格式化數字: +FormatInt和FormatUint函数可以用不同的进制来格式化数字: ```Go fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011" ``` -fmt.Printf函數的%b、%d、%o和%x等參數提供功能往往比strconv包的Format函數方便很多,特别是在需要包含附加額外信息的時候: +fmt.Printf函数的%b、%d、%o和%x等参数提供功能往往比strconv包的Format函数方便很多,特别是在需要包含附加额外信息的时候: ```Go s := fmt.Sprintf("x=%b", x) // "x=1111011" ``` -如果要將一個字符串解析爲整數,可以使用strconv包的Atoi或ParseInt函數,還有用於解析無符號整數的ParseUint函數: +如果要将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数,还有用于解析无符号整数的ParseUint函数: ```Go x, err := strconv.Atoi("123") // x is an int y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits ``` -ParseInt函數的第三個參數是用於指定整型數的大小;例如16表示int16,0則表示int。在任何情況下,返迴的結果y總是int64類型,你可以通過強製類型轉換將它轉爲更小的整數類型。 +ParseInt函数的第三个参数是用于指定整型数的大小;例如16表示int16,0则表示int。在任何情况下,返回的结果y总是int64类型,你可以通过强制类型转换将它转为更小的整数类型。 -有時候也會使用fmt.Scanf來解析輸入的字符串和數字,特别是當字符串和數字混合在一行的時候,它可以靈活處理不完整或不規則的輸入。 +有时候也会使用fmt.Scanf来解析输入的字符串和数字,特别是当字符串和数字混合在一行的时候,它可以灵活处理不完整或不规则的输入。 diff --git a/ch3/ch3-05.md b/ch3/ch3-05.md index fcec87b9..8d92118b 100644 --- a/ch3/ch3-05.md +++ b/ch3/ch3-05.md @@ -1,8 +1,8 @@ ## 3.5. 字符串 -一個字符串是一個不可改變的字節序列。字符串可以包含任意的數據,包括byte值0,但是通常是用來包含人類可讀的文本。文本字符串通常被解釋爲采用UTF8編碼的Unicode碼點(rune)序列,我們稍後會詳細討論這個問題。 +一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通常是用来包含人类可读的文本。文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列,我们稍后会详细讨论这个问题。 -內置的len函數可以返迴一個字符串中的字節數目(不是rune字符數目),索引操作s[i]返迴第i個字節的字節值,i必須滿足0 ≤ i< len(s)條件約束。 +内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目),索引操作s[i]返回第i个字节的字节值,i必须满足0 ≤ i< len(s)条件约束。 ```Go s := "hello, world" @@ -10,23 +10,23 @@ fmt.Println(len(s)) // "12" fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w') ``` -如果試圖訪問超出字符串索引范圍的字節將會導致panic異常: +如果试图访问超出字符串索引范围的字节将会导致panic异常: ```Go c := s[len(s)] // panic: index out of range ``` -第i個字節併不一定是字符串的第i個字符,因爲對於非ASCII字符的UTF8編碼會要兩個或多個字節。我們先簡單説下字符的工作方式。 +第i个字节并不一定是字符串的第i个字符,因为对于非ASCII字符的UTF8编码会要两个或多个字节。我们先简单说下字符的工作方式。 -子字符串操作s[i:j]基於原始的s字符串的第i個字節開始到第j個字節(併不包含j本身)生成一個新字符串。生成的新字符串將包含j-i個字節。 +子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节(并不包含j本身)生成一个新字符串。生成的新字符串将包含j-i个字节。 ```Go fmt.Println(s[0:5]) // "hello" ``` -同樣,如果索引超出字符串范圍或者j小於i的話將導致panic異常。 +同样,如果索引超出字符串范围或者j小于i的话将导致panic异常。 -不管i還是j都可能被忽略,當它們被忽略時將采用0作爲開始位置,采用len(s)作爲結束的位置。 +不管i还是j都可能被忽略,当它们被忽略时将采用0作为开始位置,采用len(s)作为结束的位置。 ```Go fmt.Println(s[:5]) // "hello" @@ -34,15 +34,15 @@ fmt.Println(s[7:]) // "world" fmt.Println(s[:]) // "hello, world" ``` -其中+操作符將兩個字符串鏈接構造一個新字符串: +其中+操作符将两个字符串链接构造一个新字符串: ```Go fmt.Println("goodbye" + s[5:]) // "goodbye, world" ``` -字符串可以用==和<進行比較;比較通過逐個字節比較完成的,因此比較的結果是字符串自然編碼的順序。 +字符串可以用==和<进行比较;比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺序。 -字符串的值是不可變的:一個字符串包含的字節序列永遠不會被改變,當然我們也可以給一個字符串變量分配一個新字符串值。可以像下面這樣將一個字符串追加到另一個字符串: +字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变量分配一个新字符串值。可以像下面这样将一个字符串追加到另一个字符串: ```Go s := "left foot" @@ -50,20 +50,20 @@ t := s s += ", right foot" ``` -這併不會導致原始的字符串值被改變,但是變量s將因爲+=語句持有一個新的字符串值,但是t依然是包含原先的字符串值。 +这并不会导致原始的字符串值被改变,但是变量s将因为+=语句持有一个新的字符串值,但是t依然是包含原先的字符串值。 ```Go fmt.Println(s) // "left foot, right foot" fmt.Println(t) // "left foot" ``` -因爲字符串是不可脩改的,因此嚐試脩改字符串內部數據的操作也是被禁止的: +因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的: ```Go s[0] = 'L' // compile error: cannot assign to s[0] ``` -不變性意味如果兩個字符串共享相同的底層數據的話也是安全的,這使得複製任何長度的字符串代價是低廉的。同樣,一個字符串s和對應的子字符串切片s[7:]的操作也可以安全地共享相同的內存,因此字符串切片操作代價也是低廉的。在這兩種情況下都沒有必要分配新的內存。 圖3.4演示了一個字符串和兩個字串共享相同的底層數據。 +不变性意味如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。在这两种情况下都没有必要分配新的内存。 图3.4演示了一个字符串和两个字串共享相同的底层数据。 {% include "./ch3-05-1.md" %} diff --git a/ch3/ch3-06-1.md b/ch3/ch3-06-1.md index e3bbc65c..085d71c4 100644 --- a/ch3/ch3-06-1.md +++ b/ch3/ch3-06-1.md @@ -1,8 +1,8 @@ ### 3.6.1. iota 常量生成器 -常量聲明可以使用iota常量生成器初始化,它用於生成一組以相似規則初始化的常量,但是不用每行都寫一遍初始化表達式。在一個const聲明語句中,在第一個聲明的常量所在的行,iota將會被置爲0,然後在每一個有常量聲明的行加一。 +常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。 -下面是來自time包的例子,它首先定義了一個Weekday命名類型,然後爲一週的每天定義了一個常量,從週日0開始。在其它編程語言中,這種類型一般被稱爲枚舉類型。 +下面是来自time包的例子,它首先定义了一个Weekday命名类型,然后为一周的每天定义了一个常量,从周日0开始。在其它编程语言中,这种类型一般被称为枚举类型。 ```Go type Weekday int @@ -18,9 +18,9 @@ const ( ) ``` -週一將對應0,週一爲1,如此等等。 +周一将对应0,周一为1,如此等等。 -我們也可以在複雜的常量表達式中使用iota,下面是來自net包的例子,用於給一個無符號整數的最低5bit的每個bit指定一個名字: +我们也可以在复杂的常量表达式中使用iota,下面是来自net包的例子,用于给一个无符号整数的最低5bit的每个bit指定一个名字: ```Go type Flags uint @@ -34,7 +34,7 @@ const ( ) ``` -隨着iota的遞增,每個常量對應表達式1 << iota,是連續的2的冪,分别對應一個bit位置。使用這些常量可以用於測試、設置或清除對應的bit位的值: +随着iota的递增,每个常量对应表达式1 << iota,是连续的2的幂,分别对应一个bit位置。使用这些常量可以用于测试、设置或清除对应的bit位的值: gopl.io/ch3/netflag ```Go @@ -54,7 +54,7 @@ unc main() { } ``` -下面是一個更複雜的例子,每個常量都是1024的冪: +下面是一个更复杂的例子,每个常量都是1024的幂: ```Go const ( @@ -70,6 +70,6 @@ const ( ) ``` -不過iota常量生成規則也有其局限性。例如,它併不能用於産生1000的冪(KB、MB等),因爲Go語言併沒有計算冪的運算符。 +不过iota常量生成规则也有其局限性。例如,它并不能用于产生1000的幂(KB、MB等),因为Go语言并没有计算幂的运算符。 -**練習 3.13:** 編寫KB、MB的常量聲明,然後擴展到YB。 +**练习 3.13:** 编写KB、MB的常量声明,然后扩展到YB。 diff --git a/ch3/ch3-06-2.md b/ch3/ch3-06-2.md index 72d3099a..2034f253 100644 --- a/ch3/ch3-06-2.md +++ b/ch3/ch3-06-2.md @@ -1,14 +1,14 @@ -### 3.6.2. 無類型常量 +### 3.6.2. 无类型常量 -Go語言的常量有個不同尋常之處。雖然一個常量可以有任意有一個確定的基礎類型,例如int或float64,或者是類似time.Duration這樣命名的基礎類型,但是許多常量併沒有一個明確的基礎類型。編譯器爲這些沒有明確的基礎類型的數字常量提供比基礎類型更高精度的算術運算;你可以認爲至少有256bit的運算精度。這里有六種未明確類型的常量類型,分别是無類型的布爾型、無類型的整數、無類型的字符、無類型的浮點數、無類型的複數、無類型的字符串。 +Go语言的常量有个不同寻常之处。虽然一个常量可以有任意有一个确定的基础类型,例如int或float64,或者是类似time.Duration这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。 -通過延遲明確常量的具體類型,無類型的常量不僅可以提供更高的運算精度,而且可以直接用於更多的表達式而不需要顯式的類型轉換。例如,例子中的ZiB和YiB的值已經超出任何Go語言中整數類型能表達的范圍,但是它們依然是合法的常量,而且可以像下面常量表達式依然有效(譯註:YiB/ZiB是在編譯期計算出來的,併且結果常量是1024,是Go語言int變量能有效表示的): +通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。例如,例子中的ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围,但是它们依然是合法的常量,而且可以像下面常量表达式依然有效(译注:YiB/ZiB是在编译期计算出来的,并且结果常量是1024,是Go语言int变量能有效表示的): ```Go fmt.Println(YiB/ZiB) // "1024" ``` -另一個例子,math.Pi無類型的浮點數常量,可以直接用於任意需要浮點數或複數的地方: +另一个例子,math.Pi无类型的浮点数常量,可以直接用于任意需要浮点数或复数的地方: ```Go var x float32 = math.Pi @@ -16,7 +16,7 @@ var y float64 = math.Pi var z complex128 = math.Pi ``` -如果math.Pi被確定爲特定類型,比如float64,那麽結果精度可能會不一樣,同時對於需要float32或complex128類型值的地方則會強製需要一個明確的類型轉換: +如果math.Pi被确定为特定类型,比如float64,那么结果精度可能会不一样,同时对于需要float32或complex128类型值的地方则会强制需要一个明确的类型转换: ```Go const Pi64 float64 = math.Pi @@ -26,9 +26,9 @@ var y float64 = Pi64 var z complex128 = complex128(Pi64) ``` -對於常量面值,不同的寫法可能會對應不同的類型。例如0、0.0、0i和'\u0000'雖然有着相同的常量值,但是它們分别對應無類型的整數、無類型的浮點數、無類型的複數和無類型的字符等不同的常量類型。同樣,true和false也是無類型的布爾類型,字符串面值常量是無類型的字符串類型。 +对于常量面值,不同的写法可能会对应不同的类型。例如0、0.0、0i和'\u0000'虽然有着相同的常量值,但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样,true和false也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。 -前面説過除法運算符/會根據操作數的類型生成對應類型的結果。因此,不同寫法的常量除法表達式可能對應不同的結果: +前面说过除法运算符/会根据操作数的类型生成对应类型的结果。因此,不同写法的常量除法表达式可能对应不同的结果: ```Go var f float64 = 212 @@ -37,7 +37,7 @@ fmt.Println(5 / 9 * (f - 32)) // "0"; 5/9 is an untyped integer, 0 fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float ``` -隻有常量可以是無類型的。當一個無類型的常量被賦值給一個變量的時候,就像上面的第一行語句,或者是像其餘三個語句中右邊表達式中含有明確類型的值,無類型的常量將會被隱式轉換爲對應的類型,如果轉換合法的話。 +只有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的时候,就像上面的第一行语句,或者是像其余三个语句中右边表达式中含有明确类型的值,无类型的常量将会被隐式转换为对应的类型,如果转换合法的话。 ```Go var f float64 = 3 + 0i // untyped complex -> float64 @@ -46,7 +46,7 @@ f = 1e123 // untyped floating-point -> float64 f = 'a' // untyped rune -> float64 ``` -上面的語句相當於: +上面的语句相当于: ```Go var f float64 = float64(3 + 0i) @@ -55,7 +55,7 @@ f = float64(1e123) f = float64('a') ``` -無論是隱式或顯式轉換,將一種類型轉換爲另一種類型都要求目標可以表示原始值。對於浮點數和複數,可能會有舍入處理: +无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值。对于浮点数和复数,可能会有舍入处理: ```Go const ( @@ -69,7 +69,7 @@ const ( ) ``` -對於一個沒有顯式類型的變量聲明語法(包括短變量聲明語法),無類型的常量會被隱式轉爲默認的變量類型,就像下面的例子: +对于一个没有显式类型的变量声明语法(包括短变量声明语法),无类型的常量会被隐式转为默认的变量类型,就像下面的例子: ```Go i := 0 // untyped integer; implicit int(0) @@ -78,16 +78,16 @@ f := 0.0 // untyped floating-point; implicit float64(0.0) c := 0i // untyped complex; implicit complex128(0i) ``` -註意默認類型是規則的:無類型的整數常量默認轉換爲int,對應不確定的內存大小,但是浮點數和複數常量則默認轉換爲float64和complex128。Go語言本身併沒有不確定內存大小的浮點數和複數類型,而且如果不知道浮點數類型的話將很難寫出正確的數值算法。 +注意默认类型是规则的:无类型的整数常量默认转换为int,对应不确定的内存大小,但是浮点数和复数常量则默认转换为float64和complex128。Go语言本身并没有不确定内存大小的浮点数和复数类型,而且如果不知道浮点数类型的话将很难写出正确的数值算法。 -如果要給變量一個不同的類型,我們必須顯式地將無類型的常量轉化爲所需的類型,或給聲明的變量指定明確的類型,像下面例子這樣: +如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指定明确的类型,像下面例子这样: ```Go var i = int8(0) var i int8 = 0 ``` -當嚐試將這些無類型的常量轉爲一個接口值時(見第7章),這些默認類型將顯得尤爲重要,因爲要靠它們明確接口對應的動態類型。 +当尝试将这些无类型的常量转为一个接口值时(见第7章),这些默认类型将显得尤为重要,因为要靠它们明确接口对应的动态类型。 ```Go fmt.Printf("%T\n", 0) // "int" @@ -96,7 +96,7 @@ fmt.Printf("%T\n", 0i) // "complex128" fmt.Printf("%T\n", '\000') // "int32" (rune) ``` -現在我們已經講述了Go語言中全部的基礎數據類型。下一步將演示如何用基礎數據類型組合成數組或結構體等複雜數據類型,然後構建用於解決實際編程問題的數據結構,這將是第四章的討論主題。 +现在我们已经讲述了Go语言中全部的基础数据类型。下一步将演示如何用基础数据类型组合成数组或结构体等复杂数据类型,然后构建用于解决实际编程问题的数据结构,这将是第四章的讨论主题。 diff --git a/ch3/ch3-06.md b/ch3/ch3-06.md index c3683eeb..6a9d14c9 100644 --- a/ch3/ch3-06.md +++ b/ch3/ch3-06.md @@ -1,14 +1,14 @@ ## 3.6. 常量 -常量表達式的值在編譯期計算,而不是在運行期。每種常量的潛在類型都是基礎類型:boolean、string或數字。 +常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数字。 -一個常量的聲明語句定義了常量的名字,和變量的聲明語法類似,常量的值不可脩改,這樣可以防止在運行期被意外或惡意的脩改。例如,常量比變量更適合用於表達像π之類的數學常數,因爲它們的值不會發生變化: +一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在运行期被意外或恶意的修改。例如,常量比变量更适合用于表达像π之类的数学常数,因为它们的值不会发生变化: ```Go const pi = 3.14159 // approximately; math.Pi is a better approximation ``` -和變量聲明一樣,可以批量聲明多個常量;這比較適合聲明一組相關的常量: +和变量声明一样,可以批量声明多个常量;这比较适合声明一组相关的常量: ```Go const ( @@ -17,11 +17,11 @@ const ( ) ``` -所有常量的運算都可以在編譯期完成,這樣可以減少運行時的工作,也方便其他編譯優化。當操作數是常量時,一些運行時的錯誤也可以在編譯時被發現,例如整數除零、字符串索引越界、任何導致無效浮點數的操作等。 +所有常量的运算都可以在编译期完成,这样可以减少运行时的工作,也方便其他编译优化。当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。 -常量間的所有算術運算、邏輯運算和比較運算的結果也是常量,對常量的類型轉換操作或以下函數調用都是返迴常量結果:len、cap、real、imag、complex和unsafe.Sizeof(§13.1)。 +常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof(§13.1)。 -因爲它們的值是在編譯期就確定的,因此常量可以是構成類型的一部分,例如用於指定數組類型的長度: +因为它们的值是在编译期就确定的,因此常量可以是构成类型的一部分,例如用于指定数组类型的长度: ```Go const IPv4Len = 4 @@ -33,7 +33,7 @@ func parseIPv4(s string) IP { } ``` -一個常量的聲明也可以包含一個類型和一個值,但是如果沒有顯式指明類型,那麽將從右邊的表達式推斷類型。在下面的代碼中,time.Duration是一個命名類型,底層類型是int64,time.Minute是對應類型的常量。下面聲明的兩個常量都是time.Duration類型,可以通過%T參數打印類型信息: +一个常量的声明也可以包含一个类型和一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。在下面的代码中,time.Duration是一个命名类型,底层类型是int64,time.Minute是对应类型的常量。下面声明的两个常量都是time.Duration类型,可以通过%T参数打印类型信息: ```Go const noDelay time.Duration = 0 @@ -43,7 +43,7 @@ fmt.Printf("%T %[1]v\n", timeout) // "time.Duration 5m0s" fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s" ``` -如果是批量聲明的常量,除了第一個外其它的常量右邊的初始化表達式都可以省略,如果省略初始化表達式則表示使用前面常量的初始化表達式寫法,對應的常量類型也一樣的。例如: +如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的。例如: ```Go const ( @@ -56,7 +56,7 @@ const ( fmt.Println(a, b, c, d) // "1 1 2 2" ``` -如果隻是簡單地複製右邊的常量表達式,其實併沒有太實用的價值。但是它可以帶來其它的特性,那就是iota常量生成器語法。 +如果只是简单地复制右边的常量表达式,其实并没有太实用的价值。但是它可以带来其它的特性,那就是iota常量生成器语法。 {% include "./ch3-06-1.md" %} diff --git a/ch3/ch3.md b/ch3/ch3.md index e5795282..9320c29f 100644 --- a/ch3/ch3.md +++ b/ch3/ch3.md @@ -1,5 +1,5 @@ -# 第3章 基礎數據類型 +# 第3章 基础数据类型 -雖然從底層而言,所有的數據都是由比特組成,但計算機一般操作的是固定大小的數,如整數、浮點數、比特數組、內存地址等。進一步將這些數組織在一起,就可表達更多的對象,例如數據包、像素點、詩歌,甚至其他任何對象。Go語言提供了豐富的數據組織形式,這依賴於Go語言內置的數據類型。這些內置的數據類型,兼顧了硬件的特性和表達複雜數據結構的便捷性。 +虽然从底层而言,所有的数据都是由比特组成,但计算机一般操作的是固定大小的数,如整数、浮点数、比特数组、内存地址等。进一步将这些数组织在一起,就可表达更多的对象,例如数据包、像素点、诗歌,甚至其他任何对象。Go语言提供了丰富的数据组织形式,这依赖于Go语言内置的数据类型。这些内置的数据类型,兼顾了硬件的特性和表达复杂数据结构的便捷性。 -Go語言將數據類型分爲四類:基礎類型、複合類型、引用類型和接口類型。本章介紹基礎類型,包括:數字、字符串和布爾型。複合數據類型——數組(§4.1)和結構體(§4.2)——是通過組合簡單類型,來表達更加複雜的數據結構。引用類型包括指針(§2.3.2)、切片(§4.2))字典(§4.3)、函數(§5)、通道(§8),雖然數據種類很多,但它們都是對程序中一個變量或狀態的間接引用。這意味着對任一引用類型數據的脩改都會影響所有該引用的拷貝。我們將在第7章介紹接口類型。 +Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。本章介绍基础类型,包括:数字、字符串和布尔型。复合数据类型——数组(§4.1)和结构体(§4.2)——是通过组合简单类型,来表达更加复杂的数据结构。引用类型包括指针(§2.3.2)、切片(§4.2))字典(§4.3)、函数(§5)、通道(§8),虽然数据种类很多,但它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。我们将在第7章介绍接口类型。 diff --git a/ch4/ch4-01.md b/ch4/ch4-01.md index ca77cfa6..f12c765e 100644 --- a/ch4/ch4-01.md +++ b/ch4/ch4-01.md @@ -1,8 +1,8 @@ -## 4.1. 數組 +## 4.1. 数组 -數組是一個由固定長度的特定類型元素組成的序列,一個數組可以由零個或多個元素組成。因爲數組的長度是固定的,因此在Go語言中很少直接使用數組。和數組對應的類型是Slice(切片),它是可以增長和收縮動態序列,slice功能也更靈活,但是要理解slice工作原理的話需要先理解數組。 +数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组。 -數組的每個元素可以通過索引下標來訪問,索引下標的范圍是從0開始到數組長度減1的位置。內置的len函數將返迴數組中元素的個數。 +数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。 ```Go var a [3]int // array of 3 integers @@ -20,7 +20,7 @@ for _, v := range a { } ``` -默認情況下,數組的每個元素都被初始化爲元素類型對應的零值,對於數字類型來説就是0。我們也可以使用數組字面值語法用一組值來初始化數組: +默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。我们也可以使用数组字面值语法用一组值来初始化数组: ```Go var q [3]int = [3]int{1, 2, 3} @@ -28,30 +28,30 @@ var r [3]int = [3]int{1, 2} fmt.Println(r[2]) // "0" ``` -在數組字面值中,如果在數組的長度位置出現的是“...”省略號,則表示數組的長度是根據初始化值的個數來計算。因此,上面q數組的定義可以簡化爲 +在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。因此,上面q数组的定义可以简化为 ```Go q := [...]int{1, 2, 3} fmt.Printf("%T\n", q) // "[3]int" ``` -數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必須是常量表達式,因爲數組的長度需要在編譯階段確定。 +数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。 ```Go q := [3]int{1, 2, 3} q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int ``` -我們將會發現,數組、slice、map和結構體字面值的寫法都很相似。上面的形式是直接提供順序初始化值序列,但是也可以指定一個索引和對應值列表的方式初始化,就像下面這樣: +我们将会发现,数组、slice、map和结构体字面值的写法都很相似。上面的形式是直接提供顺序初始化值序列,但是也可以指定一个索引和对应值列表的方式初始化,就像下面这样: ```Go type Currency int const ( USD Currency = iota // 美元 - EUR // 歐元 - GBP // 英鎊 - RMB // 人民幣 + EUR // 欧元 + GBP // 英镑 + RMB // 人民币 ) symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"} @@ -59,15 +59,15 @@ symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"} fmt.Println(RMB, symbol[RMB]) // "3 ¥" ``` -在這種形式的數組字面值形式中,初始化索引的順序是無關緊要的,而且沒用到的索引可以省略,和前面提到的規則一樣,未指定初始值的元素將用零值初始化。例如, +在这种形式的数组字面值形式中,初始化索引的顺序是无关紧要的,而且没用到的索引可以省略,和前面提到的规则一样,未指定初始值的元素将用零值初始化。例如, ```Go r := [...]int{99: -1} ``` -定義了一個含有100個元素的數組r,最後一個元素被初始化爲-1,其它元素都是用0初始化。 +定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其它元素都是用0初始化。 -如果一個數組的元素類型是可以相互比較的,那麽數組類型也是可以相互比較的,這時候我們可以直接通過==比較運算符來比較兩個數組,隻有當兩個數組的所有元素都是相等的時候數組才是相等的。不相等比較運算符!=遵循同樣的規則。 +如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则。 ```Go a := [2]int{1, 2} @@ -78,7 +78,7 @@ d := [3]int{1, 2} fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int ``` -作爲一個眞實的例子,crypto/sha256包的Sum256函數對一個任意的字節slice類型的數據生成一個對應的消息摘要。消息摘要有256bit大小,因此對應[32]byte數組類型。如果兩個消息摘要是相同的,那麽可以認爲兩個消息本身也是相同(譯註:理論上有HASH碼碰撞的情況,但是實際應用可以基本忽略);如果消息摘要不同,那麽消息本身必然也是不同的。下面的例子用SHA256算法分别生成“x”和“X”兩個信息的摘要: +作为一个真实的例子,crypto/sha256包的Sum256函数对一个任意的字节slice类型的数据生成一个对应的消息摘要。消息摘要有256bit大小,因此对应[32]byte数组类型。如果两个消息摘要是相同的,那么可以认为两个消息本身也是相同(译注:理论上有HASH码碰撞的情况,但是实际应用可以基本忽略);如果消息摘要不同,那么消息本身必然也是不同的。下面的例子用SHA256算法分别生成“x”和“X”两个信息的摘要: gopl.io/ch4/sha256 ```Go @@ -96,11 +96,11 @@ func main() { } ``` -上面例子中,兩個消息雖然隻有一個字符的差異,但是生成的消息摘要則幾乎有一半的bit位是不相同的。需要註意Printf函數的%x副詞參數,它用於指定以十六進製的格式打印數組或slice全部的元素,%t副詞參數是用於打印布爾型數據,%T副詞參數是用於顯示一個值對應的數據類型。 +上面例子中,两个消息虽然只有一个字符的差异,但是生成的消息摘要则几乎有一半的bit位是不相同的。需要注意Printf函数的%x副词参数,它用于指定以十六进制的格式打印数组或slice全部的元素,%t副词参数是用于打印布尔型数据,%T副词参数是用于显示一个值对应的数据类型。 -當調用一個函數的時候,函數的每個調用參數將會被賦值給函數內部的參數變量,所以函數參數變量接收的是一個複製的副本,併不是原始調用的變量。因爲函數參數傳遞的機製導致傳遞大的數組類型將是低效的,併且對數組參數的任何的脩改都是發生在複製的數組上,併不能直接脩改調用時原始的數組變量。在這個方面,Go語言對待數組的方式和其它很多編程語言不同,其它編程語言可能會隱式地將數組作爲引用或指針對象傳入被調用的函數。 +当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,并不是原始调用的变量。因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。在这个方面,Go语言对待数组的方式和其它很多编程语言不同,其它编程语言可能会隐式地将数组作为引用或指针对象传入被调用的函数。 -當然,我們可以顯式地傳入一個數組指針,那樣的話函數通過指針對數組的任何脩改都可以直接反饋到調用者。下面的函數用於給[32]byte類型的數組清零: +当然,我们可以显式地传入一个数组指针,那样的话函数通过指针对数组的任何修改都可以直接反馈到调用者。下面的函数用于给[32]byte类型的数组清零: ```Go func zero(ptr *[32]byte) { @@ -110,7 +110,7 @@ func zero(ptr *[32]byte) { } ``` -其實數組字面值[32]byte{}就可以生成一個32字節的數組。而且每個數組的元素都是零值初始化,也就是0。因此,我們可以將上面的zero函數寫的更簡潔一點: +其实数组字面值[32]byte{}就可以生成一个32字节的数组。而且每个数组的元素都是零值初始化,也就是0。因此,我们可以将上面的zero函数写的更简洁一点: ```Go func zero(ptr *[32]byte) { @@ -118,8 +118,8 @@ func zero(ptr *[32]byte) { } ``` -雖然通過指針來傳遞數組參數是高效的,而且也允許在函數內部脩改數組的值,但是數組依然是殭化的類型,因爲數組的類型包含了殭化的長度信息。上面的zero函數併不能接收指向[16]byte類型數組的指針,而且也沒有任何添加或刪除數組元素的方法。由於這些原因,除了像SHA256這類需要處理特定大小數組的特例外,數組依然很少用作函數參數;相反,我們一般使用slice來替代數組。 +虽然通过指针来传递数组参数是高效的,而且也允许在函数内部修改数组的值,但是数组依然是僵化的类型,因为数组的类型包含了僵化的长度信息。上面的zero函数并不能接收指向[16]byte类型数组的指针,而且也没有任何添加或删除数组元素的方法。由于这些原因,除了像SHA256这类需要处理特定大小数组的特例外,数组依然很少用作函数参数;相反,我们一般使用slice来替代数组。 -**練習 4.1:** 編寫一個函數,計算兩個SHA256哈希碼中不同bit的數目。(參考2.6.2節的PopCount函數。) +**练习 4.1:** 编写一个函数,计算两个SHA256哈希码中不同bit的数目。(参考2.6.2节的PopCount函数。) -**練習 4.2:** 編寫一個程序,默認打印標準輸入的以SHA256哈希碼,也可以通過命令行標準參數選擇SHA384或SHA512哈希算法。 +**练习 4.2:** 编写一个程序,默认打印标准输入的以SHA256哈希码,也可以通过命令行标准参数选择SHA384或SHA512哈希算法。 diff --git a/ch4/ch4-02-1.md b/ch4/ch4-02-1.md index 8e8224fa..c9024494 100644 --- a/ch4/ch4-02-1.md +++ b/ch4/ch4-02-1.md @@ -1,6 +1,6 @@ -### 4.2.1. append函數 +### 4.2.1. append函数 -內置的append函數用於向slice追加元素: +内置的append函数用于向slice追加元素: ```Go var runes []rune @@ -10,9 +10,9 @@ for _, r := range "Hello, 世界" { fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']" ``` -在循環中使用append函數構建一個由九個rune字符構成的slice,當然對應這個特殊的問題我們可以通過Go語言內置的[]rune("Hello, 世界")轉換操作完成。 +在循环中使用append函数构建一个由九个rune字符构成的slice,当然对应这个特殊的问题我们可以通过Go语言内置的[]rune("Hello, 世界")转换操作完成。 -append函數對於理解slice底層是如何工作的非常重要,所以讓我們仔細査看究竟是發生了什麽。下面是第一個版本的appendInt函數,專門用於處理[]int類型的slice: +append函数对于理解slice底层是如何工作的非常重要,所以让我们仔细查看究竟是发生了什么。下面是第一个版本的appendInt函数,专门用于处理[]int类型的slice: gopl.io/ch4/append ```Go @@ -37,13 +37,13 @@ func appendInt(x []int, y int) []int { } ``` -每次調用appendInt函數,必須先檢測slice底層數組是否有足夠的容量來保存新添加的元素。如果有足夠空間的話,直接擴展slice(依然在原有的底層數組之上),將新添加的y元素複製到新擴展的空間,併返迴slice。因此,輸入的x和輸出的z共享相同的底層數組。 +每次调用appendInt函数,必须先检测slice底层数组是否有足够的容量来保存新添加的元素。如果有足够空间的话,直接扩展slice(依然在原有的底层数组之上),将新添加的y元素复制到新扩展的空间,并返回slice。因此,输入的x和输出的z共享相同的底层数组。 -如果沒有足夠的增長空間的話,appendInt函數則會先分配一個足夠大的slice用於保存新的結果,先將輸入的x複製到新的空間,然後添加y元素。結果z和輸入的x引用的將是不同的底層數組。 +如果没有足够的增长空间的话,appendInt函数则会先分配一个足够大的slice用于保存新的结果,先将输入的x复制到新的空间,然后添加y元素。结果z和输入的x引用的将是不同的底层数组。 -雖然通過循環複製元素更直接,不過內置的copy函數可以方便地將一個slice複製另一個相同類型的slice。copy函數的第一個參數是要複製的目標slice,第二個參數是源slice,目標和源的位置順序和`dst = src`賦值語句是一致的。兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。copy函數將返迴成功複製的元素的個數(我們這里沒有用到),等於兩個slice中較小的長度,所以我們不用擔心覆蓋會超出目標slice的范圍。 +虽然通过循环复制元素更直接,不过内置的copy函数可以方便地将一个slice复制另一个相同类型的slice。copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,目标和源的位置顺序和`dst = src`赋值语句是一致的。两个slice可以共享同一个底层数组,甚至有重叠也没有问题。copy函数将返回成功复制的元素的个数(我们这里没有用到),等于两个slice中较小的长度,所以我们不用担心覆盖会超出目标slice的范围。 -爲了提高內存使用效率,新分配的數組一般略大於保存x和y所需要的最低大小。通過在每次擴展數組時直接將長度翻倍從而避免了多次內存分配,也確保了添加單個元素操的平均時間是一個常數時間。這個程序演示了效果: +为了提高内存使用效率,新分配的数组一般略大于保存x和y所需要的最低大小。通过在每次扩展数组时直接将长度翻倍从而避免了多次内存分配,也确保了添加单个元素操的平均时间是一个常数时间。这个程序演示了效果: ```Go func main() { @@ -56,7 +56,7 @@ func main() { } ``` -每一次容量的變化都會導致重新分配內存和copy操作: +每一次容量的变化都会导致重新分配内存和copy操作: ``` 0 cap=1 [0] @@ -71,21 +71,21 @@ func main() { 9 cap=16 [0 1 2 3 4 5 6 7 8 9] ``` -讓我們仔細査看i=3次的迭代。當時x包含了[0 1 2]三個元素,但是容量是4,因此可以簡單將新的元素添加到末尾,不需要新的內存分配。然後新的y的長度和容量都是4,併且和x引用着相同的底層數組,如圖4.2所示。 +让我们仔细查看i=3次的迭代。当时x包含了[0 1 2]三个元素,但是容量是4,因此可以简单将新的元素添加到末尾,不需要新的内存分配。然后新的y的长度和容量都是4,并且和x引用着相同的底层数组,如图4.2所示。 ![](../images/ch4-02.png) -在下一次迭代時i=4,現在沒有新的空餘的空間了,因此appendInt函數分配一個容量爲8的底層數組,將x的4個元素[0 1 2 3]複製到新空間的開頭,然後添加新的元素i,新元素的值是4。新的y的長度是5,容量是8;後面有3個空閒的位置,三次迭代都不需要分配新的空間。當前迭代中,y和x是對應不同底層數組的view。這次操作如圖4.3所示。 +在下一次迭代时i=4,现在没有新的空余的空间了,因此appendInt函数分配一个容量为8的底层数组,将x的4个元素[0 1 2 3]复制到新空间的开头,然后添加新的元素i,新元素的值是4。新的y的长度是5,容量是8;后面有3个空闲的位置,三次迭代都不需要分配新的空间。当前迭代中,y和x是对应不同底层数组的view。这次操作如图4.3所示。 ![](../images/ch4-03.png) -內置的append函數可能使用比appendInt更複雜的內存擴展策略。因此,通常我們併不知道append調用是否導致了內存的重新分配,因此我們也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。同樣,我們不能確認在原先的slice上的操作是否會影響到新的slice。因此,通常是將append返迴的結果直接賦值給輸入的slice變量: +内置的append函数可能使用比appendInt更复杂的内存扩展策略。因此,通常我们并不知道append调用是否导致了内存的重新分配,因此我们也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。同样,我们不能确认在原先的slice上的操作是否会影响到新的slice。因此,通常是将append返回的结果直接赋值给输入的slice变量: ```Go runes = append(runes, r) ``` -更新slice變量不僅對調用append函數是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正確地使用slice,需要記住盡管底層數組的元素是間接訪問的,但是slice對應結構體本身的指針、長度和容量部分是直接訪問的。要更新這些信息需要像上面例子那樣一個顯式的賦值操作。從這個角度看,slice併不是一個純粹的引用類型,它實際上是一個類似下面結構體的聚合類型: +更新slice变量不仅对调用append函数是必要的,实际上对应任何可能导致长度、容量或底层数组变化的操作都是必要的。要正确地使用slice,需要记住尽管底层数组的元素是间接访问的,但是slice对应结构体本身的指针、长度和容量部分是直接访问的。要更新这些信息需要像上面例子那样一个显式的赋值操作。从这个角度看,slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型: ```Go type IntSlice struct { @@ -94,7 +94,7 @@ type IntSlice struct { } ``` -我們的appendInt函數每次隻能向slice追加一個元素,但是內置的append函數則可以追加多個元素,甚至追加一個slice。 +我们的appendInt函数每次只能向slice追加一个元素,但是内置的append函数则可以追加多个元素,甚至追加一个slice。 ```Go var x []int @@ -105,7 +105,7 @@ x = append(x, x...) // append the slice x fmt.Println(x) // "[1 2 3 4 5 6 1 2 3 4 5 6]" ``` -通過下面的小脩改,我們可以可以達到append函數類似的功能。其中在appendInt函數參數中的最後的“...”省略號表示接收變長的參數爲slice。我們將在5.7節詳細解釋這個特性。 +通过下面的小修改,我们可以可以达到append函数类似的功能。其中在appendInt函数参数中的最后的“...”省略号表示接收变长的参数为slice。我们将在5.7节详细解释这个特性。 ```Go func appendInt(x []int, y ...int) []int { @@ -117,4 +117,4 @@ func appendInt(x []int, y ...int) []int { } ``` -爲了避免重複,和前面相同的代碼併沒有顯示。 +为了避免重复,和前面相同的代码并没有显示。 diff --git a/ch4/ch4-02-2.md b/ch4/ch4-02-2.md index 10bb63a3..28276e39 100644 --- a/ch4/ch4-02-2.md +++ b/ch4/ch4-02-2.md @@ -1,6 +1,6 @@ -### 4.2.2. Slice內存技巧 +### 4.2.2. Slice内存技巧 -讓我們看看更多的例子,比如镟轉slice、反轉slice或在slice原有內存空間脩改元素。給定一個字符串列表,下面的nonempty函數將在原有slice內存空間之上返迴不包含空字符串的列表: +让我们看看更多的例子,比如旋转slice、反转slice或在slice原有内存空间修改元素。给定一个字符串列表,下面的nonempty函数将在原有slice内存空间之上返回不包含空字符串的列表: gopl.io/ch4/nonempty ```Go @@ -23,7 +23,7 @@ func nonempty(strings []string) []string { } ``` -比較微妙的地方是,輸入的slice和輸出的slice共享一個底層數組。這可以避免分配另一個數組,不過原來的數據將可能會被覆蓋,正如下面兩個打印語句看到的那樣: +比较微妙的地方是,输入的slice和输出的slice共享一个底层数组。这可以避免分配另一个数组,不过原来的数据将可能会被覆盖,正如下面两个打印语句看到的那样: ```Go data := []string{"one", "", "three"} @@ -31,9 +31,9 @@ fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]` fmt.Printf("%q\n", data) // `["one" "three" "three"]` ``` -因此我們通常會這樣使用nonempty函數:`data = nonempty(data)`。 +因此我们通常会这样使用nonempty函数:`data = nonempty(data)`。 -nonempty函數也可以使用append函數實現: +nonempty函数也可以使用append函数实现: ```Go func nonempty2(strings []string) []string { @@ -47,27 +47,27 @@ func nonempty2(strings []string) []string { } ``` -無論如何實現,以這種方式重用一個slice一般都要求最多爲每個輸入值産生一個輸出值,事實上很多這類算法都是用來過濾或合併序列中相鄰的元素。這種slice用法是比較複雜的技巧,雖然使用到了slice的一些技巧,但是對於某些場合是比較清晰和有效的。 +无论如何实现,以这种方式重用一个slice一般都要求最多为每个输入值产生一个输出值,事实上很多这类算法都是用来过滤或合并序列中相邻的元素。这种slice用法是比较复杂的技巧,虽然使用到了slice的一些技巧,但是对于某些场合是比较清晰和有效的。 -一個slice可以用來模擬一個stack。最初給定的空slice對應一個空的stack,然後可以使用append函數將新的值壓入stack: +一个slice可以用来模拟一个stack。最初给定的空slice对应一个空的stack,然后可以使用append函数将新的值压入stack: ```Go stack = append(stack, v) // push v ``` -stack的頂部位置對應slice的最後一個元素: +stack的顶部位置对应slice的最后一个元素: ```Go top := stack[len(stack)-1] // top of stack ``` -通過收縮stack可以彈出棧頂的元素 +通过收缩stack可以弹出栈顶的元素 ```Go stack = stack[:len(stack)-1] // pop ``` -要刪除slice中間的某個元素併保存原有的元素順序,可以通過內置的copy函數將後面的子slice向前依次移動一位完成: +要删除slice中间的某个元素并保存原有的元素顺序,可以通过内置的copy函数将后面的子slice向前依次移动一位完成: ```Go func remove(slice []int, i int) []int { @@ -81,7 +81,7 @@ func main() { } ``` -如果刪除元素後不用保持原來順序的話,我們可以簡單的用最後一個元素覆蓋被刪除的元素: +如果删除元素后不用保持原来顺序的话,我们可以简单的用最后一个元素覆盖被删除的元素: ```Go func remove(slice []int, i int) []int { @@ -95,12 +95,12 @@ func main() { } ``` -**練習 4.3:** 重寫reverse函數,使用數組指針代替slice。 +**练习 4.3:** 重写reverse函数,使用数组指针代替slice。 -**練習 4.4:** 編寫一個rotate函數,通過一次循環完成镟轉。 +**练习 4.4:** 编写一个rotate函数,通过一次循环完成旋转。 -**練習 4.5:** 寫一個函數在原地完成消除[]string中相鄰重複的字符串的操作。 +**练习 4.5:** 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。 -**練習 4.6:** 編寫一個函數,原地將一個UTF-8編碼的[]byte類型的slice中相鄰的空格(參考unicode.IsSpace)替換成一個空格返迴 +**练习 4.6:** 编写一个函数,原地将一个UTF-8编码的[]byte类型的slice中相邻的空格(参考unicode.IsSpace)替换成一个空格返回 -**練習 4.7:** 脩改reverse函數用於原地反轉UTF-8編碼的[]byte。是否可以不用分配額外的內存? +**练习 4.7:** 修改reverse函数用于原地反转UTF-8编码的[]byte。是否可以不用分配额外的内存? diff --git a/ch4/ch4-02.md b/ch4/ch4-02.md index a005e3cd..ac6cf28f 100644 --- a/ch4/ch4-02.md +++ b/ch4/ch4-02.md @@ -1,18 +1,18 @@ ## 4.2. Slice -Slice(切片)代表變長的序列,序列中每個元素都有相同的類型。一個slice類型一般寫作[]T,其中T代表slice中元素的類型;slice的語法和數組很像,隻是沒有固定長度而已。 +Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。 -數組和slice之間有着緊密的聯繫。一個slice是一個輕量級的數據結構,提供了訪問數組子序列(或者全部)元素的功能,而且slice的底層確實引用一個數組對象。一個slice由三個部分構成:指針、長度和容量。指針指向第一個slice元素對應的底層數組元素的地址,要註意的是slice的第一個元素併不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分别返迴slice的長度和容量。 +数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。 -多個slice之間可以共享底層的數據,併且引用的數組部分區間可能重疊。圖4.1顯示了表示一年中每個月份名字的字符串數組,還有重疊引用了該數組的兩個slice。數組這樣定義 +多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。图4.1显示了表示一年中每个月份名字的字符串数组,还有重叠引用了该数组的两个slice。数组这样定义 ```Go months := [...]string{1: "January", /* ... */, 12: "December"} ``` -因此一月份是months[1],十二月份是months[12]。通常,數組的第一個元素從索引0開始,但是月份一般是從1開始的,因此我們聲明數組時直接跳過第0個元素,第0個元素會被自動初始化爲空字符串。 +因此一月份是months[1],十二月份是months[12]。通常,数组的第一个元素从索引0开始,但是月份一般是从1开始的,因此我们声明数组时直接跳过第0个元素,第0个元素会被自动初始化为空字符串。 -slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用於創建一個新的slice,引用s的從第i個元素開始到第j-1個元素的子序列。新的slice將隻有j-i個元素。如果i位置的索引被省略的話將使用0代替,如果j位置的索引被省略的話將使用len(s)代替。因此,months[1:13]切片操作將引用全部有效的月份,和months[1:]操作等價;months[:]切片操作則是引用整個數組。讓我們分别定義表示第二季度和北方夏天月份的slice,它們有重疊部分: +slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。新的slice将只有j-i个元素。如果i位置的索引被省略的话将使用0代替,如果j位置的索引被省略的话将使用len(s)代替。因此,months[1:13]切片操作将引用全部有效的月份,和months[1:]操作等价;months[:]切片操作则是引用整个数组。让我们分别定义表示第二季度和北方夏天月份的slice,它们有重叠部分: ![](../images/ch4-01.png) @@ -23,7 +23,7 @@ fmt.Println(Q2) // ["April" "May" "June"] fmt.Println(summer) // ["June" "July" "August"] ``` -兩個slice都包含了六月份,下面的代碼是一個包含相同月份的測試(性能較低): +两个slice都包含了六月份,下面的代码是一个包含相同月份的测试(性能较低): ```Go for _, s := range summer { @@ -35,7 +35,7 @@ for _, s := range summer { } ``` -如果切片操作超出cap(s)的上限將導致一個panic異常,但是超出len(s)則是意味着擴展了slice,因爲新slice的長度會變大: +如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大: ```Go fmt.Println(summer[:20]) // panic: out of range @@ -44,9 +44,9 @@ endlessSummer := summer[:5] // extend a slice (within capacity) fmt.Println(endlessSummer) // "[June July August September October]" ``` -另外,字符串的切片操作和[]byte字節類型切片的切片操作是類似的。它們都寫作x[m:n],併且都是返迴一個原始字節繫列的子序列,底層都是共享之前的底層數組,因此切片操作對應常量時間複雜度。x[m:n]切片操作對於字符串則生成一個新字符串,如果x是[]byte的話則生成一個新的[]byte。 +另外,字符串的切片操作和[]byte字节类型切片的切片操作是类似的。它们都写作x[m:n],并且都是返回一个原始字节系列的子序列,底层都是共享之前的底层数组,因此切片操作对应常量时间复杂度。x[m:n]切片操作对于字符串则生成一个新字符串,如果x是[]byte的话则生成一个新的[]byte。 -因爲slice值包含指向第一個slice元素的指針,因此向函數傳遞slice將允許在函數內部脩改底層數組的元素。換句話説,複製一個slice隻是對底層的數組創建了一個新的slice别名(§2.3.2)。下面的reverse函數在原內存空間將[]int類型的slice反轉,而且它可以用於任意長度的slice。 +因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名(§2.3.2)。下面的reverse函数在原内存空间将[]int类型的slice反转,而且它可以用于任意长度的slice。 gopl.io/ch4/rev ```Go @@ -58,7 +58,7 @@ func reverse(s []int) { } ``` -這里我們反轉數組的應用: +这里我们反转数组的应用: ```Go a := [...]int{0, 1, 2, 3, 4, 5} @@ -66,7 +66,7 @@ reverse(a[:]) fmt.Println(a) // "[5 4 3 2 1 0]" ``` -一種將slice元素循環向左镟轉n個元素的方法是三次調用reverse反轉函數,第一次是反轉開頭的n個元素,然後是反轉剩下的元素,最後是反轉整個slice的元素。(如果是向右循環镟轉,則將第三個函數調用移到第一個調用位置就可以了。) +一种将slice元素循环向左旋转n个元素的方法是三次调用reverse反转函数,第一次是反转开头的n个元素,然后是反转剩下的元素,最后是反转整个slice的元素。(如果是向右循环旋转,则将第三个函数调用移到第一个调用位置就可以了。) ```Go s := []int{0, 1, 2, 3, 4, 5} @@ -77,9 +77,9 @@ reverse(s) fmt.Println(s) // "[2 3 4 5 0 1]" ``` -要註意的是slice類型的變量s和數組類型的變量a的初始化語法的差異。slice和數組的字面值語法很類似,它們都是用花括弧包含一繫列的初始化元素,但是對於slice併沒有指明序列的長度。這會隱式地創建一個合適大小的數組,然後slice的指針指向底層的數組。就像數組字面值一樣,slice的字面值也可以按順序指定初始化值序列,或者是通過索引和元素值指定,或者的兩種風格的混合語法初始化。 +要注意的是slice类型的变量s和数组类型的变量a的初始化语法的差异。slice和数组的字面值语法很类似,它们都是用花括弧包含一系列的初始化元素,但是对于slice并没有指明序列的长度。这会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组。就像数组字面值一样,slice的字面值也可以按顺序指定初始化值序列,或者是通过索引和元素值指定,或者的两种风格的混合语法初始化。 -和數組不同的是,slice之間不能比較,因此我們不能使用==操作符來判斷兩個slice是否含有全部相等元素。不過標準庫提供了高度優化的bytes.Equal函數來判斷兩個字節型slice是否相等([]byte),但是對於其他類型的slice,我們必須自己展開每個元素進行比較: +和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较: ```Go func equal(x, y []string) bool { @@ -95,17 +95,17 @@ func equal(x, y []string) bool { } ``` -上面關於兩個slice的深度相等測試,運行的時間併不比支持==操作的數組或字符串更多,但是爲何slice不直接支持比較運算符呢?這方面有兩個原因。第一個原因,一個slice的元素是間接引用的,一個slice甚至可以包含自身。雖然有很多辦法處理這種情形,但是沒有一個是簡單有效的。 +上面关于两个slice的深度相等测试,运行的时间并不比支持==操作的数组或字符串更多,但是为何slice不直接支持比较运算符呢?这方面有两个原因。第一个原因,一个slice的元素是间接引用的,一个slice甚至可以包含自身。虽然有很多办法处理这种情形,但是没有一个是简单有效的。 -第二個原因,因爲slice的元素是間接引用的,一個固定值的slice在不同的時間可能包含不同的元素,因爲底層數組的元素可能會被脩改。併且Go語言中map等哈希表之類的數據結構的key隻做簡單的淺拷貝,它要求在整個聲明週期中相等的key必須對相同的元素。對於像指針或chan之類的引用類型,==相等測試可以判斷兩個是否是引用相同的對象。一個針對slice的淺相等測試的==操作符可能是有一定用處的,也能臨時解決map類型的key問題,但是slice和數組不同的相等測試行爲會讓人睏惑。因此,安全的做法是直接禁止slice之間的比較操作。 +第二个原因,因为slice的元素是间接引用的,一个固定值的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改。并且Go语言中map等哈希表之类的数据结构的key只做简单的浅拷贝,它要求在整个声明周期中相等的key必须对相同的元素。对于像指针或chan之类的引用类型,==相等测试可以判断两个是否是引用相同的对象。一个针对slice的浅相等测试的==操作符可能是有一定用处的,也能临时解决map类型的key问题,但是slice和数组不同的相等测试行为会让人困惑。因此,安全的做法是直接禁止slice之间的比较操作。 -slice唯一合法的比較操作是和nil比較,例如: +slice唯一合法的比较操作是和nil比较,例如: ```Go if summer == nil { /* ... */ } ``` -一個零值的slice等於nil。一個nil值的slice併沒有底層數組。一個nil值的slice的長度和容量都是0,但是也有非nil值的slice的長度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。與任意類型的nil值一樣,我們可以用[]int(nil)類型轉換表達式來生成一個對應類型slice的nil值。 +一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0,但是也有非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。与任意类型的nil值一样,我们可以用[]int(nil)类型转换表达式来生成一个对应类型slice的nil值。 ```Go var s []int // len(s) == 0, s == nil @@ -114,16 +114,16 @@ s = []int(nil) // len(s) == 0, s == nil s = []int{} // len(s) == 0, s != nil ``` -如果你需要測試一個slice是否是空的,使用len(s) == 0來判斷,而不應該用s == nil來判斷。除了和nil相等比較外,一個nil值的slice的行爲和其它任意0長度的slice一樣;例如reverse(nil)也是安全的。除了文檔已經明確説明的地方,所有的Go語言函數應該以相同的方式對待nil值的slice和0長度的slice。 +如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。除了和nil相等比较外,一个nil值的slice的行为和其它任意0长度的slice一样;例如reverse(nil)也是安全的。除了文档已经明确说明的地方,所有的Go语言函数应该以相同的方式对待nil值的slice和0长度的slice。 -內置的make函數創建一個指定元素類型、長度和容量的slice。容量部分可以省略,在這種情況下,容量將等於長度。 +内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。 ```Go make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len] ``` -在底層,make創建了一個匿名的數組變量,然後返迴一個slice;隻有通過返迴的slice才能引用底層匿名的數組變量。在第一種語句中,slice是整個數組的view。在第二個語句中,slice隻引用了底層數組的前len個元素,但是容量將包含整個的數組。額外的元素是留給未來的增長用的。 +在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的。 {% include "./ch4-02-1.md" %} diff --git a/ch4/ch4-03.md b/ch4/ch4-03.md index fb84b68a..e4812ac4 100644 --- a/ch4/ch4-03.md +++ b/ch4/ch4-03.md @@ -1,16 +1,16 @@ ## 4.3. Map -哈希表是一種巧妙併且實用的數據結構。它是一個無序的key/value對的集合,其中所有的key都是不同的,然後通過給定的key可以在常數時間複雜度內檢索、更新或刪除對應的value。 +哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。 -在Go語言中,一個map就是一個哈希表的引用,map類型可以寫爲map[K]V,其中K和V分别對應key和value。map中所有的key都有相同的類型,所有的value也有着相同的類型,但是key和value之間可以是不同的數據類型。其中K對應的key必須是支持==比較運算符的數據類型,所以map可以通過測試key是否相等來判斷是否已經存在。雖然浮點數類型也是支持相等運算符比較的,但是將浮點數用做key類型則是一個壞的想法,正如第三章提到的,最壞的情況是可能出現的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限製。 +在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用做key类型则是一个坏的想法,正如第三章提到的,最坏的情况是可能出现的NaN和任何浮点数都不相等。对于V对应的value数据类型则没有任何的限制。 -內置的make函數可以創建一個map: +内置的make函数可以创建一个map: ```Go ages := make(map[string]int) // mapping from strings to ints ``` -我們也可以用map字面值的語法創建map,同時還可以指定一些最初的key/value: +我们也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value: ```Go ages := map[string]int{ @@ -19,7 +19,7 @@ ages := map[string]int{ } ``` -這相當於 +这相当于 ```Go ages := make(map[string]int) @@ -27,48 +27,48 @@ ages["alice"] = 31 ages["charlie"] = 34 ``` -因此,另一種創建空的map的表達式是`map[string]int{}`。 +因此,另一种创建空的map的表达式是`map[string]int{}`。 -Map中的元素通過key對應的下標語法訪問: +Map中的元素通过key对应的下标语法访问: ```Go ages["alice"] = 32 fmt.Println(ages["alice"]) // "32" ``` -使用內置的delete函數可以刪除元素: +使用内置的delete函数可以删除元素: ```Go delete(ages, "alice") // remove element ages["alice"] ``` -所有這些操作是安全的,卽使這些元素不在map中也沒有關繫;如果一個査找失敗將返迴value類型對應的零值,例如,卽使map中不存在“bob”下面的代碼也可以正常工作,因爲ages["bob"]失敗時將返迴0。 +所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值,例如,即使map中不存在“bob”下面的代码也可以正常工作,因为ages["bob"]失败时将返回0。 ```Go ages["bob"] = ages["bob"] + 1 // happy birthday! ``` -而且`x += y`和`x++`等簡短賦值語法也可以用在map上,所以上面的代碼可以改寫成 +而且`x += y`和`x++`等简短赋值语法也可以用在map上,所以上面的代码可以改写成 ```Go ages["bob"] += 1 ``` -更簡單的寫法 +更简单的写法 ```Go ages["bob"]++ ``` -但是map中的元素併不是一個變量,因此我們不能對map的元素進行取址操作: +但是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作: ```Go _ = &ages["bob"] // compile error: cannot take address of map element ``` -禁止對map元素取址的原因是map可能隨着元素數量的增長而重新分配更大的內存空間,從而可能導致之前的地址無效。 +禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。 -要想遍歷map中全部的key/value對的話,可以使用range風格的for循環實現,和之前的slice遍歷語法類似。下面的迭代語句將在每次迭代時設置name和age變量,它們對應下一個鍵/值對: +要想遍历map中全部的key/value对的话,可以使用range风格的for循环实现,和之前的slice遍历语法类似。下面的迭代语句将在每次迭代时设置name和age变量,它们对应下一个键/值对: ```Go for name, age := range ages { @@ -76,7 +76,7 @@ for name, age := range ages { } ``` -Map的迭代順序是不確定的,併且不同的哈希函數實現可能導致不同的遍歷順序。在實踐中,遍歷的順序是隨機的,每一次遍歷的順序都不相同。這是故意的,每次都使用隨機的遍歷順序可以強製要求程序不會依賴具體的哈希函數實現。如果要按順序遍歷key/value對,我們必須顯式地對key進行排序,可以使用sort包的Strings函數對字符串slice進行排序。下面是常見的處理方式: +Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。这是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,我们必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。下面是常见的处理方式: ```Go import "sort" @@ -91,15 +91,15 @@ for _, name := range names { } ``` -因爲我們一開始就知道names的最終大小,因此給slice分配一個合適的大小將會更有效。下面的代碼創建了一個空的slice,但是slice的容量剛好可以放下map中全部的key: +因为我们一开始就知道names的最终大小,因此给slice分配一个合适的大小将会更有效。下面的代码创建了一个空的slice,但是slice的容量刚好可以放下map中全部的key: ```Go names := make([]string, 0, len(ages)) ``` -在上面的第一個range循環中,我們隻關心map中的key,所以我們忽略了第二個循環變量。在第二個循環中,我們隻關心names中的名字,所以我們使用“_”空白標識符來忽略第一個循環變量,也就是迭代slice時的索引。 +在上面的第一个range循环中,我们只关心map中的key,所以我们忽略了第二个循环变量。在第二个循环中,我们只关心names中的名字,所以我们使用“_”空白标识符来忽略第一个循环变量,也就是迭代slice时的索引。 -map類型的零值是nil,也就是沒有引用任何哈希表。 +map类型的零值是nil,也就是没有引用任何哈希表。 ```Go var ages map[string]int @@ -107,30 +107,30 @@ fmt.Println(ages == nil) // "true" fmt.Println(len(ages) == 0) // "true" ``` -map上的大部分操作,包括査找、刪除、len和range循環都可以安全工作在nil值的map上,它們的行爲和一個空的map類似。但是向一個nil值的map存入元素將導致一個panic異常: +map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常: ```Go ages["carol"] = 21 // panic: assignment to entry in nil map ``` -在向map存數據前必須先創建map。 +在向map存数据前必须先创建map。 -通過key作爲索引下標來訪問map將産生一個value。如果key在map中是存在的,那麽將得到與key對應的value;如果key不存在,那麽將得到value對應類型的零值,正如我們前面看到的ages["bob"]那樣。這個規則很實用,但是有時候可能需要知道對應的元素是否眞的是在map之中。例如,如果元素類型是一個數字,你可以需要區分一個已經存在的0,和不存在而返迴零值的0,可以像下面這樣測試: +通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值,正如我们前面看到的ages["bob"]那样。这个规则很实用,但是有时候可能需要知道对应的元素是否真的是在map之中。例如,如果元素类型是一个数字,你可以需要区分一个已经存在的0,和不存在而返回零值的0,可以像下面这样测试: ```Go age, ok := ages["bob"] if !ok { /* "bob" is not a key in this map; age == 0. */ } ``` -你會經常看到將這兩個結合起來使用,像這樣: +你会经常看到将这两个结合起来使用,像这样: ```Go if age, ok := ages["bob"]; !ok { /* ... */ } ``` -在這種場景下,map的下標語法將産生兩個值;第二個是一個布爾值,用於報告元素是否眞的存在。布爾變量一般命名爲ok,特别適合馬上用於if條件判斷部分。 +在这种场景下,map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为ok,特别适合马上用于if条件判断部分。 -和slice一樣,map之間也不能進行相等比較;唯一的例外是和nil進行比較。要判斷兩個map是否包含相同的key和value,我們必須通過一個循環實現: +和slice一样,map之间也不能进行相等比较;唯一的例外是和nil进行比较。要判断两个map是否包含相同的key和value,我们必须通过一个循环实现: ```Go func equal(x, y map[string]int) bool { @@ -146,14 +146,14 @@ func equal(x, y map[string]int) bool { } ``` -要註意我們是如何用!ok來區分元素缺失和元素不同的。我們不能簡單地用xv != y[k]判斷,那樣會導致在判斷下面兩個map時産生錯誤的結果: +要注意我们是如何用!ok来区分元素缺失和元素不同的。我们不能简单地用xv != y[k]判断,那样会导致在判断下面两个map时产生错误的结果: ```Go // True if equal is written incorrectly. equal(map[string]int{"A": 0}, map[string]int{"B": 42}) ``` -Go語言中併沒有提供一個set類型,但是map中的key也是不相同的,可以用map實現類似set的功能。爲了説明這一點,下面的dedup程序讀取多行輸入,但是隻打印第一次出現的行。(它是1.3節中出現的dup程序的變體。)dedup程序通過map來表示所有的輸入行所對應的set集合,以確保已經在集合存在的行不會被重複打印。 +Go语言中并没有提供一个set类型,但是map中的key也是不相同的,可以用map实现类似set的功能。为了说明这一点,下面的dedup程序读取多行输入,但是只打印第一次出现的行。(它是1.3节中出现的dup程序的变体。)dedup程序通过map来表示所有的输入行所对应的set集合,以确保已经在集合存在的行不会被重复打印。 gopl.io/ch4/dedup ```Go @@ -175,11 +175,11 @@ func main() { } ``` -Go程序員將這種忽略value的map當作一個字符串集合,併非所有`map[string]bool`類型value都是無關緊要的;有一些則可能會同時包含true和false的值。 +Go程序员将这种忽略value的map当作一个字符串集合,并非所有`map[string]bool`类型value都是无关紧要的;有一些则可能会同时包含true和false的值。 -有時候我們需要一個map或set的key是slice類型,但是map的key必須是可比較的類型,但是slice併不滿足這個條件。不過,我們可以通過兩個步驟繞過這個限製。第一步,定義一個輔助函數k,將slice轉爲map對應的string類型的key,確保隻有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map,在每次對map操作時先用k輔助函數將slice轉化爲string類型。 +有时候我们需要一个map或set的key是slice类型,但是map的key必须是可比较的类型,但是slice并不满足这个条件。不过,我们可以通过两个步骤绕过这个限制。第一步,定义一个辅助函数k,将slice转为map对应的string类型的key,确保只有x和y相等时k(x) == k(y)才成立。然后创建一个key为string类型的map,在每次对map操作时先用k辅助函数将slice转化为string类型。 -下面的例子演示了如何使用map來記録提交相同的字符串列表的次數。它使用了fmt.Sprintf函數將字符串列表轉換爲一個字符串以用於map的key,通過%q參數忠實地記録每個字符串元素的信息: +下面的例子演示了如何使用map来记录提交相同的字符串列表的次数。它使用了fmt.Sprintf函数将字符串列表转换为一个字符串以用于map的key,通过%q参数忠实地记录每个字符串元素的信息: ```Go var m = make(map[string]int) @@ -190,9 +190,9 @@ func Add(list []string) { m[k(list)]++ } func Count(list []string) int { return m[k(list)] } ``` -使用同樣的技術可以處理任何不可比較的key類型,而不僅僅是slice類型。這種技術對於想使用自定義key比較函數的時候也很有用,例如在比較字符串的時候忽略大小寫。同時,輔助函數k(x)也不一定是字符串類型,它可以返迴任何可比較的類型,例如整數、數組或結構體等。 +使用同样的技术可以处理任何不可比较的key类型,而不仅仅是slice类型。这种技术对于想使用自定义key比较函数的时候也很有用,例如在比较字符串的时候忽略大小写。同时,辅助函数k(x)也不一定是字符串类型,它可以返回任何可比较的类型,例如整数、数组或结构体等。 -這是map的另一個例子,下面的程序用於統計輸入中每個Unicode碼點出現的次數。雖然Unicode全部碼點的數量鉅大,但是出現在特定文檔中的字符種類併沒有多少,使用map可以用比較自然的方式來跟蹤那些出現過字符的次數。 +这是map的另一个例子,下面的程序用于统计输入中每个Unicode码点出现的次数。虽然Unicode全部码点的数量巨大,但是出现在特定文档中的字符种类并没有多少,使用map可以用比较自然的方式来跟踪那些出现过字符的次数。 gopl.io/ch4/charcount ```Go @@ -246,15 +246,15 @@ func main() { } ``` -ReadRune方法執行UTF-8解碼併返迴三個值:解碼的rune字符的值,字符UTF-8編碼後的長度,和一個錯誤值。我們可預期的錯誤值隻有對應文件結尾的io.EOF。如果輸入的是無效的UTF-8編碼的字符,返迴的將是unicode.ReplacementChar表示無效字符,併且編碼長度是1。 +ReadRune方法执行UTF-8解码并返回三个值:解码的rune字符的值,字符UTF-8编码后的长度,和一个错误值。我们可预期的错误值只有对应文件结尾的io.EOF。如果输入的是无效的UTF-8编码的字符,返回的将是unicode.ReplacementChar表示无效字符,并且编码长度是1。 -charcount程序同時打印不同UTF-8編碼長度的字符數目。對此,map併不是一個合適的數據結構;因爲UTF-8編碼的長度總是從1到utf8.UTFMax(最大是4個字節),使用數組將更有效。 +charcount程序同时打印不同UTF-8编码长度的字符数目。对此,map并不是一个合适的数据结构;因为UTF-8编码的长度总是从1到utf8.UTFMax(最大是4个字节),使用数组将更有效。 -作爲一個實驗,我們用charcount程序對英文版原稿的字符進行了統計。雖然大部分是英語,但是也有一些非ASCII字符。下面是排名前10的非ASCII字符: +作为一个实验,我们用charcount程序对英文版原稿的字符进行了统计。虽然大部分是英语,但是也有一些非ASCII字符。下面是排名前10的非ASCII字符: ![](../images/ch4-xx-01.png) -下面是不同UTF-8編碼長度的字符的數目: +下面是不同UTF-8编码长度的字符的数目: ``` len count @@ -264,7 +264,7 @@ len count 4 0 ``` -Map的value類型也可以是一個聚合類型,比如是一個map或slice。在下面的代碼中,圖graph的key類型是一個字符串,value類型map[string]bool代表一個字符串集合。從概念上將,graph將一個字符串類型的key映射到一組相關的字符串集合,它們指向新的graph的key。 +Map的value类型也可以是一个聚合类型,比如是一个map或slice。在下面的代码中,图graph的key类型是一个字符串,value类型map[string]bool代表一个字符串集合。从概念上将,graph将一个字符串类型的key映射到一组相关的字符串集合,它们指向新的graph的key。 gopl.io/ch4/graph ```Go @@ -284,8 +284,8 @@ func hasEdge(from, to string) bool { } ``` -其中addEdge函數惰性初始化map是一個慣用方式,也就是説在每個值首次作爲key時才初始化。addEdge函數顯示了如何讓map的零值也能正常工作;卽使from到to的邊不存在,graph[from][to]依然可以返迴一個有意義的結果。 +其中addEdge函数惰性初始化map是一个惯用方式,也就是说在每个值首次作为key时才初始化。addEdge函数显示了如何让map的零值也能正常工作;即使from到to的边不存在,graph[from][to]依然可以返回一个有意义的结果。 -**練習 4.8:** 脩改charcount程序,使用unicode.IsLetter等相關的函數,統計字母、數字等Unicode中不同的字符類别。 +**练习 4.8:** 修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别。 -**練習 4.9:** 編寫一個程序wordfreq程序,報告輸入文本中每個單詞出現的頻率。在第一次調用Scan前先調用input.Split(bufio.ScanWords)函數,這樣可以按單詞而不是按行輸入。 +**练习 4.9:** 编写一个程序wordfreq程序,报告输入文本中每个单词出现的频率。在第一次调用Scan前先调用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行输入。 diff --git a/ch4/ch4-04-1.md b/ch4/ch4-04-1.md index 235ca56a..035eb4e9 100644 --- a/ch4/ch4-04-1.md +++ b/ch4/ch4-04-1.md @@ -1,6 +1,6 @@ -### 4.4.1. 結構體面值 +### 4.4.1. 结构体面值 -結構體值也可以用結構體面值表示,結構體面值可以指定每個成員的值。 +结构体值也可以用结构体面值表示,结构体面值可以指定每个成员的值。 ```Go type Point struct{ X, Y int } @@ -8,17 +8,17 @@ type Point struct{ X, Y int } p := Point{1, 2} ``` -這里有兩種形式的結構體面值語法,上面的是第一種寫法,要求以結構體成員定義的順序爲每個結構體成員指定一個面值。它要求寫代碼和讀代碼的人要記住結構體的每個成員的類型和順序,不過結構體成員有細微的調整就可能導致上述代碼不能編譯。因此,上述的語法一般隻在定義結構體的包內部使用,或者是在較小的結構體中使用,這些結構體的成員排列比較規則,比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。 +这里有两种形式的结构体面值语法,上面的是第一种写法,要求以结构体成员定义的顺序为每个结构体成员指定一个面值。它要求写代码和读代码的人要记住结构体的每个成员的类型和顺序,不过结构体成员有细微的调整就可能导致上述代码不能编译。因此,上述的语法一般只在定义结构体的包内部使用,或者是在较小的结构体中使用,这些结构体的成员排列比较规则,比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。 -其實更常用的是第二種寫法,以成員名字和相應的值來初始化,可以包含部分或全部的成員,如1.4節的Lissajous程序的寫法: +其实更常用的是第二种写法,以成员名字和相应的值来初始化,可以包含部分或全部的成员,如1.4节的Lissajous程序的写法: ```Go anim := gif.GIF{LoopCount: nframes} ``` -在這種形式的結構體面值寫法中,如果成員被忽略的話將默認用零值。因爲,提供了成員的名字,所有成員出現的順序併不重要。 +在这种形式的结构体面值写法中,如果成员被忽略的话将默认用零值。因为,提供了成员的名字,所有成员出现的顺序并不重要。 -兩種不同形式的寫法不能混合使用。而且,你不能企圖在外部包中用第一種順序賦值的技巧來偷偷地初始化結構體中未導出的成員。 +两种不同形式的写法不能混合使用。而且,你不能企图在外部包中用第一种顺序赋值的技巧来偷偷地初始化结构体中未导出的成员。 ```Go package p @@ -30,9 +30,9 @@ var _ = p.T{a: 1, b: 2} // compile error: can't reference a, b var _ = p.T{1, 2} // compile error: can't reference a, b ``` -雖然上面最後一行代碼的編譯錯誤信息中併沒有顯式提到未導出的成員,但是這樣企圖隱式使用未導出成員的行爲也是不允許的。 +虽然上面最后一行代码的编译错误信息中并没有显式提到未导出的成员,但是这样企图隐式使用未导出成员的行为也是不允许的。 -結構體可以作爲函數的參數和返迴值。例如,這個Scale函數將Point類型的值縮放後返迴: +结构体可以作为函数的参数和返回值。例如,这个Scale函数将Point类型的值缩放后返回: ```Go func Scale(p Point, factor int) Point { @@ -42,7 +42,7 @@ func Scale(p Point, factor int) Point { fmt.Println(Scale(Point{1, 2}, 5)) // "{5 10}" ``` -如果考慮效率的話,較大的結構體通常會用指針的方式傳入和返迴, +如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回, ```Go func Bonus(e *Employee, percent int) int { @@ -50,7 +50,7 @@ func Bonus(e *Employee, percent int) int { } ``` -如果要在函數內部脩改結構體成員的話,用指針傳入是必須的;因爲在Go語言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。 +如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。 ```Go func AwardAnnualRaise(e *Employee) { @@ -58,17 +58,17 @@ func AwardAnnualRaise(e *Employee) { } ``` -因爲結構體通常通過指針處理,可以用下面的寫法來創建併初始化一個結構體變量,併返迴結構體的地址: +因为结构体通常通过指针处理,可以用下面的写法来创建并初始化一个结构体变量,并返回结构体的地址: ```Go pp := &Point{1, 2} ``` -它是下面的語句是等價的 +它是下面的语句是等价的 ```Go pp := new(Point) *pp = Point{1, 2} ``` -不過&Point{1, 2}寫法可以直接在表達式中使用,比如一個函數調用。 +不过&Point{1, 2}写法可以直接在表达式中使用,比如一个函数调用。 diff --git a/ch4/ch4-04-2.md b/ch4/ch4-04-2.md index 0a16e244..972a9b39 100644 --- a/ch4/ch4-04-2.md +++ b/ch4/ch4-04-2.md @@ -1,6 +1,6 @@ -### 4.4.2. 結構體比較 +### 4.4.2. 结构体比较 -如果結構體的全部成員都是可以比較的,那麽結構體也是可以比較的,那樣的話兩個結構體將可以使用==或!=運算符進行比較。相等比較運算符==將比較兩個結構體的每個成員,因此下面兩個比較的表達式是等價的: +如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较。相等比较运算符==将比较两个结构体的每个成员,因此下面两个比较的表达式是等价的: ```Go type Point struct{ X, Y int } @@ -11,7 +11,7 @@ fmt.Println(p.X == q.X && p.Y == q.Y) // "false" fmt.Println(p == q) // "false" ``` -可比較的結構體類型和其他可比較的類型一樣,可以用於map的key類型。 +可比较的结构体类型和其他可比较的类型一样,可以用于map的key类型。 ```Go type address struct { diff --git a/ch4/ch4-04-3.md b/ch4/ch4-04-3.md index c5803786..b79337e1 100644 --- a/ch4/ch4-04-3.md +++ b/ch4/ch4-04-3.md @@ -1,8 +1,8 @@ -### 4.4.3. 結構體嵌入和匿名成員 +### 4.4.3. 结构体嵌入和匿名成员 -在本節中,我們將看到如何使用Go語言提供的不同尋常的結構體嵌入機製讓一個命名的結構體包含另一個結構體類型的匿名成員,這樣就可以通過簡單的點運算符x.f來訪問匿名成員鏈中嵌套的x.d.e.f成員。 +在本节中,我们将看到如何使用Go语言提供的不同寻常的结构体嵌入机制让一个命名的结构体包含另一个结构体类型的匿名成员,这样就可以通过简单的点运算符x.f来访问匿名成员链中嵌套的x.d.e.f成员。 -考慮一個二維的繪圖程序,提供了一個各種圖形的庫,例如矩形、橢圓形、星形和輪形等幾何形狀。這里是其中兩個的定義: +考虑一个二维的绘图程序,提供了一个各种图形的库,例如矩形、椭圆形、星形和轮形等几何形状。这里是其中两个的定义: ```Go type Circle struct { @@ -14,7 +14,7 @@ type Wheel struct { } ``` -一個Circle代表的圓形類型包含了標準圓心的X和Y坐標信息,和一個Radius表示的半徑信息。一個Wheel輪形除了包含Circle類型所有的全部成員外,還增加了Spokes表示徑向輻條的數量。我們可以這樣創建一個wheel變量: +一个Circle代表的圆形类型包含了标准圆心的X和Y坐标信息,和一个Radius表示的半径信息。一个Wheel轮形除了包含Circle类型所有的全部成员外,还增加了Spokes表示径向辐条的数量。我们可以这样创建一个wheel变量: ```Go var w Wheel @@ -24,7 +24,7 @@ w.Radius = 5 w.Spokes = 20 ``` -隨着庫中幾何形狀數量的增多,我們一定會註意到它們之間的相似和重複之處,所以我們可能爲了便於維護而將相同的屬性獨立出來: +随着库中几何形状数量的增多,我们一定会注意到它们之间的相似和重复之处,所以我们可能为了便于维护而将相同的属性独立出来: ```Go type Point struct { @@ -42,7 +42,7 @@ type Wheel struct { } ``` -這樣改動之後結構體類型變的清晰了,但是這種脩改同時也導致了訪問每個成員變得繁瑣: +这样改动之后结构体类型变的清晰了,但是这种修改同时也导致了访问每个成员变得繁琐: ```Go var w Wheel @@ -52,7 +52,7 @@ w.Circle.Radius = 5 w.Spokes = 20 ``` -Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字;這類成員就叫匿名成員。匿名成員的數據類型必須是命名的類型或指向一個命名的類型的指針。下面的代碼中,Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入到了Circle結構體,同時Circle類型被嵌入到了Wheel結構體。 +Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下面的代码中,Circle和Wheel各自都有一个匿名成员。我们可以说Point类型被嵌入到了Circle结构体,同时Circle类型被嵌入到了Wheel结构体。 ```Go type Circle struct { @@ -66,7 +66,7 @@ type Wheel struct { } ``` -得意於匿名嵌入的特性,我們可以直接訪問葉子屬性而不需要給出完整的路徑: +得意于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径: ```Go var w Wheel @@ -76,16 +76,16 @@ w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20 ``` -在右邊的註釋中給出的顯式形式訪問這些葉子成員的語法依然有效,因此匿名成員併不是眞的無法訪問了。其中匿名成員Circle和Point都有自己的名字——就是命名的類型名字——但是這些名字在點操作符中是可選的。我們在訪問子成員的時候可以忽略任何匿名成員部分。 +在右边的注释中给出的显式形式访问这些叶子成员的语法依然有效,因此匿名成员并不是真的无法访问了。其中匿名成员Circle和Point都有自己的名字——就是命名的类型名字——但是这些名字在点操作符中是可选的。我们在访问子成员的时候可以忽略任何匿名成员部分。 -不幸的是,結構體字面值併沒有簡短表示匿名成員的語法, 因此下面的語句都不能編譯通過: +不幸的是,结构体字面值并没有简短表示匿名成员的语法, 因此下面的语句都不能编译通过: ```Go w = Wheel{8, 8, 5, 20} // compile error: unknown fields w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields ``` -結構體字面值必須遵循形狀類型聲明時的結構,所以我們隻能用下面的兩種語法,它們彼此是等價的: +结构体字面值必须遵循形状类型声明时的结构,所以我们只能用下面的两种语法,它们彼此是等价的: gopl.io/ch4/embed ```Go @@ -110,16 +110,16 @@ fmt.Printf("%#v\n", w) // Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20} ``` -需要註意的是Printf函數中%v參數包含的#副詞,它表示用和Go語言類似的語法打印值。對於結構體類型來説,將包含每個成員的名字。 +需要注意的是Printf函数中%v参数包含的#副词,它表示用和Go语言类似的语法打印值。对于结构体类型来说,将包含每个成员的名字。 -因爲匿名成員也有一個隱式的名字,因此不能同時包含兩個類型相同的匿名成員,這會導致名字衝突。同時,因爲成員的名字是由其類型隱式地決定的,所有匿名成員也有可見性的規則約束。在上面的例子中,Point和Circle匿名成員都是導出的。卽使它們不導出(比如改成小寫字母開頭的point和circle),我們依然可以用簡短形式訪問匿名成員嵌套的成員 +因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突。同时,因为成员的名字是由其类型隐式地决定的,所有匿名成员也有可见性的规则约束。在上面的例子中,Point和Circle匿名成员都是导出的。即使它们不导出(比如改成小写字母开头的point和circle),我们依然可以用简短形式访问匿名成员嵌套的成员 ```Go w.X = 8 // equivalent to w.circle.point.X = 8 ``` -但是在包外部,因爲circle和point沒有導出不能訪問它們的成員,因此簡短的匿名成員訪問語法也是禁止的。 +但是在包外部,因为circle和point没有导出不能访问它们的成员,因此简短的匿名成员访问语法也是禁止的。 -到目前爲止,我們看到匿名成員特性隻是對訪問嵌套成員的點運算符提供了簡短的語法糖。稍後,我們將會看到匿名成員併不要求是結構體類型;其實任何命名的類型都可以作爲結構體的匿名成員。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢? +到目前为止,我们看到匿名成员特性只是对访问嵌套成员的点运算符提供了简短的语法糖。稍后,我们将会看到匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构体的匿名成员。但是为什么要嵌入一个没有任何子成员类型的匿名成员类型呢? -答案是匿名類型的方法集。簡短的點運算符語法可以用於選擇匿名成員嵌套的成員,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成員,而且也獲得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。組合是Go語言中面向對象編程的核心,我們將在6.3節中專門討論。 +答案是匿名类型的方法集。简短的点运算符语法可以用于选择匿名成员嵌套的成员,也可以用于访问它们的方法。实际上,外层的结构体不仅仅是获得了匿名成员类型的所有成员,而且也获得了该类型导出的全部的方法。这个机制可以用于将一个有简单行为的对象组合成有复杂行为的对象。组合是Go语言中面向对象编程的核心,我们将在6.3节中专门讨论。 diff --git a/ch4/ch4-04.md b/ch4/ch4-04.md index 753ed388..4a99006e 100644 --- a/ch4/ch4-04.md +++ b/ch4/ch4-04.md @@ -1,8 +1,8 @@ -## 4.4. 結構體 +## 4.4. 结构体 -結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每個值稱爲結構體的成員。用結構體的經典案例處理公司的員工信息,每個員工信息包含一個唯一的員工編號、員工的名字、家庭住址、出生日期、工作崗位、薪資、上級領導等等。所有的這些信息都需要綁定到一個實體中,可以作爲一個整體單元被複製,作爲函數的參數或返迴值,或者是被存儲到數組中,等等。 +结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典案例处理公司的员工信息,每个员工信息包含一个唯一的员工编号、员工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。所有的这些信息都需要绑定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返回值,或者是被存储到数组中,等等。 -下面兩個語句聲明了一個叫Employee的命名的結構體類型,併且聲明了一個Employee類型的變量dilbert: +下面两个语句声明了一个叫Employee的命名的结构体类型,并且声明了一个Employee类型的变量dilbert: ```Go type Employee struct { @@ -18,33 +18,33 @@ type Employee struct { var dilbert Employee ``` -dilbert結構體變量的成員可以通過點操作符訪問,比如dilbert.Name和dilbert.DoB。因爲dilbert是一個變量,它所有的成員也同樣是變量,我們可以直接對每個成員賦值: +dilbert结构体变量的成员可以通过点操作符访问,比如dilbert.Name和dilbert.DoB。因为dilbert是一个变量,它所有的成员也同样是变量,我们可以直接对每个成员赋值: ```Go dilbert.Salary -= 5000 // demoted, for writing too few lines of code ``` -或者是對成員取地址,然後通過指針訪問: +或者是对成员取地址,然后通过指针访问: ```Go position := &dilbert.Position *position = "Senior " + *position // promoted, for outsourcing to Elbonia ``` -點操作符也可以和指向結構體的指針一起工作: +点操作符也可以和指向结构体的指针一起工作: ```Go var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)" ``` -相當於下面語句 +相当于下面语句 ```Go (*employeeOfTheMonth).Position += " (proactive team player)" ``` -下面的EmployeeByID函數將根據給定的員工ID返迴對應的員工信息結構體的指針。我們可以使用點操作符來訪問它里面的成員: +下面的EmployeeByID函数将根据给定的员工ID返回对应的员工信息结构体的指针。我们可以使用点操作符来访问它里面的成员: ```Go func EmployeeByID(id int) *Employee { /* ... */ } @@ -55,9 +55,9 @@ id := dilbert.ID EmployeeByID(id).Salary = 0 // fired for... no real reason ``` -後面的語句通過EmployeeByID返迴的結構體指針更新了Employee結構體的成員。如果將EmployeeByID函數的返迴值從`*Employee`指針類型改爲Employee值類型,那麽更新語句將不能編譯通過,因爲在賦值語句的左邊併不確定是一個變量(譯註:調用函數返迴的是值,併不是一個可取地址的變量)。 +后面的语句通过EmployeeByID返回的结构体指针更新了Employee结构体的成员。如果将EmployeeByID函数的返回值从`*Employee`指针类型改为Employee值类型,那么更新语句将不能编译通过,因为在赋值语句的左边并不确定是一个变量(译注:调用函数返回的是值,并不是一个可取地址的变量)。 -通常一行對應一個結構體成員,成員的名字在前類型在後,不過如果相鄰的成員類型如果相同的話可以被合併到一行,就像下面的Name和Address成員那樣: +通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行,就像下面的Name和Address成员那样: ```Go type Employee struct { @@ -70,13 +70,13 @@ type Employee struct { } ``` -結構體成員的輸入順序也有重要的意義。我們也可以將Position成員合併(因爲也是字符串類型),或者是交換Name和Address出現的先後順序,那樣的話就是定義了不同的結構體類型。通常,我們隻是將相關的成員寫到一起。 +结构体成员的输入顺序也有重要的意义。我们也可以将Position成员合并(因为也是字符串类型),或者是交换Name和Address出现的先后顺序,那样的话就是定义了不同的结构体类型。通常,我们只是将相关的成员写到一起。 -如果結構體成員名字是以大寫字母開頭的,那麽該成員就是導出的;這是Go語言導出規則決定的。一個結構體可能同時包含導出和未導出的成員。 +如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员。 -結構體類型往往是冗長的,因爲它的每個成員可能都會占一行。雖然我們每次都可以重寫整個結構體成員,但是重複會令人厭煩。因此,完整的結構體寫法通常隻在類型聲明語句的地方出現,就像Employee類型聲明語句那樣。 +结构体类型往往是冗长的,因为它的每个成员可能都会占一行。虽然我们每次都可以重写整个结构体成员,但是重复会令人厌烦。因此,完整的结构体写法通常只在类型声明语句的地方出现,就像Employee类型声明语句那样。 -一個命名爲S的結構體類型將不能再包含S類型的成員:因爲一個聚合的值不能包含它自身。(該限製同樣適應於數組。)但是S類型的結構體可以包含`*S`指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。在下面的代碼中,我們使用一個二叉樹來實現一個插入排序: +一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适应于数组。)但是S类型的结构体可以包含`*S`指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。在下面的代码中,我们使用一个二叉树来实现一个插入排序: gopl.io/ch4/treesort ```Go @@ -121,9 +121,9 @@ func add(t *tree, value int) *tree { } ``` -結構體類型的零值是每個成員都對是零值。通常會將零值作爲最合理的默認值。例如,對於bytes.Buffer類型,結構體初始值就是一個隨時可用的空緩存,還有在第9章將會講到的sync.Mutex的零值也是有效的未鎖定狀態。有時候這種零值可用的特性是自然獲得的,但是也有些類型需要一些額外的工作。 +结构体类型的零值是每个成员都对是零值。通常会将零值作为最合理的默认值。例如,对于bytes.Buffer类型,结构体初始值就是一个随时可用的空缓存,还有在第9章将会讲到的sync.Mutex的零值也是有效的未锁定状态。有时候这种零值可用的特性是自然获得的,但是也有些类型需要一些额外的工作。 -如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小爲0,也不包含任何信息,但是有時候依然是有價值的。有些Go語言程序員用map帶模擬set數據結構時,用它來代替map中布爾類型的value,隻是強調key的重要性,但是因爲節約的空間有限,而且語法比較複雜,所有我們通常避免避免這樣的用法。 +如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小为0,也不包含任何信息,但是有时候依然是有价值的。有些Go语言程序员用map带模拟set数据结构时,用它来代替map中布尔类型的value,只是强调key的重要性,但是因为节约的空间有限,而且语法比较复杂,所有我们通常避免避免这样的用法。 ```Go seen := make(map[string]struct{}) // set of strings diff --git a/ch4/ch4-05.md b/ch4/ch4-05.md index 4d982e8d..7bef00b7 100644 --- a/ch4/ch4-05.md +++ b/ch4/ch4-05.md @@ -1,14 +1,14 @@ ## 4.5. JSON -JavaScript對象表示法(JSON)是一種用於發送和接收結構化信息的標準協議。在類似的協議中,JSON併不是唯一的一個標準協議。 XML(§7.14)、ASN.1和Google的Protocol Buffers都是類似的協議,併且有各自的特色,但是由於簡潔性、可讀性和流行程度等原因,JSON是應用最廣泛的一個。 +JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议。在类似的协议中,JSON并不是唯一的一个标准协议。 XML(§7.14)、ASN.1和Google的Protocol Buffers都是类似的协议,并且有各自的特色,但是由于简洁性、可读性和流行程度等原因,JSON是应用最广泛的一个。 -Go語言對於這些標準格式的編碼和解碼都有良好的支持,由標準庫中的encoding/json、encoding/xml、encoding/asn1等包提供支持(譯註:Protocol Buffers的支持由 github.com/golang/protobuf 包提供),併且這類包都有着相似的API接口。本節,我們將對重要的encoding/json包的用法做個概述。 +Go语言对于这些标准格式的编码和解码都有良好的支持,由标准库中的encoding/json、encoding/xml、encoding/asn1等包提供支持(译注:Protocol Buffers的支持由 github.com/golang/protobuf 包提供),并且这类包都有着相似的API接口。本节,我们将对重要的encoding/json包的用法做个概述。 -JSON是對JavaScript中各種類型的值——字符串、數字、布爾值和對象——Unicode本文編碼。它可以用有效可讀的方式表示第三章的基礎數據類型和本章的數組、slice、結構體和map等聚合數據類型。 +JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和对象——Unicode本文编码。它可以用有效可读的方式表示第三章的基础数据类型和本章的数组、slice、结构体和map等聚合数据类型。 -基本的JSON類型有數字(十進製或科學記數法)、布爾值(true或false)、字符串,其中字符串是以雙引號包含的Unicode字符序列,支持和Go語言類似的反斜槓轉義特性,不過JSON使用的是\Uhhhh轉義數字來表示一個UTF-16編碼(譯註:UTF-16和UTF-8一樣是一種變長的編碼,有些Unicode碼點較大的字符需要用4個字節表示;而且UTF-16還有大端和小端的問題),而不是Go語言的rune類型。 +基本的JSON类型有数字(十进制或科学记数法)、布尔值(true或false)、字符串,其中字符串是以双引号包含的Unicode字符序列,支持和Go语言类似的反斜杠转义特性,不过JSON使用的是\Uhhhh转义数字来表示一个UTF-16编码(译注:UTF-16和UTF-8一样是一种变长的编码,有些Unicode码点较大的字符需要用4个字节表示;而且UTF-16还有大端和小端的问题),而不是Go语言的rune类型。 -這些基礎類型可以通過JSON的數組和對象類型進行遞歸組合。一個JSON數組是一個有序的值序列,寫在一個方括號中併以逗號分隔;一個JSON數組可以用於編碼Go語言的數組和slice。一個JSON對象是一個字符串到值的映射,寫成以繫列的name:value對形式,用花括號包含併以逗號分隔;JSON的對象類型可以用於編碼Go語言的map類型(key類型是字符串)和結構體。例如: +这些基础类型可以通过JSON的数组和对象类型进行递归组合。一个JSON数组是一个有序的值序列,写在一个方括号中并以逗号分隔;一个JSON数组可以用于编码Go语言的数组和slice。一个JSON对象是一个字符串到值的映射,写成以系列的name:value对形式,用花括号包含并以逗号分隔;JSON的对象类型可以用于编码Go语言的map类型(key类型是字符串)和结构体。例如: ``` boolean true @@ -20,7 +20,7 @@ object {"year": 1980, "medals": ["gold", "silver", "bronze"]} ``` -考慮一個應用程序,該程序負責收集各種電影評論併提供反饋功能。它的Movie數據類型和一個典型的表示電影的值列表如下所示。(在結構體聲明中,Year和Color成員後面的字符串面值是結構體成員Tag;我們稍後會解釋它的作用。) +考虑一个应用程序,该程序负责收集各种电影评论并提供反馈功能。它的Movie数据类型和一个典型的表示电影的值列表如下所示。(在结构体声明中,Year和Color成员后面的字符串面值是结构体成员Tag;我们稍后会解释它的作用。) gopl.io/ch4/movie ```Go @@ -42,7 +42,7 @@ var movies = []Movie{ } ``` -這樣的數據結構特别適合JSON格式,併且在兩種之間相互轉換也很容易。將一個Go語言中類似movies的結構體slice轉爲JSON的過程叫編組(marshaling)。編組通過調用json.Marshal函數完成: +这样的数据结构特别适合JSON格式,并且在两种之间相互转换也很容易。将一个Go语言中类似movies的结构体slice转为JSON的过程叫编组(marshaling)。编组通过调用json.Marshal函数完成: ```Go data, err := json.Marshal(movies) @@ -52,7 +52,7 @@ if err != nil { fmt.Printf("%s\n", data) ``` -Marshal函數返還一個編碼後的字節slice,包含很長的字符串,併且沒有空白縮進;我們將它摺行以便於顯示: +Marshal函数返还一个编码后的字节slice,包含很长的字符串,并且没有空白缩进;我们将它折行以便于显示: ``` [{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr @@ -61,7 +61,7 @@ tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true," Actors":["Steve McQueen","Jacqueline Bisset"]}] ``` -這種緊湊的表示形式雖然包含了全部的信息,但是很難閲讀。爲了生成便於閲讀的格式,另一個json.MarshalIndent函數將産生整齊縮進的輸出。該函數有兩個額外的字符串參數用於表示每一行輸出的前綴和每一個層級的縮進: +这种紧凑的表示形式虽然包含了全部的信息,但是很难阅读。为了生成便于阅读的格式,另一个json.MarshalIndent函数将产生整齐缩进的输出。该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进: ```Go data, err := json.MarshalIndent(movies, "", " ") @@ -71,7 +71,7 @@ if err != nil { fmt.Printf("%s\n", data) ``` -上面的代碼將産生這樣的輸出(譯註:在最後一個成員或元素後面併沒有逗號分隔符): +上面的代码将产生这样的输出(译注:在最后一个成员或元素后面并没有逗号分隔符): ```Json [ @@ -103,18 +103,18 @@ fmt.Printf("%s\n", data) ] ``` -在編碼時,默認使用Go語言結構體的成員名字作爲JSON的對象(通過reflect反射技術,我們將在12.6節討論)。隻有導出的結構體成員才會被編碼,這也就是我們爲什麽選擇用大寫字母開頭的成員名稱。 +在编码时,默认使用Go语言结构体的成员名字作为JSON的对象(通过reflect反射技术,我们将在12.6节讨论)。只有导出的结构体成员才会被编码,这也就是我们为什么选择用大写字母开头的成员名称。 -細心的讀者可能已經註意到,其中Year名字的成員在編碼後變成了released,還有Color成員編碼後變成了小寫字母開頭的color。這是因爲構體成員Tag所導致的。一個構體成員Tag是和在編譯階段關聯到該成員的元信息字符串: +细心的读者可能已经注意到,其中Year名字的成员在编码后变成了released,还有Color成员编码后变成了小写字母开头的color。这是因为构体成员Tag所导致的。一个构体成员Tag是和在编译阶段关联到该成员的元信息字符串: ``` Year int `json:"released"` Color bool `json:"color,omitempty"` ``` -結構體的成員Tag可以是任意的字符串面值,但是通常是一繫列用空格分隔的key:"value"鍵值對序列;因爲值中含義雙引號字符,因此成員Tag一般用原生字符串面值的形式書寫。json開頭鍵名對應的值用於控製encoding/json包的編碼和解碼的行爲,併且encoding/...下面其它的包也遵循這個約定。成員Tag中json對應值的第一部分用於指定JSON對象的名字,比如將Go語言中的TotalCount成員對應到JSON中的total_count對象。Color成員的Tag還帶了一個額外的omitempty選項,表示當Go語言結構體成員爲空或零值時不生成JSON對象(這里false爲零值)。果然,Casablanca是一個黑白電影,併沒有輸出Color成員。 +结构体的成员Tag可以是任意的字符串面值,但是通常是一系列用空格分隔的key:"value"键值对序列;因为值中含义双引号字符,因此成员Tag一般用原生字符串面值的形式书写。json开头键名对应的值用于控制encoding/json包的编码和解码的行为,并且encoding/...下面其它的包也遵循这个约定。成员Tag中json对应值的第一部分用于指定JSON对象的名字,比如将Go语言中的TotalCount成员对应到JSON中的total_count对象。Color成员的Tag还带了一个额外的omitempty选项,表示当Go语言结构体成员为空或零值时不生成JSON对象(这里false为零值)。果然,Casablanca是一个黑白电影,并没有输出Color成员。 -編碼的逆操作是解碼,對應將JSON數據解碼爲Go語言的數據結構,Go語言中一般叫unmarshaling,通過json.Unmarshal函數完成。下面的代碼將JSON格式的電影數據解碼爲一個結構體slice,結構體中隻有Title成員。通過定義合適的Go語言數據結構,我們可以選擇性地解碼JSON中感興趣的成員。當Unmarshal函數調用返迴,slice將被隻含有Title信息值填充,其它JSON成員將被忽略。 +编码的逆操作是解码,对应将JSON数据解码为Go语言的数据结构,Go语言中一般叫unmarshaling,通过json.Unmarshal函数完成。下面的代码将JSON格式的电影数据解码为一个结构体slice,结构体中只有Title成员。通过定义合适的Go语言数据结构,我们可以选择性地解码JSON中感兴趣的成员。当Unmarshal函数调用返回,slice将被只含有Title信息值填充,其它JSON成员将被忽略。 ```Go var titles []struct{ Title string } @@ -124,7 +124,7 @@ if err := json.Unmarshal(data, &titles); err != nil { fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]" ``` -許多web服務都提供JSON接口,通過HTTP接口發送JSON格式請求併返迴JSON格式的信息。爲了説明這一點,我們通過Github的issue査詢服務來演示類似的用法。首先,我們要定義合適的類型和常量: +许多web服务都提供JSON接口,通过HTTP接口发送JSON格式请求并返回JSON格式的信息。为了说明这一点,我们通过Github的issue查询服务来演示类似的用法。首先,我们要定义合适的类型和常量: gopl.io/ch4/github ```Go @@ -157,9 +157,9 @@ type User struct { } ``` -和前面一樣,卽使對應的JSON對象名是小寫字母,每個結構體的成員名也是聲明爲大小字母開頭的。因爲有些JSON成員名字和Go結構體成員名字併不相同,因此需要Go語言結構體成員Tag來指定對應的JSON名字。同樣,在解碼的時候也需要做同樣的處理,GitHub服務返迴的信息比我們定義的要多很多。 +和前面一样,即使对应的JSON对象名是小写字母,每个结构体的成员名也是声明为大小字母开头的。因为有些JSON成员名字和Go结构体成员名字并不相同,因此需要Go语言结构体成员Tag来指定对应的JSON名字。同样,在解码的时候也需要做同样的处理,GitHub服务返回的信息比我们定义的要多很多。 -SearchIssues函數發出一個HTTP請求,然後解碼返迴的JSON格式的結果。因爲用戶提供的査詢條件可能包含類似`?`和`&`之類的特殊字符,爲了避免對URL造成衝突,我們用url.QueryEscape來對査詢中的特殊字符進行轉義操作。 +SearchIssues函数发出一个HTTP请求,然后解码返回的JSON格式的结果。因为用户提供的查询条件可能包含类似`?`和`&`之类的特殊字符,为了避免对URL造成冲突,我们用url.QueryEscape来对查询中的特殊字符进行转义操作。 gopl.io/ch4/github ```Go @@ -198,9 +198,9 @@ func SearchIssues(terms []string) (*IssuesSearchResult, error) { } ``` -在早些的例子中,我們使用了json.Unmarshal函數來將JSON格式的字符串解碼爲字節slice。但是這個例子中,我們使用了基於流式的解碼器json.Decoder,它可以從一個輸入流解碼JSON數據,盡管這不是必須的。如您所料,還有一個針對輸出流的json.Encoder編碼對象。 +在早些的例子中,我们使用了json.Unmarshal函数来将JSON格式的字符串解码为字节slice。但是这个例子中,我们使用了基于流式的解码器json.Decoder,它可以从一个输入流解码JSON数据,尽管这不是必须的。如您所料,还有一个针对输出流的json.Encoder编码对象。 -我們調用Decode方法來填充變量。這里有多種方法可以格式化結構。下面是最簡單的一種,以一個固定寬度打印每個issue,但是在下一節我們將看到如果利用模闆來輸出複雜的格式。 +我们调用Decode方法来填充变量。这里有多种方法可以格式化结构。下面是最简单的一种,以一个固定宽度打印每个issue,但是在下一节我们将看到如果利用模板来输出复杂的格式。 gopl.io/ch4/issues ```Go @@ -228,7 +228,7 @@ func main() { } ``` -通過命令行參數指定檢索條件。下面的命令是査詢Go語言項目中和JSON解碼相關的問題,還有査詢返迴的結果: +通过命令行参数指定检索条件。下面的命令是查询Go语言项目中和JSON解码相关的问题,还有查询返回的结果: ``` $ go build gopl.io/ch4/issues @@ -249,12 +249,12 @@ $ ./issues repo:golang/go is:open json decoder #4237 gjemiller encoding/base64: URLEncoding padding is optional ``` -GitHub的Web服務接口 https://developer.github.com/v3/ 包含了更多的特性。 +GitHub的Web服务接口 https://developer.github.com/v3/ 包含了更多的特性。 -**練習 4.10:** 脩改issues程序,根據問題的時間進行分類,比如不到一個月的、不到一年的、超過一年。 +**练习 4.10:** 修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。 -**練習 4.11:** 編寫一個工具,允許用戶在命令行創建、讀取、更新和關閉GitHub上的issue,當必要的時候自動打開用戶默認的編輯器用於輸入文本信息。 +**练习 4.11:** 编写一个工具,允许用户在命令行创建、读取、更新和关闭GitHub上的issue,当必要的时候自动打开用户默认的编辑器用于输入文本信息。 -**練習 4.12:** 流行的web漫畵服務xkcd也提供了JSON接口。例如,一個 https://xkcd.com/571/info.0.json 請求將返迴一個很多人喜愛的571編號的詳細描述。下載每個鏈接(隻下載一次)然後創建一個離線索引。編寫一個xkcd工具,使用這些離線索引,打印和命令行輸入的檢索詞相匹配的漫畵的URL。 +**练习 4.12:** 流行的web漫画服务xkcd也提供了JSON接口。例如,一个 https://xkcd.com/571/info.0.json 请求将返回一个很多人喜爱的571编号的详细描述。下载每个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具,使用这些离线索引,打印和命令行输入的检索词相匹配的漫画的URL。 -**練習 4.13:** 使用開放電影數據庫的JSON服務接口,允許你檢索和下載 https://omdbapi.com/ 上電影的名字和對應的海報圖像。編寫一個poster工具,通過命令行輸入的電影名字,下載對應的海報。 +**练习 4.13:** 使用开放电影数据库的JSON服务接口,允许你检索和下载 https://omdbapi.com/ 上电影的名字和对应的海报图像。编写一个poster工具,通过命令行输入的电影名字,下载对应的海报。 diff --git a/ch4/ch4-06.md b/ch4/ch4-06.md index 44135f70..e66c701d 100644 --- a/ch4/ch4-06.md +++ b/ch4/ch4-06.md @@ -1,8 +1,8 @@ -## 4.6. 文本和HTML模闆 +## 4.6. 文本和HTML模板 -前面的例子,隻是最簡單的格式化,使用Printf是完全足夠的。但是有時候會需要複雜的打印格式,這時候一般需要將格式化代碼分離出來以便更安全地脩改。這寫功能是由text/template和html/template等模闆包提供的,它們提供了一個將變量值填充到一個文本或HTML格式的模闆的機製。 +前面的例子,只是最简单的格式化,使用Printf是完全足够的。但是有时候会需要复杂的打印格式,这时候一般需要将格式化代码分离出来以便更安全地修改。这写功能是由text/template和html/template等模板包提供的,它们提供了一个将变量值填充到一个文本或HTML格式的模板的机制。 -一個模闆是一個字符串或一個文件,里面包含了一個或多個由雙花括號包含的`{{action}}`對象。大部分的字符串隻是按面值打印,但是對於actions部分將觸發其它的行爲。每個actions都包含了一個用模闆語言書寫的表達式,一個action雖然簡短但是可以輸出複雜的打印值,模闆語言包含通過選擇結構體的成員、調用函數或方法、表達式控製流if-else語句和range循環語句,還有其它實例化模闆等諸多特性。下面是一個簡單的模闆字符串: +一个模板是一个字符串或一个文件,里面包含了一个或多个由双花括号包含的`{{action}}`对象。大部分的字符串只是按面值打印,但是对于actions部分将触发其它的行为。每个actions都包含了一个用模板语言书写的表达式,一个action虽然简短但是可以输出复杂的打印值,模板语言包含通过选择结构体的成员、调用函数或方法、表达式控制流if-else语句和range循环语句,还有其它实例化模板等诸多特性。下面是一个简单的模板字符串: {% raw %} @@ -21,11 +21,11 @@ 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 %} -在一個action中,`|`操作符表示將前一個表達式的結果作爲後一個函數的輸入,類似於UNIX中管道的概念。在Title這一行的action中,第二個操作是一個printf函數,是一個基於fmt.Sprintf實現的內置函數,所有模闆都可以直接使用。對於Age部分,第二個動作是一個叫daysAgo的函數,通過time.Since函數將CreatedAt成員轉換爲過去的時間長度: +在一个action中,`|`操作符表示将前一个表达式的结果作为后一个函数的输入,类似于UNIX中管道的概念。在Title这一行的action中,第二个操作是一个printf函数,是一个基于fmt.Sprintf实现的内置函数,所有模板都可以直接使用。对于Age部分,第二个动作是一个叫daysAgo的函数,通过time.Since函数将CreatedAt成员转换为过去的时间长度: ```Go func daysAgo(t time.Time) int { @@ -33,9 +33,9 @@ func daysAgo(t time.Time) int { } ``` -需要註意的是CreatedAt的參數類型是time.Time,併不是字符串。以同樣的方式,我們可以通過定義一些方法來控製字符串的格式化(§2.5),一個類型同樣可以定製自己的JSON編碼和解碼行爲。time.Time類型對應的JSON值是一個標準時間格式的字符串。 +需要注意的是CreatedAt的参数类型是time.Time,并不是字符串。以同样的方式,我们可以通过定义一些方法来控制字符串的格式化(§2.5),一个类型同样可以定制自己的JSON编码和解码行为。time.Time类型对应的JSON值是一个标准时间格式的字符串。 -生成模闆的輸出需要兩個處理步驟。第一步是要分析模闆併轉爲內部表示,然後基於指定的輸入執行模闆。分析模闆部分一般隻需要執行一次。下面的代碼創建併分析上面定義的模闆templ。註意方法調用鏈的順序:template.New先創建併返迴一個模闆;Funcs方法將daysAgo等自定義函數註冊到模闆中,併返迴模闆;最後調用Parse函數分析模闆。 +生成模板的输出需要两个处理步骤。第一步是要分析模板并转为内部表示,然后基于指定的输入执行模板。分析模板部分一般只需要执行一次。下面的代码创建并分析上面定义的模板templ。注意方法调用链的顺序:template.New先创建并返回一个模板;Funcs方法将daysAgo等自定义函数注册到模板中,并返回模板;最后调用Parse函数分析模板。 ```Go report, err := template.New("report"). @@ -46,9 +46,9 @@ if err != nil { } ``` -因爲模闆通常在編譯時就測試好了,如果模闆解析失敗將是一個致命的錯誤。template.Must輔助函數可以簡化這個致命錯誤的處理:它接受一個模闆和一個error類型的參數,檢測error是否爲nil(如果不是nil則發出panic異常),然後返迴傳入的模闆。我們將在5.9節再討論這個話題。 +因为模板通常在编译时就测试好了,如果模板解析失败将是一个致命的错误。template.Must辅助函数可以简化这个致命错误的处理:它接受一个模板和一个error类型的参数,检测error是否为nil(如果不是nil则发出panic异常),然后返回传入的模板。我们将在5.9节再讨论这个话题。 -一旦模闆已經創建、註冊了daysAgo函數、併通過分析和檢測,我們就可以使用github.IssuesSearchResult作爲輸入源、os.Stdout作爲輸出源來執行模闆: +一旦模板已经创建、注册了daysAgo函数、并通过分析和检测,我们就可以使用github.IssuesSearchResult作为输入源、os.Stdout作为输出源来执行模板: ```Go var report = template.Must(template.New("issuelist"). @@ -66,7 +66,7 @@ func main() { } ``` -程序輸出一個純文本報告: +程序输出一个纯文本报告: ``` $ go build gopl.io/ch4/issuesreport @@ -86,9 +86,9 @@ Age: 695 days ... ``` -現在讓我們轉到html/template模闆包。它使用和text/template包相同的API和模闆語言,但是增加了一個將字符串自動轉義特性,這可以避免輸入字符串和HTML、JavaScript、CSS或URL語法産生衝突的問題。這個特性還可以避免一些長期存在的安全問題,比如通過生成HTML註入攻擊,通過構造一個含有惡意代碼的問題標題,這些都可能讓模闆輸出錯誤的輸出,從而讓他們控製頁面。 +现在让我们转到html/template模板包。它使用和text/template包相同的API和模板语言,但是增加了一个将字符串自动转义特性,这可以避免输入字符串和HTML、JavaScript、CSS或URL语法产生冲突的问题。这个特性还可以避免一些长期存在的安全问题,比如通过生成HTML注入攻击,通过构造一个含有恶意代码的问题标题,这些都可能让模板输出错误的输出,从而让他们控制页面。 -下面的模闆以HTML格式輸出issue列表。註意import語句的不同: +下面的模板以HTML格式输出issue列表。注意import语句的不同: {% raw %} @@ -119,26 +119,26 @@ var issueList = template.Must(template.New("issuelist").Parse(` {% endraw %} -下面的命令將在新的模闆上執行一個稍微不同的査詢: +下面的命令将在新的模板上执行一个稍微不同的查询: ``` $ go build gopl.io/ch4/issueshtml $ ./issueshtml repo:golang/go commenter:gopherbot json encoder >issues.html ``` -圖4.4顯示了在web瀏覽器中的效果圖。每個issue包含到Github對應頁面的鏈接。 +图4.4显示了在web浏览器中的效果图。每个issue包含到Github对应页面的链接。 ![](../images/ch4-04.png) -圖4.4中issue沒有包含會對HTML格式産生衝突的特殊字符,但是我們馬上將看到標題中含有`&`和`<`字符的issue。下面的命令選擇了兩個這樣的issue: +图4.4中issue没有包含会对HTML格式产生冲突的特殊字符,但是我们马上将看到标题中含有`&`和`<`字符的issue。下面的命令选择了两个这样的issue: ``` $ ./issueshtml repo:golang/go 3133 10535 >issues2.html ``` -圖4.5顯示了該査詢的結果。註意,html/template包已經自動將特殊字符轉義,因此我們依然可以看到正確的字面值。如果我們使用text/template包的話,這2個issue將會産生錯誤,其中“&lt;”四個字符將會被當作小於字符“<”處理,同時“<link>”字符串將會被當作一個鏈接元素處理,它們都會導致HTML文檔結構的改變,從而導致有未知的風險。 +图4.5显示了该查询的结果。注意,html/template包已经自动将特殊字符转义,因此我们依然可以看到正确的字面值。如果我们使用text/template包的话,这2个issue将会产生错误,其中“&lt;”四个字符将会被当作小于字符“<”处理,同时“<link>”字符串将会被当作一个链接元素处理,它们都会导致HTML文档结构的改变,从而导致有未知的风险。 -我們也可以通過對信任的HTML字符串使用template.HTML類型來抑製這種自動轉義的行爲。還有很多采用類型命名的字符串類型分别對應信任的JavaScript、CSS和URL。下面的程序演示了兩個使用不同類型的相同字符串産生的不同結果:A是一個普通字符串,B是一個信任的template.HTML字符串類型。 +我们也可以通过对信任的HTML字符串使用template.HTML类型来抑制这种自动转义的行为。还有很多采用类型命名的字符串类型分别对应信任的JavaScript、CSS和URL。下面的程序演示了两个使用不同类型的相同字符串产生的不同结果:A是一个普通字符串,B是一个信任的template.HTML字符串类型。 ![](../images/ch4-05.png) @@ -163,15 +163,15 @@ func main() { {% endraw %} -圖4.6顯示了出現在瀏覽器中的模闆輸出。我們看到A的黑體標記被轉義失效了,但是B沒有。 +图4.6显示了出现在浏览器中的模板输出。我们看到A的黑体标记被转义失效了,但是B没有。 ![](../images/ch4-06.png) -我們這里隻講述了模闆繫統中最基本的特性。一如旣往,如果想了解更多的信息,請自己査看包文檔: +我们这里只讲述了模板系统中最基本的特性。一如既往,如果想了解更多的信息,请自己查看包文档: ``` $ go doc text/template $ go doc html/template ``` -**練習 4.14:** 創建一個web服務器,査詢一次GitHub,然後生成BUG報告、里程碑和對應的用戶信息。 +**练习 4.14:** 创建一个web服务器,查询一次GitHub,然后生成BUG报告、里程碑和对应的用户信息。 diff --git a/ch4/ch4.md b/ch4/ch4.md index 42186ec1..48bfbd9e 100644 --- a/ch4/ch4.md +++ b/ch4/ch4.md @@ -1,6 +1,6 @@ -# 第四章 複合數據類型 +# 第四章 复合数据类型 -在第三章我們討論了基本數據類型,它們可以用於構建程序中數據結構,是Go語言的世界的原子。在本章,我們將討論複合數據類型,它是以不同的方式組合基本類型可以構造出來的複合數據類型。我們主要討論四種類型——數組、slice、map和結構體——同時在本章的最後,我們將演示如何使用結構體來解碼和編碼到對應JSON格式的數據,併且通過結合使用模闆來生成HTML頁面。 +在第三章我们讨论了基本数据类型,它们可以用于构建程序中数据结构,是Go语言的世界的原子。在本章,我们将讨论复合数据类型,它是以不同的方式组合基本类型可以构造出来的复合数据类型。我们主要讨论四种类型——数组、slice、map和结构体——同时在本章的最后,我们将演示如何使用结构体来解码和编码到对应JSON格式的数据,并且通过结合使用模板来生成HTML页面。 -數組和結構體是聚合類型;它們的值由許多元素或成員字段的值組成。數組是由同構的元素組成——每個數組元素都是完全相同的類型——結構體則是由異構的元素組成的。數組和結構體都是有固定內存大小的數據結構。相比之下,slice和map則是動態的數據結構,它們將根據需要動態增長。 +数组和结构体是聚合类型;它们的值由许多元素或成员字段的值组成。数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。 diff --git a/ch5/ch5-01.md b/ch5/ch5-01.md index 3af2a666..1ba5a59a 100644 --- a/ch5/ch5-01.md +++ b/ch5/ch5-01.md @@ -1,6 +1,6 @@ -## 5.1. 函數聲明 +## 5.1. 函数声明 -函數聲明包括函數名、形式參數列表、返迴值列表(可省略)以及函數體。 +函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。 ```Go func name(parameter-list) (result-list) { @@ -8,8 +8,8 @@ func name(parameter-list) (result-list) { } ``` -形式參數列表描述了函數的參數名以及參數類型。這些參數作爲局部變量,其值由參數調用者提供。返迴值列表描述了函數返迴值的變量名以及類型。如果函數返迴一個無名變量或者沒有返迴值,返迴值列表的括號是可以省略的。如果一個函數聲明不包括返迴值列表,那麽函數體執行完畢後,不會返迴任何值。 -在hypot函數中, +形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部变量,其值由参数调用者提供。返回值列表描述了函数返回值的变量名以及类型。如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。 +在hypot函数中, ```Go func hypot(x, y float64) float64 { @@ -18,18 +18,18 @@ func hypot(x, y float64) float64 { fmt.Println(hypot(3,4)) // "5" ``` -x和y是形參名,3和4是調用時的傳入的實數,函數返迴了一個float64類型的值。 -返迴值也可以像形式參數一樣被命名。在這種情況下,每個返迴值被聲明成一個局部變量,併根據該返迴值的類型,將其初始化爲0。 -如果一個函數在聲明時,包含返迴值列表,該函數必須以 return語句結尾,除非函數明顯無法運行到結尾處。例如函數在結尾時調用了panic異常或函數中存在無限循環。 +x和y是形参名,3和4是调用时的传入的实数,函数返回了一个float64类型的值。 +返回值也可以像形式参数一样被命名。在这种情况下,每个返回值被声明成一个局部变量,并根据该返回值的类型,将其初始化为0。 +如果一个函数在声明时,包含返回值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic异常或函数中存在无限循环。 -正如hypot一樣,如果一組形參或返迴值有相同的類型,我們不必爲每個形參都寫出參數類型。下面2個聲明是等價的: +正如hypot一样,如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型。下面2个声明是等价的: ```Go func f(i, j, k int, s, t string) { /* ... */ } func f(i int, j int, k int, s string, t string) { /* ... */ } ``` -下面,我們給出4種方法聲明擁有2個int型參數和1個int型返迴值的函數.blank identifier(譯者註:卽下文的_符號)可以強調某個參數未被使用。 +下面,我们给出4种方法声明拥有2个int型参数和1个int型返回值的函数.blank identifier(译者注:即下文的_符号)可以强调某个参数未被使用。 ```Go func add(x int, y int) int {return x + y} @@ -43,15 +43,15 @@ fmt.Printf("%T\n", first) // "func(int, int) int" fmt.Printf("%T\n", zero) // "func(int, int) int" ``` -函數的類型被稱爲函數的標識符。如果兩個函數形式參數列表和返迴值列表中的變量類型一一對應,那麽這兩個函數被認爲有相同的類型和標識符。形參和返迴值的變量名不影響函數標識符也不影響它們是否可以以省略參數類型的形式表示。 +函数的类型被称为函数的标识符。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型和标识符。形参和返回值的变量名不影响函数标识符也不影响它们是否可以以省略参数类型的形式表示。 -每一次函數調用都必須按照聲明順序爲所有參數提供實參(參數值)。在函數調用時,Go語言沒有默認參數值,也沒有任何方法可以通過參數名指定形參,因此形參和返迴值的變量名對於函數調用者而言沒有意義。 +每一次函数调用都必须按照声明顺序为所有参数提供实参(参数值)。在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。 -在函數體中,函數的形參作爲局部變量,被初始化爲調用者提供的值。函數的形參和有名返迴值作爲函數最外層的局部變量,被存儲在相同的詞法塊中。 +在函数体中,函数的形参作为局部变量,被初始化为调用者提供的值。函数的形参和有名返回值作为函数最外层的局部变量,被存储在相同的词法块中。 -實參通過值的方式傳遞,因此函數的形參是實參的拷貝。對形參進行脩改不會影響實參。但是,如果實參包括引用類型,如指針,slice(切片)、map、function、channel等類型,實參可能會由於函數的簡介引用被脩改。 +实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的简介引用被修改。 -你可能會偶爾遇到沒有函數體的函數聲明,這表示該函數不是以Go實現的。這樣的聲明定義了函數標識符。 +你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数标识符。 ```Go package math diff --git a/ch5/ch5-02.md b/ch5/ch5-02.md index 6b0b6391..9c7fd92c 100644 --- a/ch5/ch5-02.md +++ b/ch5/ch5-02.md @@ -1,10 +1,10 @@ -## 5.2. 遞歸 +## 5.2. 递归 -函數可以是遞歸的,這意味着函數可以直接或間接的調用自身。對許多問題而言,遞歸是一種強有力的技術,例如處理遞歸的數據結構。在4.4節,我們通過遍歷二叉樹來實現簡單的插入排序,在本章節,我們再次使用它來處理HTML文件。 +函数可以是递归的,这意味着函数可以直接或间接的调用自身。对许多问题而言,递归是一种强有力的技术,例如处理递归的数据结构。在4.4节,我们通过遍历二叉树来实现简单的插入排序,在本章节,我们再次使用它来处理HTML文件。 -下文的示例代碼使用了非標準包 golang.org/x/net/html ,解析HTML。golang.org/x/... 目録下存儲了一些由Go糰隊設計、維護,對網絡編程、国際化文件處理、移動平台、圖像處理、加密解密、開發者工具提供支持的擴展包。未將這些擴展包加入到標準庫原因有二,一是部分包仍在開發中,二是對大多數Go語言的開發者而言,擴展包提供的功能很少被使用。 +下文的示例代码使用了非标准包 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 @@ -35,7 +35,7 @@ type Attribute struct { func Parse(r io.Reader) (*Node, error) ``` -main函數解析HTML標準輸入,通過遞歸函數visit獲得links(鏈接),併打印出這些links: +main函数解析HTML标准输入,通过递归函数visit获得links(链接),并打印出这些links: gopl.io/ch5/findlinks1 ```Go @@ -61,7 +61,7 @@ func main() { } ``` -visit函數遍歷HTML的節點樹,從每一個anchor元素的href屬性獲得link,將這些links存入字符串數組中,併返迴這個字符串數組。 +visit函数遍历HTML的节点树,从每一个anchor元素的href属性获得link,将这些links存入字符串数组中,并返回这个字符串数组。 ```Go // visit appends to links each link found in n and returns the result. @@ -80,9 +80,9 @@ func visit(links []string, n *html.Node) []string { } ``` -爲了遍歷結點n的所有後代結點,每次遇到n的孩子結點時,visit遞歸的調用自身。這些孩子結點存放在FirstChild鏈表中。 +为了遍历结点n的所有后代结点,每次遇到n的孩子结点时,visit递归的调用自身。这些孩子结点存放在FirstChild链表中。 -讓我們以Go的主頁(golang.org)作爲目標,運行findlinks。我們以fetch(1.5章)的輸出作爲findlinks的輸入。下面的輸出做了簡化處理。 +让我们以Go的主页(golang.org)作为目标,运行findlinks。我们以fetch(1.5章)的输出作为findlinks的输入。下面的输出做了简化处理。 ``` $ go build gopl.io/ch1/fetch @@ -102,9 +102,9 @@ https://golang.org/dl/ http://www.google.com/intl/en/policies/privacy/ ``` -註意在頁面中出現的鏈接格式,在之後我們會介紹如何將這些鏈接,根據根路徑( https://golang.org )生成可以直接訪問的url。 +注意在页面中出现的链接格式,在之后我们会介绍如何将这些链接,根据根路径( https://golang.org )生成可以直接访问的url。 -在函數outline中,我們通過遞歸的方式遍歷整個HTML結點樹,併輸出樹的結構。在outline內部,每遇到一個HTML元素標籤,就將其入棧,併輸出。 +在函数outline中,我们通过递归的方式遍历整个HTML结点树,并输出树的结构。在outline内部,每遇到一个HTML元素标签,就将其入栈,并输出。 gopl.io/ch5/outline ```Go @@ -127,9 +127,9 @@ func outline(stack []string, n *html.Node) { } ``` -有一點值得註意:outline有入棧操作,但沒有相對應的出棧操作。當outline調用自身時,被調用者接收的是stack的拷貝。被調用者的入棧操作,脩改的是stack的拷貝,而不是調用者的stack,因對當函數返迴時,調用者的stack併未被脩改。 +有一点值得注意:outline有入栈操作,但没有相对应的出栈操作。当outline调用自身时,被调用者接收的是stack的拷贝。被调用者的入栈操作,修改的是stack的拷贝,而不是调用者的stack,因对当函数返回时,调用者的stack并未被修改。 -下面是 https://golang.org 頁面的簡要結構: +下面是 https://golang.org 页面的简要结构: ``` $ go build gopl.io/ch5/outline @@ -149,14 +149,14 @@ $ ./fetch https://golang.org | ./outline ... ``` -正如你在上面實驗中所見,大部分HTML頁面隻需幾層遞歸就能被處理,但仍然有些頁面需要深層次的遞歸。 +正如你在上面实验中所见,大部分HTML页面只需几层递归就能被处理,但仍然有些页面需要深层次的递归。 -大部分編程語言使用固定大小的函數調用棧,常見的大小從64KB到2MB不等。固定大小棧會限製遞歸的深度,當你用遞歸處理大量數據時,需要避免棧溢出;除此之外,還會導致安全性問題。與相反,Go語言使用可變棧,棧的大小按需增加(初始時很小)。這使得我們使用遞歸時不必考慮溢出和安全問題。 +大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到2MB不等。固定大小栈会限制递归的深度,当你用递归处理大量数据时,需要避免栈溢出;除此之外,还会导致安全性问题。与相反,Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。 -**練習 5.1:** 脩改findlinks代碼中遍歷n.FirstChild鏈表的部分,將循環調用visit,改成遞歸調用。 +**练习 5.1:** 修改findlinks代码中遍历n.FirstChild链表的部分,将循环调用visit,改成递归调用。 -**練習 5.2:** 編寫函數,記録在HTML樹中出現的同名元素的次數。 +**练习 5.2:** 编写函数,记录在HTML树中出现的同名元素的次数。 -**練習 5.3:** 編寫函數輸出所有text結點的內容。註意不要訪問`