回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,14 +1,14 @@
# 貢獻 # 贡献
者 | 章 者 | 章
-------------------------------------- | ------------------------- -------------------------------------- | -------------------------
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章 `chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章 `Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
`CrazySssst` | 第5章 `CrazySssst` | 第5章
`foreversmart <njutree@gmail.com>` | 第7章 `foreversmart <njutree@gmail.com>` | 第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)授.
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="./images/by-nc-sa-4.0-88x31.png"></img></a> <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="./images/by-nc-sa-4.0-88x31.png"></img></a>

View File

@@ -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) [![](cover_middle.jpg)](http://golang-china.github.io/gopl-zh)
-版本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/archive/gh-pages.zip
- 目主http://github.com/golang-china/gopl-zh - 目主http://github.com/golang-china/gopl-zh
- 原版官http://gopl.io - 原版官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)。 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插件。 2.`gopl-zh`录,运`gitbook install`,安GitBook插件。
3. `make`,生成`_book` 3. `make`,生成`_book`
4.`_book/index.html`文件。 4.`_book/index.html`文件。
### 簡體/繁體轉換 ### 简体/繁体转换
切片到 `gopl-zh` 切片到 `gopl-zh`
- `make zh2tw``go run zh2tw.go . "\.md$" zh2tw`轉繁體 - `make zh2tw``go run zh2tw.go . "\.md$" zh2tw`转繁体
- `make tw2zh``go run zh2tw.go . "\.md$" tw2zh`轉簡體 - `make tw2zh``go run zh2tw.go . "\.md$" tw2zh`转简体
# 版權聲 # 版权声
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a> <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="./images/by-nc-sa-4.0-88x31.png"></img></a> <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="./images/by-nc-sa-4.0-88x31.png"></img></a>
禁任何商業行爲使用或引用該文檔的全部或部分容! 禁任何商业行为使用或引用该文档的全部或部分容!
迎大家提供建 迎大家提供建

View File

@@ -1,129 +1,129 @@
# Summary # Summary
* [前言](preface.md) * [前言](preface.md)
* [Go言起源](ch0/ch0-01.md) * [Go言起源](ch0/ch0-01.md)
* [Go語言項](ch0/ch0-02.md) * [Go语言项](ch0/ch0-02.md)
* [書的組織](ch0/ch0-03.md) * [书的组织](ch0/ch0-03.md)
* [更多的信息](ch0/ch0-04.md) * [更多的信息](ch0/ch0-04.md)
* [](ch0/ch0-05.md) * [](ch0/ch0-05.md)
* [](ch1/ch1.md) * [](ch1/ch1.md)
* [Hello, World](ch1/ch1-01.md) * [Hello, World](ch1/ch1-01.md)
* [命令行參數](ch1/ch1-02.md) * [命令行参数](ch1/ch1-02.md)
* [找重的行](ch1/ch1-03.md) * [找重的行](ch1/ch1-03.md)
* [GIF動畵](ch1/ch1-04.md) * [GIF动画](ch1/ch1-04.md)
* [取URL](ch1/ch1-05.md) * [取URL](ch1/ch1-05.md)
* [併發獲取多URL](ch1/ch1-06.md) * [并发获取多URL](ch1/ch1-06.md)
* [Web服](ch1/ch1-07.md) * [Web服](ch1/ch1-07.md)
* [本章要](ch1/ch1-08.md) * [本章要](ch1/ch1-08.md)
* [程序結構](ch2/ch2.md) * [程序结构](ch2/ch2.md)
* [命名](ch2/ch2-01.md) * [命名](ch2/ch2-01.md)
* [](ch2/ch2-02.md) * [](ch2/ch2-02.md)
* [](ch2/ch2-03.md) * [](ch2/ch2-03.md)
* [](ch2/ch2-04.md) * [](ch2/ch2-04.md)
* [](ch2/ch2-05.md) * [](ch2/ch2-05.md)
* [包和文件](ch2/ch2-06.md) * [包和文件](ch2/ch2-06.md)
* [作用域](ch2/ch2-07.md) * [作用域](ch2/ch2-07.md)
* [礎數據類](ch3/ch3.md) * [础数据类](ch3/ch3.md)
* [整型](ch3/ch3-01.md) * [整型](ch3/ch3-01.md)
* [點數](ch3/ch3-02.md) * [点数](ch3/ch3-02.md)
* [複數](ch3/ch3-03.md) * [复数](ch3/ch3-03.md)
* [](ch3/ch3-04.md) * [](ch3/ch3-04.md)
* [字符串](ch3/ch3-05.md) * [字符串](ch3/ch3-05.md)
* [常量](ch3/ch3-06.md) * [常量](ch3/ch3-06.md)
* [複合數據類](ch4/ch4.md) * [复合数据类](ch4/ch4.md)
* [數組](ch4/ch4-01.md) * [数组](ch4/ch4-01.md)
* [Slice](ch4/ch4-02.md) * [Slice](ch4/ch4-02.md)
* [Map](ch4/ch4-03.md) * [Map](ch4/ch4-03.md)
* [結構體](ch4/ch4-04.md) * [结构体](ch4/ch4-04.md)
* [JSON](ch4/ch4-05.md) * [JSON](ch4/ch4-05.md)
* [文本和HTML模](ch4/ch4-06.md) * [文本和HTML模](ch4/ch4-06.md)
* [](ch5/ch5.md) * [](ch5/ch5.md)
* [數聲](ch5/ch5-01.md) * [数声](ch5/ch5-01.md)
* [遞歸](ch5/ch5-02.md) * [递归](ch5/ch5-02.md)
* [多返](ch5/ch5-03.md) * [多返](ch5/ch5-03.md)
* [錯誤](ch5/ch5-04.md) * [错误](ch5/ch5-04.md)
* [](ch5/ch5-05.md) * [](ch5/ch5-05.md)
* [匿名函](ch5/ch5-06.md) * [匿名函](ch5/ch5-06.md)
* [變參數](ch5/ch5-07.md) * [变参数](ch5/ch5-07.md)
* [Deferred函](ch5/ch5-08.md) * [Deferred函](ch5/ch5-08.md)
* [Panic](ch5/ch5-09.md) * [Panic](ch5/ch5-09.md)
* [Recover捕獲異](ch5/ch5-10.md) * [Recover捕获异](ch5/ch5-10.md)
* [方法](ch6/ch6.md) * [方法](ch6/ch6.md)
* [方法](ch6/ch6-01.md) * [方法](ch6/ch6-01.md)
* [於指針對象的方法](ch6/ch6-02.md) * [于指针对象的方法](ch6/ch6-02.md)
* [嵌入結構體來擴展類](ch6/ch6-03.md) * [嵌入结构体来扩展类](ch6/ch6-03.md)
* [方法值和方法表](ch6/ch6-04.md) * [方法值和方法表](ch6/ch6-04.md)
* [示例: Bit數組](ch6/ch6-05.md) * [示例: Bit数组](ch6/ch6-05.md)
* [](ch6/ch6-06.md) * [](ch6/ch6-06.md)
* [接口](ch7/ch7.md) * [接口](ch7/ch7.md)
* [接口是合](ch7/ch7-01.md) * [接口是合](ch7/ch7-01.md)
* [接口](ch7/ch7-02.md) * [接口](ch7/ch7-02.md)
* [實現接口的](ch7/ch7-03.md) * [实现接口的](ch7/ch7-03.md)
* [flag.Value接口](ch7/ch7-04.md) * [flag.Value接口](ch7/ch7-04.md)
* [接口值](ch7/ch7-05.md) * [接口值](ch7/ch7-05.md)
* [sort.Interface接口](ch7/ch7-06.md) * [sort.Interface接口](ch7/ch7-06.md)
* [http.Handler接口](ch7/ch7-07.md) * [http.Handler接口](ch7/ch7-07.md)
* [error接口](ch7/ch7-08.md) * [error接口](ch7/ch7-08.md)
* [示例: 表式求值](ch7/ch7-09.md) * [示例: 表式求值](ch7/ch7-09.md)
* [類型斷](ch7/ch7-10.md) * [类型断](ch7/ch7-10.md)
* [於類型斷言識别錯誤類](ch7/ch7-11.md) * [于类型断言识别错误类](ch7/ch7-11.md)
* [過類型斷言査詢接口](ch7/ch7-12.md) * [过类型断言查询接口](ch7/ch7-12.md)
* [型分支](ch7/ch7-13.md) * [型分支](ch7/ch7-13.md)
* [示例: 基於標記的XML解](ch7/ch7-14.md) * [示例: 基于标记的XML解](ch7/ch7-14.md)
* [補充幾點](ch7/ch7-15.md) * [补充几点](ch7/ch7-15.md)
* [Goroutines和Channels](ch8/ch8.md) * [Goroutines和Channels](ch8/ch8.md)
* [Goroutines](ch8/ch8-01.md) * [Goroutines](ch8/ch8-01.md)
* [示例: 併發的Clock服](ch8/ch8-02.md) * [示例: 并发的Clock服](ch8/ch8-02.md)
* [示例: 併發的Echo服](ch8/ch8-03.md) * [示例: 并发的Echo服](ch8/ch8-03.md)
* [Channels](ch8/ch8-04.md) * [Channels](ch8/ch8-04.md)
* [併發的循](ch8/ch8-05.md) * [并发的循](ch8/ch8-05.md)
* [示例: 併發的Web爬](ch8/ch8-06.md) * [示例: 并发的Web爬](ch8/ch8-06.md)
* [select的多路](ch8/ch8-07.md) * [select的多路](ch8/ch8-07.md)
* [示例: 併發的字典遍](ch8/ch8-08.md) * [示例: 并发的字典遍](ch8/ch8-08.md)
* [併發的退出](ch8/ch8-09.md) * [并发的退出](ch8/ch8-09.md)
* [示例: 聊天服](ch8/ch8-10.md) * [示例: 聊天服](ch8/ch8-10.md)
* [共享量的併發](ch9/ch9.md) * [共享量的并发](ch9/ch9.md)
* [競爭條](ch9/ch9-01.md) * [竞争条](ch9/ch9-01.md)
* [sync.Mutex互斥](ch9/ch9-02.md) * [sync.Mutex互斥](ch9/ch9-02.md)
* [sync.RWMutex讀寫鎖](ch9/ch9-03.md) * [sync.RWMutex读写锁](ch9/ch9-03.md)
* [存同步](ch9/ch9-04.md) * [存同步](ch9/ch9-04.md)
* [sync.Once初始化](ch9/ch9-05.md) * [sync.Once初始化](ch9/ch9-05.md)
* [競爭條件檢測](ch9/ch9-06.md) * [竞争条件检测](ch9/ch9-06.md)
* [示例: 併發的非阻塞](ch9/ch9-07.md) * [示例: 并发的非阻塞](ch9/ch9-07.md)
* [Goroutines和](ch9/ch9-08.md) * [Goroutines和线](ch9/ch9-08.md)
* [包和工具](ch10/ch10.md) * [包和工具](ch10/ch10.md)
* [](ch10/ch10-01.md) * [](ch10/ch10-01.md)
* [入路](ch10/ch10-02.md) * [入路](ch10/ch10-02.md)
* [](ch10/ch10-03.md) * [](ch10/ch10-03.md)
* [導入聲](ch10/ch10-04.md) * [导入声](ch10/ch10-04.md)
* [包的匿名](ch10/ch10-05.md) * [包的匿名](ch10/ch10-05.md)
* [包和命名](ch10/ch10-06.md) * [包和命名](ch10/ch10-06.md)
* [工具](ch10/ch10-07.md) * [工具](ch10/ch10-07.md)
* [測試](ch11/ch11.md) * [测试](ch11/ch11.md)
* [go test](ch11/ch11-01.md) * [go test](ch11/ch11-01.md)
* [測試函數](ch11/ch11-02.md) * [测试函数](ch11/ch11-02.md)
* [測試覆蓋](ch11/ch11-03.md) * [测试覆盖](ch11/ch11-03.md)
* [準測試](ch11/ch11-04.md) * [准测试](ch11/ch11-04.md)
* [剖析](ch11/ch11-05.md) * [剖析](ch11/ch11-05.md)
* [示例函](ch11/ch11-06.md) * [示例函](ch11/ch11-06.md)
* [反射](ch12/ch12.md) * [反射](ch12/ch12.md)
* [何需要反射?](ch12/ch12-01.md) * [何需要反射?](ch12/ch12-01.md)
* [reflect.Type和reflect.Value](ch12/ch12-02.md) * [reflect.Type和reflect.Value](ch12/ch12-02.md)
* [Display遞歸打印](ch12/ch12-03.md) * [Display递归打印](ch12/ch12-03.md)
* [示例: 編碼S表](ch12/ch12-04.md) * [示例: 编码S表](ch12/ch12-04.md)
* [reflect.Value改值](ch12/ch12-05.md) * [reflect.Value改值](ch12/ch12-05.md)
* [示例: 解S表](ch12/ch12-06.md) * [示例: 解S表](ch12/ch12-06.md)
* [獲取結構體字段標識](ch12/ch12-07.md) * [获取结构体字段标识](ch12/ch12-07.md)
* [示一個類型的方法集](ch12/ch12-08.md) * [示一个类型的方法集](ch12/ch12-08.md)
* [幾點忠告](ch12/ch12-09.md) * [几点忠告](ch12/ch12-09.md)
* [層編](ch13/ch13.md) * [层编](ch13/ch13.md)
* [unsafe.Sizeof, Alignof 和 Offsetof](ch13/ch13-01.md) * [unsafe.Sizeof, Alignof 和 Offsetof](ch13/ch13-01.md)
* [unsafe.Pointer](ch13/ch13-02.md) * [unsafe.Pointer](ch13/ch13-02.md)
* [示例: 深度相等判](ch13/ch13-03.md) * [示例: 深度相等判](ch13/ch13-03.md)
* [cgo調用C代](ch13/ch13-04.md) * [cgo用C代](ch13/ch13-04.md)
* [幾點忠告](ch13/ch13-05.md) * [几点忠告](ch13/ch13-05.md)
* [](appendix/appendix.md) * [](appendix/appendix.md)
* [A原文勘](appendix/appendix-a-errata.md) * [A原文勘](appendix/appendix-a-errata.md)
* [B作者](appendix/appendix-b-author.md) * [B作者](appendix/appendix-b-author.md)
* [C文授](appendix/appendix-c-cpoyright.md) * [C文授](appendix/appendix-c-cpoyright.md)
* [D其它](appendix/appendix-d-translations.md) * [D其它](appendix/appendix-d-translations.md)

View File

@@ -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.) **p.9, ¶2:** for "can compared", read "can be compared". (Thanks to Antonio Macías Ojeda, 2015-10-22. Corrected in the second printing.)

View File

@@ -1,4 +1,4 @@
## 附B作者/ ## 附B作者/
### 英文作者 ### 英文作者
@@ -7,9 +7,9 @@
------- -------
### 中文 ### 中文
中文者 | 章 中文者 | 章
-------------------------------------- | ------------------------- -------------------------------------- | -------------------------
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章 `chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章 `Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章

View File

@@ -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)授.
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="../images/by-nc-sa-4.0-88x31.png"></img></a> <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="../images/by-nc-sa-4.0-88x31.png"></img></a>

View File

@@ -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] | ? 中文 | [《Go语言圣经》][gopl-zh] | 2016/2/1 | [chai2010][chai2010], [Xargin][Xargin], [CrazySssst][CrazySssst], [foreversmart][foreversmart] | ?
韓語 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ? 韩语 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ?
| [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ? | [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ?
蘭語 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ? 兰语 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ?
| [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ? | [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ?
葡萄牙 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ? 葡萄牙 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ?
中文簡體 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ? 中文简体 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ?
中文繁 | [Gotop Information (Taiwan)](http://www.gotop.com.tw/) | 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 [chai2010]: https://github.com/chai2010
[Xargin]: https://github.com/cch123 [Xargin]: https://github.com/cch123

View File

@@ -1,6 +1,6 @@
# 索引 # 索引
<!-- 索引有三列,每列度40字符 <!-- 索引有三列,每列度40字符
+------------------------------------- + ------------------------------------- + ------------ +------------------------------------- + ------------------------------------- + ------------
--> -->
@@ -85,7 +85,7 @@ TODO
### P400 ### P400
<!-- 索引有三列,每列度40字符 <!-- 索引有三列,每列度40字符
+------------------------------------- + ------------------------------------- + ------------ +------------------------------------- + ------------------------------------- + ------------
--> -->

View File

@@ -1,6 +1,6 @@
# 附 # 附
英文原版併沒有包含附部分,有一索引部分。中文版增加附部分主要用於收録一些和本書相關的內容,比如英文原版的勘(有些者可能會對照中文和英文原閲讀)、英文作者和中文者、文授權等內容。以後還可能會考慮增加一些習題解答相關的內容。 英文原版并没有包含附部分,有一索引部分。中文版增加附部分主要用于收录一些和本书相关的内容,比如英文原版的勘(有些者可能会对照中文和英文原阅读)、英文作者和中文者、文授权等内容。以后还可能会考虑增加一些习题解答相关的内容。
需要特别明的是,中文版附録併沒有包含英文原版的索引信息。因英文原版的索引信息主要是記録每個索引所在的英文面位置而中文版是以GitBook方式組織的html網頁形式,英文面位置轉爲章節位置可能更合理,不過這個會涉及到繁的手工操作。如果大家有更好的建議,請告知我 需要特别明的是,中文版附录并没有包含英文原版的索引信息。因英文原版的索引信息主要是记录每个索引所在的英文面位置而中文版是以GitBook方式组织的html网页形式,英文面位置转为章节位置可能更合理,不过这个会涉及到繁的手工操作。如果大家有更好的建议,请告知我

View File

@@ -1,21 +1,21 @@
## Go言起源 ## Go言起源
編程語言的演化就像生物物的演化似,一成功的編程語言的代一般都會繼承它祖先的優點;當然有時多種語言雜合也可能會産生令人驚訝的特性;有一些激的新特性可能併沒有先例。我可以通過觀察編程語言和硬件境是如何相互促、相互影的演化程而到很多。 编程语言的演化就像生物物的演化似,一成功的编程语言的代一般都会继承它祖先的优点;当然有时多种语言杂合也可能会产生令人惊讶的特性;有一些激的新特性可能并没有先例。我可以通过观察编程语言和硬件境是如何相互促、相互影的演化程而到很多。
展示了有哪些早期的編程語言對Go言的設計産生了重要影 展示了有哪些早期的编程语言对Go言的设计产生了重要影
![](../images/ch0-01.png) ![](../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" %} {% include "../links.md" %}

View File

@@ -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言源代本身就包含了构建规范。

View File

@@ -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 # 選擇工作目 $ export GOPATH=$HOME/gobook # 选择工作目
$ go get gopl.io/ch1/helloworld # 取/編譯/安 $ go get gopl.io/ch1/helloworld # 取/编译/安
$ $GOPATH/bin/helloworld # 行程序 $ $GOPATH/bin/helloworld # 行程序
Hello, 世界 # 是中文 Hello, 世界 # 是中文
``` ```
運行這些例子需要安Go1.5以上的版本。 运行这些例子需要安Go1.5以上的版本。
``` ```
$ go version $ go version
go version go1.5 linux/amd64 go version go1.5 linux/amd64
``` ```
如果使用其他的操作繫統, 請參考 https://golang.org/doc/install 提供的明安 如果使用其他的操作系统, 请参考 https://golang.org/doc/install 提供的明安

View File

@@ -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 Tourhttps://tour.golang.org ,是一個繫列的Go言入門敎程,它包含了多基本概念和結構相關的併可在線運行的互小程序。 Playground 建的 Go Tourhttps://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代

View File

@@ -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月 于 纽约

View File

@@ -1,6 +1,6 @@
## 1.1. Hello, World ## 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言一些核心理念。
<u><i>gopl.io/ch1/helloworld</i></u> <u><i>gopl.io/ch1/helloworld</i></u>
```go ```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 $ go run helloworld.go
``` ```
意外,這個命令會輸出: 意外,这个命令会输出:
``` ```
Hello, 世界 Hello, 世界
``` ```
Go言原生支持Unicode它可以理全世界任何言的文本。 Go言原生支持Unicode它可以理全世界任何言的文本。
如果不是一次性實驗,你肯定希望能夠編譯這個程序,保存編譯結果以備將來之用。可以用build子命令 如果不是一次性实验,你肯定希望能够编译这个程序,保存编译结果以备将来之用。可以用build子命令
``` ```
$ go build helloworld.go $ go build helloworld.go
``` ```
這個命令生成一個名爲helloworld的可行的二進製文件[^3],之你可以隨時運行它[^4],不需任何理[^5]。 这个命令生成一个名为helloworld的可行的二进制文件[^3],之你可以随时运行它[^4],不需任何理[^5]。
``` ```
$ ./helloworld $ ./helloworld
Hello, 世界 Hello, 世界
``` ```
中, 所有的示例代上都有一行標記,利用這些標記, 可以[gopl.io](http://gopl.io)站上本書源碼倉庫里獲取代 中, 所有的示例代上都有一行标记,利用这些标记, 可以[gopl.io](http://gopl.io)站上本书源码仓库里获取代
``` ```
gopl.io/ch1/helloworld 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 get golang.org/x/tools/cmd/goimports
``` ```
對於大多數用戶來説,下載、編譯包、運行測試用例、察看Go言的文等等常用功能都可以用go的工具完成。10.7節詳細介紹這些知 对于大多数用户来说,下载、编译包、运行测试用例、察看Go言的文等等常用功能都可以用go的工具完成。10.7节详细介绍这些知
[^1]: 本作者之一Brian W. Kernighan也是《The C Programming Language》一的作者。 [^1]: 本作者之一Brian W. Kernighan也是《The C Programming Language》一的作者。
[^2]: 靜態編譯 [^2]: 静态编译
[^3]: Windows繫統下生成的可行文件是helloworld.exe增加了.exe後綴名。 [^3]: Windows系统下生成的可行文件是helloworld.exe增加了.exe后缀名。
[^4]: 在Windows繫統下在命令行直接入helloworld.exe命令行。 [^4]: 在Windows系统下在命令行直接入helloworld.exe命令行。
[^5]: 因爲靜態編譯,所以不用心在繫統庫更新的時候衝突,幸福感滿滿 [^5]: 因为静态编译,所以不用心在系统库更新的时候冲突,幸福感满满
[^6]: 需要先安Git或Hg之的版本管理工具,併將對應的命令添加到PATH環境變量中。序言已提及,需要先置好GOPATH環境變量,下的代碼會放在`$GOPATH/src/gopl.io/ch1/helloworld` [^6]: 需要先安Git或Hg之的版本管理工具,并将对应的命令添加到PATH环境变量中。序言已提及,需要先置好GOPATH环境变量,下的代码会放在`$GOPATH/src/gopl.io/ch1/helloworld`
[^7]: C繫語言差不多都這樣 [^7]: C系语言差不多都这样
[^8]: Go語言編譯過程沒有警告信息,爭議特性之一。 [^8]: Go语言编译过程没有警告信息,争议特性之一。
[^9]: 最好是定一下范。 [^9]: 最好是定一下范。
[^10]: 比如行末是標識符、整、浮點數、虛數、字符或字符串文字、關鍵`break``continue``fallthrough``return`中的一個、運算符和分隔符`++``--``)``]``}`中的一 [^10]: 比如行末是标识符、整、浮点数、虚数、字符或字符串文字、关键`break``continue``fallthrough``return`中的一个、运算符和分隔符`++``--``)``]``}`中的一
[^11]: 以+尾的話不會被插入分分隔符但是以x尾的話則會被分分隔符,從而導致編譯錯誤 [^11]: 以+尾的话不会被插入分分隔符但是以x尾的话则会被分分隔符,从而导致编译错误
[^12]: 這個格式化工具有任何可以調整代格式的參數Go言就是這麽任性。 [^12]: 这个格式化工具有任何可以整代格式的参数Go言就是这么任性。
[^13]: 也致了Go言的TIOBE排名低,因缺少撕逼的話題 [^13]: 也致了Go言的TIOBE排名低,因缺少撕逼的话题

View File

@@ -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工具格式化按照字母顺序对包名排序。(示例有多版本,我们会对示例编号, 这样可以明确当前正在讨论的是哪。)
<u><i>gopl.io/ch1/echo1</i></u> <u><i>gopl.io/ch1/echo1</i></u>
```go ```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 ```go
sep + os.Args[i] sep + os.Args[i]
``` ```
表示接字符串sep和os.Args。程序中使用的句: 表示接字符串sep和os.Args。程序中使用的句:
```go ```go
s += sep + os.Args[i] s += sep + os.Args[i]
``` ```
是一條*賦值語句*, s的值跟sepos.Args[i]連接後賦值迴s價於 是一条*赋值语句*, s的值跟sepos.Args[i]连接后赋值回s价于
```go ```go
s = s + sep + os.Args[i] s = s + sep + os.Args[i]
``` ```
算符`+=`賦值運算符assignment operator種數值運算符或邏輯運算符,如`+``*`,都有對應的賦值運算符。 算符`+=`赋值运算符assignment operator种数值运算符或逻辑运算符,如`+``*`,都有对应的赋值运算符。
echo程序可以每循一次出一個參數,這個版本是不地把新文本追加到末尾來構造字符串。字符串s開始爲空,卽值爲"",每次循環會添加一些文本;第一次迭代之後,還會再插入一空格,因此循環結束時每個參數中間都有一空格。是一二次加工quadratic process當參數數量龐大時,開銷很大,但是對於echo這種情形不大可能出。本章會介紹echo的若榦改進版,下一章解低效問題 echo程序可以每循一次出一个参数,这个版本是不地把新文本追加到末尾来构造字符串。字符串s开始为空,即值为"",每次循环会添加一些文本;第一次迭代之后,还会再插入一空格,因此循环结束时每个参数中间都有一空格。是一二次加工quadratic process当参数数量庞大时,开销很大,但是对于echo这种情形不大可能出。本章会介绍echo的若干改进版,下一章解低效问题
索引量i在for循的第一部分中定。符`:=`是*短變量聲明*short variable declaration的一部分, 是定義一個或多個變量併根據它們的初始值爲這些變量賦予適當類型的句。下一章有方面更多明。 索引量i在for循的第一部分中定。符`:=`是*短变量声明*short variable declaration的一部分, 是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的句。下一章有方面更多明。
自增`i++``i`加1`i += 1`以及`i = i + 1`都是等的。對應的還`i--``i`1。它們是語而不像C的其它言那是表式。所以`j = i++`非法,而且++和--都能放在量名面,因此`--i`也非法。 自增`i++``i`加1`i += 1`以及`i = i + 1`都是等的。对应的还`i--``i`1。它们是语而不像C的其它言那是表式。所以`j = i++`非法,而且++和--都能放在量名面,因此`--i`也非法。
Go語言隻有for循環這一種循環語句。for循有多形式,其中一如下所示: Go语言只有for循环这一种循环语句。for循有多形式,其中一如下所示:
```go ```go
for initialization; condition; post { for initialization; condition; post {
@@ -68,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 ```go
// a traditional "while" loop // a traditional "while" loop
@@ -81,7 +81,7 @@ for condition {
} }
``` ```
如果`condition`也省略了,像下面這樣 如果`condition`也省略了,像下面这样
```go ```go
// a traditional infinite loop // a traditional infinite loop
@@ -90,9 +90,9 @@ for {
} }
``` ```
這就變成一個無限循環,盡管如此,可以用其他方式止循, 如一`break``return`句。 这就变成一个无限循环,尽管如此,可以用其他方式止循, 如一`break``return`句。
`for`的另一形式, 在某種數據類型的區間range上遍,如字符串或切片。`echo`的第二版本展示了這種形式: `for`的另一形式, 在某种数据类型的区间range上遍,如字符串或切片。`echo`的第二版本展示了这种形式:
<u><i>gopl.io/ch1/echo2</i></u> <u><i>gopl.io/ch1/echo2</i></u>
```go ```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 ```go
s := "" s := ""
@@ -126,11 +126,11 @@ var s = ""
var s string = "" var s string = ""
``` ```
用哪不用哪種,爲什麽呢?第一形式,是一條短變量聲明,最簡潔,但能用在函數內部,而不能用於包變量。第二形式依賴於字符串的默初始化零值機製,被初始化""。第三形式用得很少,除非同時聲明多個變量。第四形式式地標明變量的型,當變量類型與初值型相同時,類型冗,但如果兩者類型不同,變量類型就必了。實踐中一般使用前兩種形式中的某,初始值重要的話就顯式地指定量的型,否使用式初始化。 用哪不用哪种,为什么呢?第一形式,是一条短变量声明,最简洁,但能用在函数内部,而不能用于包变量。第二形式依赖于字符串的默初始化零值机制,被初始化""。第三形式用得很少,除非同时声明多个变量。第四形式式地标明变量的型,当变量类型与初值型相同时,类型冗,但如果两者类型不同,变量类型就必了。实践中一般使用前两种形式中的某,初始值重要的话就显式地指定量的型,否使用式初始化。
如前文所述,每次循迭代字符串s的容都更新。`+=`接原字符串、空格和下個參數,産生新字符串, 把它賦值給`s``s`來的內容已不再使用,將在適當時機對它進行垃圾收。 如前文所述,每次循迭代字符串s的容都更新。`+=`接原字符串、空格和下个参数,产生新字符串, 把它赋值给`s``s`来的内容已不再使用,将在适当时机对它进行垃圾收。
如果接涉及的數據量很大,這種方式代高昂。一種簡單且高效的解方案是使用`strings`包的`Join` 如果接涉及的数据量很大,这种方式代高昂。一种简单且高效的解方案是使用`strings`包的`Join`
<u><i>gopl.io/ch1/echo3</i></u> <u><i>gopl.io/ch1/echo3</i></u>
```go ```go
@@ -139,16 +139,16 @@ func main() {
} }
``` ```
,如果不關心輸出格式,想看看出值,或許隻是爲了調試,可以用`Println`爲我們格式化出。 ,如果不关心输出格式,想看看出值,或许只是为了调试,可以用`Println`为我们格式化出。
```go ```go
fmt.Println(os.Args[1:]) 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展示了如何写标准测试程序,以得到系统性的性能评测。)

View File

@@ -1,8 +1,8 @@
## 1.3. 找重的行 ## 1.3. 找重的行
文件做拷、打印、索、排序、統計或類似事情的程序都有一差不多的程序結構:一個處理輸入的循,在每元素上執行計算處理,在理的同或最後産生輸出。我們會展示一個名爲`dup`的程序的三版本;靈感來自於Unix的`uniq`命令,其找相的重行。程序使用的結構和包是個參考范例,可以方便地改。 文件做拷、打印、索、排序、统计或类似事情的程序都有一差不多的程序结构:一个处理输入的循,在每元素上执行计算处理,在理的同或最后产生输出。我们会展示一个名为`dup`的程序的三版本;灵感来自于Unix的`uniq`命令,其找相的重行。程序使用的结构和包是个参考范例,可以方便地改。
`dup`的第一版本打印標準輸入中多次出的行,以重複次數開頭。該程序引入`if`句,`map`數據類型以及`bufio`包。 `dup`的第一版本打印标准输入中多次出的行,以重复次数开头。该程序引入`if`句,`map`数据类型以及`bufio`包。
<u><i>gopl.io/ch1/dup1</i></u> <u><i>gopl.io/ch1/dup1</i></u>
```go ```go
@@ -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 ```go
line := input.Text() line := input.Text()
counts[line] = counts[line] + 1 counts[line] = counts[line] + 1
``` ```
`map`中不含某個鍵時不用心,首次到新行,等號右邊的表`counts[line]`的值將被計算爲其類型的零值,對於int`0。 `map`中不含某个键时不用心,首次到新行,等号右边的表`counts[line]`的值将被计算为其类型的零值,对于int`0。
了打印果,我使用了基`range`的循環,併在`counts`這個`map`上迭代。跟之前似,每次迭代得到兩個結果,和其在`map`中對應的值。`map`的迭代順序併不確定,從實踐來看,該順序隨機,每次行都會變化。這種設計是有意之的,因能防止程序依特定遍歷順序,而這是無法保的。 了打印果,我使用了基`range`的循环,并在`counts`这个`map`上迭代。跟之前似,每次迭代得到两个结果,和其在`map`中对应的值。`map`的迭代顺序并不确定,从实践来看,该顺序随机,每次行都会变化。这种设计是有意之的,因能防止程序依特定遍历顺序,而这是无法保的。
繼續來看`bufio`包,它使處理輸入和出方便又高效。`Scanner`型是包最有用的特性之一,它讀取輸入併將其拆成行或單詞;通常是理行形式的入最簡單的方法。 继续来看`bufio`包,它使处理输入和出方便又高效。`Scanner`型是包最有用的特性之一,它读取输入并将其拆成行或单词;通常是理行形式的入最简单的方法。
程序使用短變量聲明創建`bufio.Scanner`型的量`input`。 程序使用短变量声明创建`bufio.Scanner`型的量`input`。
``` ```
input := bufio.NewScanner(os.Stdin) 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 十進製整數 %d 十进制整数
%x, %o, %b 十六進製,八進製,二進製整數 %x, %o, %b 十六进制,八进制,二进制整数
%f, %g, %e 浮點數 3.141593 3.141592653589793 3.141593e+00 %f, %g, %e 浮点数 3.141593 3.141592653589793 3.141593e+00
%t 布true或false %t 布true或false
%c 字符rune (Unicode碼點) %c 字符rune (Unicode码点)
%s 字符串 %s 字符串
%q 帶雙引號的字符串"abc"或帶單引號的字符'c' %q 带双引号的字符串"abc"或带单引号的字符'c'
%v 量的自然形式natural format %v 量的自然形式natural format
%T 量的 %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`打开各个具名文件,操作它
<u><i>gopl.io/ch1/dup2</i></u> <u><i>gopl.io/ch1/dup2</i></u>
```go ```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`函
<u><i>gopl.io/ch1/dup3</i></u> <u><i>gopl.io/ch1/dup3</i></u>
```go ```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`,出现重复的行打印文件名

View File

@@ -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) ![](../images/ch1-01.png)
譯註:要看這個程序的果,需要將標準輸出重定向到一GIF像文件(使用 `./lissajous > output.gif` 命令。下面是GIF圖像動畵效果: 译注:要看这个程序的果,需要将标准输出重定向到一GIF像文件(使用 `./lissajous > output.gif` 命令。下面是GIF图像动画效果:
![](../images/ch1-01.gif) ![](../images/ch1-01.gif)
段代里我用了一些新的結構包括conststruct結構體類型,複合聲明。和我們舉的其它的例子不太一樣,這一個例子包含了浮點數運算。些概念我們隻在這里簡單地説明一下,之的章節會更詳細地講解。 段代里我用了一些新的结构包括conststruct结构体类型,复合声明。和我们举的其它的例子不太一样,这一个例子包含了浮点数运算。些概念我们只在这里简单地说明一下,之的章节会更详细地讲解。
<u><i>gopl.io/ch1/lissajous</i></u> <u><i>gopl.io/ch1/lissajous</i></u>
```go ```go
@@ -66,25 +66,25 @@ func lissajous(out io.Writer) {
``` ```
當我們import了一包路包含有多個單詞的package比如image/colorimage和color兩個單詞),通常我們隻需要用最後那個單詞表示這個包就可以。所以當我們寫color.White時,這個變量指向的是image/color包里的同理gif.GIF是屬於image/gif包里的量。 当我们import了一包路包含有多个单词的package比如image/colorimage和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 $ go build gopl.io/ch1/lissajous
$ ./lissajous >out.gif $ ./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的第三个参数,看看显示结果吧。

View File

@@ -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 curlcurl提供的功能更爲複雜豐富,這里隻編寫最簡單的樣例。這個樣例之後還會多次被用到。 了最简单地展示基HTTP取信息的方式,下面出一示例程序fetch这个程序将获取对应的url并将其源文本打印出来;这个例子的灵感来源于curl工具译注unix下的一个用来发http求的工具,具可以man curlcurl提供的功能更为复杂丰富,这里只编写最简单的样例。这个样例之后还会多次被用到。
<u><i>gopl.io/ch1/fetch</i></u> <u><i>gopl.io/ch1/fetch</i></u>
```go ```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 $ go build gopl.io/ch1/fetch
@@ -45,24 +45,24 @@ $ ./fetch http://gopl.io
... ...
``` ```
HTTP求如果失了的話,會得到下面這樣的結果: HTTP求如果失了的话,会得到下面这样的结果:
``` ```
$ ./fetch http://bad.gopl.io $ ./fetch http://bad.gopl.io
fetch: Get http://bad.gopl.io: dial tcp: lookup bad.gopl.io: no such host fetch: Get http://bad.gopl.io: dial tcp: lookup bad.gopl.io: no such host
``` ```
譯註:在大天朝的網絡環境下很容易重現這種錯誤下面是Windows下行得到的錯誤信息: 译注:在大天朝的网络环境下很容易重现这种错误下面是Windows下行得到的错误信息:
``` ```
$ go run main.go http://gopl.io $ go run main.go http://gopl.io
fetch: Get http://gopl.io: dial tcp: lookup gopl.io: getaddrinfow: No such host is known. 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量得到该状态码

View File

@@ -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程序只会打印取的容大小和经过的时间,不像之前那打印取的容。
<u><i>gopl.io/ch1/fetchall</i></u> <u><i>gopl.io/ch1/fetchall</i></u>
```go ```go
@@ -48,7 +48,7 @@ func fetch(url string, ch chan<- string) {
} }
``` ```
下面使用fetchall來請求幾個地址: 下面使用fetchall来请求几个地址:
``` ```
$ go build gopl.io/ch1/fetchall $ go build gopl.io/ch1/fetchall
@@ -59,10 +59,10 @@ $ ./fetchall https://golang.org http://gopl.io https://godoc.org
0.48s elapsed 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执行两遍请求查看两次时间是否有大的差别且每次取到的响应内容是否一致改本中的程序将响应结果输以便于进行对

View File

@@ -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"。
<u><i>gopl.io/ch1/server1</i></u> <u><i>gopl.io/ch1/server1</i></u>
```go ```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 run src/gopl.io/ch1/server1/main.go &
``` ```
在可以通命令行來發送客戶端請求了: 在可以通命令行来发送客户端请求了:
``` ```
$ go build gopl.io/ch1/fetch $ go build gopl.io/ch1/fetch
@@ -42,11 +42,11 @@ $ ./fetch http://localhost:8000/help
URL.Path = "/help" URL.Path = "/help"
``` ```
可以直接在瀏覽器里訪問這個URL得到返迴結果,如1.2 可以直接在浏览器里访问这个URL得到返回结果,如1.2
![](../images/ch1-02.png) ![](../images/ch1-02.png)
這個服務的基礎上疊加特性是很容易的。一種比較實用的改是爲訪問的url添加某種狀態。比如,下面這個版本出了同樣的內容,但是會對請求的次數進行計算;URL的請求結果會包含各URL被訪問的總次數,直接/count這個URL的訪問要除外。 这个服务的基础上叠加特性是很容易的。一种比较实用的改是为访问的url添加某种状态。比如,下面这个版本出了同样的内容,但是会对请求的次数进行计算;URL的请求结果会包含各URL被访问的总次数,直接/count这个URL的访问要除外。
<u><i>gopl.io/ch1/server2</i></u> <u><i>gopl.io/ch1/server2</i></u>
```go ```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数据都打印出来,这样可以使检查和调试这个服务更为方便:
<u><i>gopl.io/ch1/server3</i></u> <u><i>gopl.io/ch1/server3</i></u>
```go ```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 GET /?q=query HTTP/1.1
@@ -119,7 +119,7 @@ RemoteAddr = "127.0.0.1:59911"
Form["q"] = ["query"] Form["q"] = ["query"]
``` ```
可以看到里的ParseForm被嵌套在了if句中。Go言允許這樣的一個簡單的語句結果作爲循環的變量聲明出在if句的最前面,這一點對錯誤處理很有用。我們還可以像下面這樣寫(當然看起來就長了一些): 可以看到里的ParseForm被嵌套在了if句中。Go言允许这样的一个简单的语句结果作为循环的变量声明出在if句的最前面,这一点对错误处理很有用。我们还可以像下面这样写(当然看起来就长了一些):
```go ```go
err := r.ParseForm() err := r.ParseForm()
@@ -128,13 +128,13 @@ if err != nil {
} }
``` ```
用if和ParseForm合可以讓代碼更加簡單,併且可以限err這個變量的作用域,這麽做是很不的。我們會在2.7節中講解作用域。 用if和ParseForm合可以让代码更加简单,并且可以限err这个变量的作用域,这么做是很不的。我们会在2.7节中讲解作用域。
些程序中,我看到了很多不同的型被出到標準輸出流中。比如前面的fetch程序把HTTP的響應數據拷貝到了os.Stdoutlissajous程序里我們輸出的是一文件。fetchall程序完全忽略到了HTTP的響應Body隻是計算了一下響應Body的大小這個程序中把響應Body拷到了ioutil.Discard。在本的web服器程序中是用fmt.Fprintf直接到了http.ResponseWriter中。 些程序中,我看到了很多不同的型被出到标准输出流中。比如前面的fetch程序把HTTP的响应数据拷贝到了os.Stdoutlissajous程序里我们输出的是一文件。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 ```Go
handler := func(w http.ResponseWriter, r *http.Request) { handler := func(w http.ResponseWriter, r *http.Request) {
@@ -143,7 +143,7 @@ handler := func(w http.ResponseWriter, r *http.Request) {
http.HandleFunc("/", handler) http.HandleFunc("/", handler)
``` ```
或者另一種等價形式: 或者另一种等价形式:
```Go ```Go
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 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) ![](../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的详细说明。

View File

@@ -1,8 +1,8 @@
## 1.8. 本章要 ## 1.8. 本章要
本章Go言做了一些介Go言很多方面在有限的篇幅中法覆到。本節會把沒有講到的容也做一些簡單的介紹,這樣讀者在到完整的容之前,可以有個簡單的印象。 本章Go言做了一些介Go言很多方面在有限的篇幅中法覆到。本节会把没有讲到的容也做一些简单的介绍,这样读者在到完整的容之前,可以有个简单的印象。
**控流:** 在本章我們隻介紹了if控和for但是有提到switch多路選擇。這里是一個簡單的switch的例子 **控流:** 在本章我们只介绍了if控和for但是有提到switch多路选择。这里是一个简单的switch的例子
```go ```go
switch coinflip() { 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 ```go
func Signum(x int) int { 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 ```go
type Point struct { type Point struct {
@@ -47,15 +47,15 @@ type Point struct {
var p Point 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 $ go doc http.ListenAndServe
@@ -66,7 +66,7 @@ func ListenAndServe(addr string, handler Handler) error
... ...
``` ```
**註釋**之前已提到了在源文件的開頭寫的註釋是這個源文件的文。在每一個函數之前寫一個説明函數行爲的註釋也是一個好習慣。這些慣例很重要,因爲這些內容會被像godoc這樣的工具檢測到,且在行命令時顯示這些註釋。具可以考10.7.4。 **注释**之前已提到了在源文件的开头写的注释是这个源文件的文。在每一个函数之前写一个说明函数行为的注释也是一个好习惯。这些惯例很重要,因为这些内容会被像godoc这样的工具检测到,且在行命令时显示这些注释。具可以考10.7.4。
多行註釋可以用 `/* ... */` 包裹,和其它大多數語言一。在文件一開頭的註釋一般都是這種形式,或者一大段的解性的註釋文字也會被這符號包住,避免每一行都需要加//。在註釋中//和/*是沒什麽意義的,所以不要在註釋中再嵌入註釋 多行注释可以用 `/* ... */` 包裹,和其它大多数语言一。在文件一开头的注释一般都是这种形式,或者一大段的解性的注释文字也会被这符号包住,避免每一行都需要加//。在注释中//和/*是没什么意义的,所以不要在注释中再嵌入注释

View File

@@ -1,5 +1,5 @@
# 第1章 入 # 第1章 入
本章介Go言的基礎組件。本章提供了足的信息和示例程序,希望可以幫你盡快入, 出有用的程序。本章和之後章節的示例程序都針對你可能遇到的現實案例。先了解幾個Go程序涉及的主題從簡單的文件理、圖像處理到互聯網客戶端和服務端併發。當然,第一章不會解釋細枝末,但用些程序來學習一門新語言還是很有效的。 本章介Go言的基础组件。本章提供了足的信息和示例程序,希望可以帮你尽快入, 出有用的程序。本章和之后章节的示例程序都针对你可能遇到的现实案例。先了解几个Go程序涉及的主题从简单的文件理、图像处理到互联网客户端和服务端并发。当然,第一章不会解释细枝末,但用些程序来学习一门新语言还是很有效的。
學習一門新語言時,會有一自然的向, 按照自己熟悉的言的套路寫新語言程序。學習Go言的程中,警惕這種想法,量别這麽做。我們會演示怎麽寫好Go言程序,所以使用本的代碼作爲你自己程序的指南。 学习一门新语言时,会有一自然的向, 按照自己熟悉的言的套路写新语言程序。学习Go言的程中,警惕这种想法,量别这么做。我们会演示怎么写好Go言程序,所以使用本的代码作为你自己程序的指南。

View File

@@ -1,9 +1,9 @@
## 10.1. 包 ## 10.1. 包
任何包繫統設計的目的都是爲了簡化大型程序的設計和維護工作,通過將一組相關的特性放進一個獨立的元以便理解和更新,在每個單元更新的同保持和程序中其它元的相對獨立性。這種模塊化的特性允許每個包可以被其它的不同目共享和重用,在目范圍內、甚至全球范圍統一的分發和複用。 任何包系统设计的目的都是为了简化大型程序的设计和维护工作,通过将一组相关的特性放进一个独立的元以便理解和更新,在每个单元更新的同保持和程序中其它元的相对独立性。这种模块化的特性允许每个包可以被其它的不同目共享和重用,在目范围内、甚至全球范围统一的分发和复用。
包一般都定了一不同的名字空間用於它內部的每個標識符的訪問。每名字空間關聯到一特定的包,讓我們給類型、函數等選擇簡短明了的名字,這樣可以避免在我使用它們的時候減少和其它部分名字的突。 包一般都定了一不同的名字空间用于它内部的每个标识符的访问。每名字空间关联到一特定的包,让我们给类型、函数等选择简短明了的名字,这样可以避免在我使用它们的时候减少和其它部分名字的突。
個包還通過控製包內名字的可性和是否導出來實現封裝特性。通過限製包成的可見性併隱藏包API的具體實現,將允許包的維護者在不影外部包用的前提下調整包的內部實現。通過限製包內變量的可性,可以強製用戶通過某些特定函數來訪問和更新內部變量,這樣可以保證內部變量的一致性和併發時的互斥束。 个包还通过控制包内名字的可性和是否导出来实现封装特性。通过限制包成的可见性并隐藏包API的具体实现,将允许包的维护者在不影外部包用的前提下整包的内部实现。通过限制包内变量的可性,可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥束。
當我們脩改了一源文件,我們必須重新編譯該源文件對應的包和所有依賴該包的其他包。使是從頭構Go語言編譯器的編譯速度也明顯快於其它編譯語言。Go言的閃電般的編譯速度主要得益於三個語言特性。第一,所有入的包必在每文件的開頭顯式聲明,這樣的話編譯器就有必要取和分析整源文件來判斷包的依賴關繫。第二,禁止包的環狀依賴,因爲沒有循環依賴,包的依賴關繫形成一有向無環圖,每包可以被獨立編譯,而且很可能是被併發編譯。第三點,編譯後包的目文件不僅僅記録包本身的出信息,目文件同時還記録了包的依賴關繫。因此,在編譯一個包的候,編譯器隻需要取每直接入包的目文件,而不需要遍所有依的的文件(譯註:很多都是重複的間接依)。 当我们修改了一源文件,我们必须重新编译该源文件对应的包和所有依赖该包的其他包。使是从头构Go语言编译器的编译速度也明显快于其它编译语言。Go言的闪电般的编译速度主要得益于三个语言特性。第一,所有入的包必在每文件的开头显式声明,这样的话编译器就有必要取和分析整源文件来判断包的依赖关系。第二,禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一有向无环图,每包可以被独立编译,而且很可能是被并发编译。第三点,编译后包的目文件不仅仅记录包本身的出信息,目文件同时还记录了包的依赖关系。因此,在编译一个包的候,编译器只需要取每直接入包的目文件,而不需要遍所有依的的文件(译注:很多都是重复的间接依)。

View File

@@ -1,6 +1,6 @@
## 10.2. 入路 ## 10.2. 入路
包是由一全局唯一的字符串所標識的導入路定位。出在import句中的入路也是字符串。 包是由一全局唯一的字符串所标识的导入路定位。出在import句中的入路也是字符串。
```Go ```Go
import ( 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驱动

View File

@@ -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 ```Go
package main 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。

View File

@@ -1,6 +1,6 @@
## 10.4. 導入聲 ## 10.4. 导入声
可以在一Go言源文件包聲明語句之,其它非導入聲明語句之前,包含零到多個導入包聲明語句。每個導入聲明可以單獨指定一個導入路,也可以通過圓括號同時導入多個導入路。下面兩個導入形式是等的,但是第二形式更爲常見 可以在一Go言源文件包声明语句之,其它非导入声明语句之前,包含零到多个导入包声明语句。每个导入声明可以单独指定一个导入路,也可以通过圆括号同时导入多个导入路。下面两个导入形式是等的,但是第二形式更为常见
```Go ```Go
import "fmt" import "fmt"
@@ -12,7 +12,7 @@ import (
) )
``` ```
入的包之可以通添加空行來分組;通常將來自不同組織的包自分。包的導入順序無關緊要,但是在每個分組中一般會根據字符串序排列。gofmt和goimports工具都可以不同分組導入的包立排序。) 入的包之可以通添加空行来分组;通常将来自不同组织的包自分。包的导入顺序无关紧要,但是在每个分组中一般会根据字符串序排列。gofmt和goimports工具都可以不同分组导入的包立排序。)
```Go ```Go
import ( import (
@@ -25,7 +25,7 @@ import (
) )
``` ```
如果我想同時導入兩個有着名字相同的包例如math/rand包和crypto/rand包麽導入聲明必至少爲一個同名包指定一新的包名以避免突。叫做入包的重命名。 如果我想同时导入两个有着名字相同的包例如math/rand包和crypto/rand包么导入声明必至少为一个同名包指定一新的包名以避免突。叫做入包的重命名。
```Go ```Go
import ( import (
@@ -34,8 +34,8 @@ import (
) )
``` ```
入包的重命名隻影響當前的源文件。其它的源文件如果入了相同的包,可以用入包原本默的名字或重命名另一完全不同的名字。 入包的重命名只影响当前的源文件。其它的源文件如果入了相同的包,可以用入包原本默的名字或重命名另一完全不同的名字。
入包重命名是一有用的特性,它不僅僅隻是爲了解名字突。如果入的一包名很笨重,特别是在一些自生成的代中,這時候用一個簡短名稱會更方便。選擇用簡短名重命名入包候最好一,以避免包名混亂。選擇另一包名稱還可以助避免和本地普通量名産生衝突。例如,如果文件中已有了一個名爲path的量,那麽我們可以"path"標準包重命名pathpkg。 入包重命名是一有用的特性,它不仅仅只是为了解名字突。如果入的一包名很笨重,特别是在一些自生成的代中,这时候用一个简短名称会更方便。选择用简短名重命名入包候最好一,以避免包名混乱。选择另一包名称还可以助避免和本地普通量名产生冲突。例如,如果文件中已有了一个名为path的量,那么我们可以"path"标准包重命名pathpkg。
個導入聲明語句都明指定了前包和被入包之的依賴關繫。如果遇到包循環導入的情Go言的建工具將報告錯誤 个导入声明语句都明指定了前包和被入包之的依赖关系。如果遇到包循环导入的情Go言的建工具将报告错误

View File

@@ -1,14 +1,14 @@
## 10.5. 包的匿名 ## 10.5. 包的匿名
如果隻是導入一包而不使用入的包將會導致一個編譯錯誤。但是有候我們隻是想利用入包而生的副作用:它會計算包級變量的初始化表式和執行導入包的init初始化函§2.6.2)。這時候我需要抑“unused import”編譯錯誤,我可以用下劃線`_`重命名入的包。像往常一,下劃線`_`空白標識符,不能被訪問 如果只是导入一包而不使用入的包将会导致一个编译错误。但是有候我们只是想利用入包而生的副作用:它会计算包级变量的初始化表式和执行导入包的init初始化函§2.6.2)。这时候我需要抑“unused import”编译错误,我可以用下划线`_`重命名入的包。像往常一,下划线`_`空白标识符,不能被访问
```Go ```Go
import _ "image/png" // register PNG decoder import _ "image/png" // register PNG decoder
``` ```
這個被稱爲包的匿名入。它通常是用來實現一個編譯時機製,然後通過在main主程序入口選擇性地入附加的包。首先,讓我們看看如何使用特性,然再看看它是如何工作的。 这个被称为包的匿名入。它通常是用来实现一个编译时机制,然后通过在main主程序入口选择性地入附加的包。首先,让我们看看如何使用特性,然再看看它是如何工作的。
標準庫的image像包包含了一`Decode`,用於從`io.Reader`接口讀取數據併解碼圖像,它調用底層註冊的圖像解碼器來完成任,然後返迴image.Image型的像。使用`image.Decode`很容易編寫一個圖像格式的轉換工具,取一格式的像,然後編碼爲另一種圖像格式: 标准库的image像包包含了一`Decode`,用于从`io.Reader`接口读取数据并解码图像,它用底层注册的图像解码器来完成任,然后返回image.Image型的像。使用`image.Decode`很容易编写一个图像格式的转换工具,取一格式的像,然后编码为另一种图像格式:
<u><i>gopl.io/ch10/jpeg</i></u> <u><i>gopl.io/ch10/jpeg</i></u>
```Go ```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 $ go build gopl.io/ch3/mandelbrot
@@ -51,7 +51,7 @@ $ ./mandelbrot | ./jpeg >mandelbrot.jpg
Input format = png Input format = png
``` ```
意image/png包的匿名導入語句。如果沒有這一行句,程序依然可以編譯和運行,但是它不能正確識别和解PNG格式的像: 意image/png包的匿名导入语句。如果没有这一行句,程序依然可以编译和运行,但是它不能正确识别和解PNG格式的像:
``` ```
$ go build gopl.io/ch10/jpeg $ go build gopl.io/ch10/jpeg
@@ -59,7 +59,7 @@ $ ./mandelbrot | ./jpeg >mandelbrot.jpg
jpeg: image: unknown format 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 ```Go
package png // image/png package png // image/png
@@ -73,9 +73,9 @@ func init() {
} }
``` ```
的效果是,主程序需要匿名入特定圖像驅動包就可以用image.Decode解碼對應格式的像了。 的效果是,主程序需要匿名入特定图像驱动包就可以用image.Decode解码对应格式的像了。
數據庫包database/sql也是采用了似的技術,讓用戶可以根自己需要選擇導入必要的數據庫驅動。例如: 数据库包database/sql也是采用了似的技术,让用户可以根自己需要选择导入必要的数据库驱动。例如:
```Go ```Go
import ( import (
@@ -89,6 +89,6 @@ db, err = sql.Open("mysql", dbname) // OK
db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3" 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** 設計一個通用的壓縮文件取框架,用來讀取ZIParchive/zip和POSIX tararchive/tar格式壓縮的文。使用似上面的註冊技術來擴展支持不同的壓縮格式,然後根據需要通匿名導入選擇導入要支持的壓縮格式的驅動包。 **练习 10.2** 设计一个通用的压缩文件取框架,用来读取ZIParchive/zip和POSIX tararchive/tar格式压缩的文。使用似上面的注册技术来扩展支持不同的压缩格式,然后根据需要通匿名导入选择导入要支持的压缩格式的驱动包。

View File

@@ -1,22 +1,22 @@
## 10.6. 包和命名 ## 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 bytes.Equal flag.Int http.Get json.Marshal
``` ```
可以看到一些常用的命名模式。strings包提供了和字符串相關的諸多操作: 可以看到一些常用的命名模式。strings包提供了和字符串相关的诸多操作:
```Go ```Go
package strings package strings
@@ -30,9 +30,9 @@ type Reader struct{ /* ... */ }
func NewReader(s string) *Reader 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 ```Go
package rand // "math/rand" package rand // "math/rand"
@@ -41,6 +41,6 @@ type Rand struct{ /* ... */ }
func New(source Source) *Rand 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等。

View File

@@ -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 $ export GOPATH=$HOME/gobook
$ go get gopl.io/... $ go get gopl.io/...
``` ```
你用前面介的命令下載本書全部的例子源碼之後,你的前工作的目録結構應該是這樣的: 你用前面介的命令下载本书全部的例子源码之后,你的前工作的目录结构应该是这样的:
``` ```
GOPATH/ 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或windowsGOARCH環境變量用指定理器的例如amd64、386或arm等。然GOPATH環境變量是唯一必需要置的,但是其它環境變量也會偶爾用到。 其中`go env`命令用于查看Go音工具涉及的所有环境变量的值,包括未设置环境变量的默值。GOOS环境变量用指定目操作系统例如android、linux、darwin或windowsGOARCH环境变量用指定理器的例如amd64、386或arm等。然GOPATH环境变量是唯一必需要置的,但是其它环境变量也会偶尔用到。
``` ```
$ go env $ go env

View File

@@ -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 $ 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 ..." 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 $ 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) 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 $ 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"> 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`参数,以别普通的浏览器请求)。

View File

@@ -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 $ cd $GOPATH/src/gopl.io/ch1/helloworld
@@ -25,7 +25,7 @@ $ cd $GOPATH
$ go build ./src/gopl.io/ch1/helloworld $ go build ./src/gopl.io/ch1/helloworld
``` ```
但不能這樣 但不能这样
``` ```
$ cd $GOPATH $ cd $GOPATH
@@ -33,7 +33,7 @@ $ go build src/gopl.io/ch1/helloworld
Error: cannot find package "src/gopl.io/ch1/helloworld". Error: cannot find package "src/gopl.io/ch1/helloworld".
``` ```
也可以指定包的源文件列表,一般這隻用於構建一些小程序或做一些臨時性的實驗。如果是main包將會以第一Go源文件的基文件名作爲最終的可行程序的名字。 也可以指定包的源文件列表,一般这只用于构建一些小程序或做一些临时性的实验。如果是main包将会以第一Go源文件的基文件名作为最终的可行程序的名字。
``` ```
$ cat quoteargs.go $ cat quoteargs.go
@@ -52,22 +52,22 @@ $ ./quoteargs one "two three" four\ five
["one" "two three" "four five"] ["one" "two three" "four five"]
``` ```
特别是對於這類一次性行的程序,我希望快的構建併運行它。`go run`命令實際上是合了建和行的兩個步驟 特别是对于这类一次性行的程序,我希望快的构建并运行它。`go run`命令实际上是合了建和行的两个步骤
``` ```
$ go run quoteargs.go one "two three" four\ five $ go run quoteargs.go one "two three" four\ five
["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型:
<u><i>gopl.io/ch10/cross</i></u> <u><i>gopl.io/ch10/cross</i></u>
```Go ```Go
@@ -76,7 +76,7 @@ func main() {
} }
``` ```
下面以64位和32位境分别行程序: 下面以64位和32位境分别行程序:
``` ```
$ go build gopl.io/ch10/cross $ go build gopl.io/ch10/cross
@@ -87,19 +87,19 @@ $ ./cross
darwin 386 darwin 386
``` ```
有些包可能需要針對不同平台和理器型使用不同版本的代文件,以便於處理底的可移植性問題或提供一些特定代提供化。如果一文件名包含了一操作繫統或處理器型名字例如net_linux.go或asm_amd64.sGo言的建工具將隻在對應的平台編譯這些文件。有一特别的構建註釋註釋可以提供更多的構建過程控。例如,文件中可能包含下面的註釋 有些包可能需要针对不同平台和理器型使用不同版本的代文件,以便于处理底的可移植性问题或提供一些特定代提供化。如果一文件名包含了一操作系统或处理器型名字例如net_linux.go或asm_amd64.sGo言的建工具将只在对应的平台编译这些文件。有一特别的构建注释注释可以提供更多的构建过程控。例如,文件中可能包含下面的注释
```Go ```Go
// +build linux darwin // +build linux darwin
``` ```
在包明和包註釋的前面,該構建註釋參數告訴`go build`隻在編譯程序對應的目操作繫統是Linux或Mac OS X時才編譯這個文件。下面的構建註釋則表示不編譯這個文件: 在包明和包注释的前面,该构建注释参数告诉`go build`只在编译程序对应的目操作系统是Linux或Mac OS X时才编译这个文件。下面的构建注释则表示不编译这个文件:
```Go ```Go
// +build ignore // +build ignore
``` ```
更多細節,可以考go/build包的構建約束部分的文 更多细节,可以考go/build包的构建约束部分的文
``` ```
$ go doc go/build $ go doc go/build

View File

@@ -1,8 +1,8 @@
### 10.7.4. 包文 ### 10.7.4. 包文
Go言的編碼風格鼓勵爲每個包提供良好的文。包中每個導出的成和包明前都應該包含目的和用法明的註釋 Go言的编码风格鼓励为每个包提供良好的文。包中每个导出的成和包明前都应该包含目的和用法明的注释
Go言中包文檔註釋一般是完整的句子,第一行是包的摘要明,註釋後僅跟着包聲明語句。註釋中函數的參數或其它的標識符併不需要外的引或其它標記註明。例如下面是fmt.Fprintf的文檔註釋 Go言中包文档注释一般是完整的句子,第一行是包的摘要明,注释后仅跟着包声明语句。注释中函数的参数或其它的标识符并不需要外的引或其它标记注明。例如下面是fmt.Fprintf的文档注释
```Go ```Go
// Fprintf formats according to a format specifier and writes to w. // 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) 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 $ go doc time
@@ -34,7 +34,7 @@ type Time struct { ... }
...many more... ...many more...
``` ```
或者是某個具體包成員的註釋文檔 或者是某个具体包成员的注释文档
``` ```
$ go doc time.Since $ go doc time.Since
@@ -44,7 +44,7 @@ func Since(t Time) Duration
It is shorthand for time.Now().Sub(t). It is shorthand for time.Now().Sub(t).
``` ```
或者是某個具體包的一方法的註釋文檔 或者是某个具体包的一方法的注释文档
``` ```
$ go doc time.Duration.Seconds $ 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. 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 $ go doc json.decode
@@ -63,12 +63,12 @@ func (dec *Decoder) Decode(v interface{}) error
it in the value pointed to by v. 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 $ godoc -http :8000
``` ```
其中`-analysis=type``-analysis=pointer`命令行標誌參數用於打開文檔和代碼中關於靜態分析的果。 其中`-analysis=type``-analysis=pointer`命令行标志参数用于打开文档和代码中关于静态分析的果。

View File

@@ -1,12 +1,12 @@
### 10.7.5. 部包 ### 10.7.5. 部包
在Go音程序中,包的封裝機製是一重要的特性。沒有導出的標識符隻在同一個包內部可以訪問,而出的標識符則是面向全宇宙都是可的。 在Go音程序中,包的封装机制是一重要的特性。没有导出的标识符只在同一个包内部可以访问,而出的标识符则是面向全宇宙都是可的。
候,一個中間的狀態可能也是有用的,對於一小部分信任的包是可的,但不是所有調用者都可。例如,當我們計劃將一個大的包拆分很多小的更容易維護的子包,但是我們併不想將內部的子包結構也完全暴露出去。同,我可能希望在部子包之共享一些通用的理包,或者我們隻是想實驗一個新包的還併不穩定的接口,暫時隻暴露一些受限的用使用。 候,一个中间的状态可能也是有用的,对于一小部分信任的包是可的,但不是所有用者都可。例如,当我们计划将一个大的包拆分很多小的更容易维护的子包,但是我们并不想将内部的子包结构也完全暴露出去。同,我可能希望在部子包之共享一些通用的理包,或者我们只是想实验一个新包的还并不稳定的接口,暂时只暴露一些受限的用使用。
![](../images/ch10-01.png) ![](../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 net/http

View File

@@ -1,13 +1,13 @@
### 10.7.6. 査詢 ### 10.7.6. 查询
`go list`命令可以査詢可用包的信息。其最簡單的形式,可以測試包是否在工作區併打印它的入路 `go list`命令可以查询可用包的信息。其最简单的形式,可以测试包是否在工作区并打印它的入路
``` ```
$ go list github.com/go-sql-driver/mysql $ go list github.com/go-sql-driver/mysql
github.com/go-sql-driver/mysql github.com/go-sql-driver/mysql
``` ```
`go list`命令的參數還可以用`"..."`表示匹配任意的包的入路。我可以用它列表工作中的所有包: `go list`命令的参数还可以用`"..."`表示匹配任意的包的入路。我可以用它列表工作中的所有包:
``` ```
$ go list ... $ go list ...
@@ -20,7 +20,7 @@ cmd/api
...many more... ...many more...
``` ```
或者是特定子目下的所有包: 或者是特定子目下的所有包:
``` ```
$ go list gopl.io/ch3/... $ go list gopl.io/ch3/...
@@ -33,7 +33,7 @@ gopl.io/ch3/printints
gopl.io/ch3/surface gopl.io/ch3/surface
``` ```
或者是和某個主題相關的所有包: 或者是和某个主题相关的所有包:
``` ```
$ go list ...xml... $ go list ...xml...
@@ -41,7 +41,7 @@ encoding/xml
gopl.io/ch7/xmlselect gopl.io/ch7/xmlselect
``` ```
`go list`命令可以取每包完整的元信息,而不僅僅隻是導入路徑,這些元信息可以以不同格式提供給用戶。其中`-json`命令行參數表示用JSON格式打印每包的元信息。 `go list`命令可以取每包完整的元信息,而不仅仅只是导入路径,这些元信息可以以不同格式提供给用户。其中`-json`命令行参数表示用JSON格式打印每包的元信息。
``` ```
$ go list -json hash $ 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 %} {% raw %}
``` ```
@@ -80,7 +80,7 @@ errors math runtime unicode/utf8 unsafe
``` ```
{% endraw %} {% endraw %}
譯註上面的命令在Windows的命令行運行會遇到`template: main:1: unclosed action`錯誤。産生這個錯誤的原因是因命令行命令中的`" "`參數進行了轉義處理。可以按照下面的方法解決轉義字符串的問題 译注上面的命令在Windows的命令行运行会遇到`template: main:1: unclosed action`错误。产生这个错误的原因是因命令行命令中的`" "`参数进行了转义处理。可以按照下面的方法解决转义字符串的问题
{% raw %} {% raw %}
``` ```
@@ -88,7 +88,7 @@ $ go list -f "{{join .Deps \" \"}}" strconv
``` ```
{% endraw %} {% endraw %}
下面的命令打印compress子目下所有包的依包列表: 下面的命令打印compress子目下所有包的依包列表:
{% raw %} {% raw %}
``` ```
@@ -101,7 +101,7 @@ compress/zlib -> bufio compress/flate errors fmt hash hash/adler32 io
``` ```
{% endraw %} {% endraw %}
譯註Windows下有同樣有問題,要避免轉義字符串的榦擾 译注Windows下有同样有问题,要避免转义字符串的干扰
{% raw %} {% raw %}
``` ```
@@ -109,8 +109,8 @@ $ go list -f "{{.ImportPath}} -> {{join .Imports \" \"}}" compress/...
``` ```
{% endraw %} {% 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格式的信息。

View File

@@ -1,10 +1,10 @@
## 10.7. 工具 ## 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 $ 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" %} {% include "./ch10-07-1.md" %}

View File

@@ -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还自带了工具箱,里面有很多用来简化工作和包管理的小工具。在本书开始的候,我们已经见识过如何使用工具箱自的工具来下载、构件和行我的演示程序了。在本章,我们将看看些工具的基本设计理论和尝试更多的功能,例如打印工作中包的文档和查询相关的元数据等。在下一章,我们将探讨探索包的单元测试用法。

View File

@@ -1,7 +1,7 @@
## 11.1. go test ## 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包用于调用相应的测试函数,然后构建并运行、报告测试结果,最清理测试中生成的临时文件。

View File

@@ -1,10 +1,10 @@
### 11.2.1. 隨機測試 ### 11.2.1. 随机测试
表格驅動的測試便於構造基精心挑選的測試數據的測試用例。另一種測試思路是隨機測試,也就是通過構造更泛的隨機輸入來測試探索函的行 表格驱动的测试便于构造基精心挑选的测试数据的测试用例。另一种测试思路是随机测试,也就是通过构造更广泛的随机输入来测试探索函的行
麽對於一個隨機的輸入,我如何能知道希望的輸出結果呢?里有兩種處理策略。第一個是編寫另一個對照函,使用簡單和清晰的算法,然效率低但是行和要測試的函是一致的,然後針對相同的隨機輸入檢査兩者的輸出結果。第二是生成的隨機輸入的數據遵循特定的模式,這樣我們就可以知道期望的出的模式。 么对于一个随机的输入,我如何能知道希望的输出结果呢?里有两种处理策略。第一个是编写另一个对照函,使用简单和清晰的算法,然效率低但是行和要测试的函是一致的,然后针对相同的随机输入检查两者的输出结果。第二是生成的随机输入的数据遵循特定的模式,这样我们就可以知道期望的出的模式。
下面的例子使用的是第二方法randomPalindrome函數用於隨機生成文字符串。 下面的例子使用的是第二方法randomPalindrome函数用于随机生成文字符串。
```Go ```Go
import "math/rand" 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是否对标点和空格做了正确处理。

View File

@@ -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可能返回的错误
<u><i>gopl.io/ch11/echo</i></u> <u><i>gopl.io/ch11/echo</i></u>
```Go ```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 ```Go
package main package main
@@ -82,15 +82,15 @@ func TestEcho(t *testing.T) {
} }
``` ```
意的是測試代碼和産品代在同一包。然是main包也有對應的main入口函,但是在測試的時候main包是TestEcho測試函數導入的一普通包里面main函數併沒有被出,而是被忽略的。 意的是测试代码和产品代在同一包。然是main包也有对应的main入口函,但是在测试的时候main包是TestEcho测试函数导入的一普通包里面main函数并没有被出,而是被忽略的。
過將測試放到表格中,我很容易添加新的測試用例。我通增加下面的測試用例看看失的情是怎麽樣的: 过将测试放到表格中,我很容易添加新的测试用例。我通增加下面的测试用例看看失的情是怎么样的:
```Go ```Go
{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation! {true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation!
``` ```
`go test`出如下: `go test`出如下:
``` ```
$ go test gopl.io/ch11/echo $ go test gopl.io/ch11/echo
@@ -100,6 +100,6 @@ FAIL
FAIL gopl.io/ch11/echo 0.006s 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的情

View File

@@ -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%的存储配额之后将发送提醒件。
<u><i>gopl.io/ch11/storage1</i></u> <u><i>gopl.io/ch11/storage1</i></u>
```Go ```Go
@@ -48,7 +48,7 @@ func CheckQuota(username string) {
} }
``` ```
們想測試這個代碼,但是我們併不希望發送眞實的郵件。因此我們將郵件處理邏輯放到一私有的notifyUser函中。 们想测试这个代码,但是我们并不希望发送真实的邮件。因此我们将邮件处理逻辑放到一私有的notifyUser函中。
<u><i>gopl.io/ch11/storage2</i></u> <u><i>gopl.io/ch11/storage2</i></u>
```Go ```Go
@@ -73,7 +73,7 @@ func CheckQuota(username string) {
} }
``` ```
在我可以在測試中用僞郵件發送函替代眞實的郵件發送函。它隻是簡單記録要通知的用戶和郵件的容。 在我可以在测试中用伪邮件发送函替代真实的邮件发送函。它只是简单记录要通知的用户和邮件的容。
```Go ```Go
package storage package storage
@@ -107,7 +107,7 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
} }
``` ```
里有一個問題:當測試函數返迴後CheckQuota不能正常工作,因notifyUsers依然使用的是測試函數的僞發送郵件函數(當更新全局象的時候總會有這種風險)。 我們必須脩改測試代碼恢複notifyUsers原先的狀態以便後續其他的測試沒有影,要保所有的行路徑後都能恢,包括測試失敗或panic常的情形。在這種情況下我們建議使用defer語句來延後執行處理恢的代 里有一个问题:当测试函数返回后CheckQuota不能正常工作,因notifyUsers依然使用的是测试函数的伪发送邮件函数(当更新全局象的时候总会有这种风险)。 我们必须修改测试代码恢复notifyUsers原先的状态以便后续其他的测试没有影,要保所有的行路径后都能恢,包括测试失败或panic常的情形。在这种情况下我们建议使用defer语句来延后执行处理恢的代
```Go ```Go
func TestCheckQuotaNotifiesUser(t *testing.T) { func TestCheckQuotaNotifiesUser(t *testing.T) {
@@ -124,6 +124,6 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
} }
``` ```
這種處理模式可以用來暫時保存和恢所有的全局量,包括命令行標誌參數、調試選項和優化參數;安和移除致生産代碼産生一些調試信息的子函數;還有有些誘導生産代碼進入某些重要狀態的改,比如超時、錯誤,甚至是一些刻意造的併發行爲等因素。 这种处理模式可以用来暂时保存和恢所有的全局量,包括命令行标志参数、调试选项和优化参数;安和移除致生产代码产生一些调试信息的子函数;还有有些诱导生产代码进入某些重要状态的改,比如超时、错误,甚至是一些刻意造的并发行为等因素。
這種方式使用全局量是安全的,因go test命令併不會同時併發地執行多個測試 这种方式使用全局量是安全的,因go test命令并不会同时并发地执行多个测试

View File

@@ -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) ![](../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) ![](../images/ch11-02.png)
過迴避循環導入依賴,擴展測試包可以更活的編寫測試,特别是集成測試(需要測試多個組件之的交互),可以像普通用程序那自由地入其他包。 过回避循环导入依赖,扩展测试包可以更活的编写测试,特别是集成测试(需要测试多个组件之的交互),可以像普通用程序那自由地入其他包。
可以用go list命令看包對應目録中哪些Go源文件是品代,哪些是包內測試,還哪些測試擴展包。我以fmt包作爲一個例子GoFiles表示品代碼對應的Go源文件列表也就是go build命令要編譯的部分。 可以用go list命令看包对应目录中哪些Go源文件是品代,哪些是包内测试,还哪些测试扩展包。我以fmt包作为一个例子GoFiles表示品代码对应的Go源文件列表也就是go build命令要编译的部分。
{% raw %} {% raw %}
@@ -25,7 +25,7 @@ $ go list -f={{.GoFiles}} fmt
{% endraw %} {% endraw %}
TestGoFiles表示的是fmt包內部測試測試代碼以_test.go爲後綴文件名,不過隻在測試時被構建: TestGoFiles表示的是fmt包内部测试测试代码以_test.go为后缀文件名,不过只在测试时被构建:
{% raw %} {% raw %}
@@ -36,9 +36,9 @@ $ go list -f={{.TestGoFiles}} fmt
{% endraw %} {% endraw %}
包的測試代碼通常都在些文件中,不fmt包非如此;稍後我們再解export_test.go文件的作用。 包的测试代码通常都在些文件中,不fmt包非如此;稍后我们再解export_test.go文件的作用。
XTestGoFiles表示的是屬於測試擴展包的測試代碼也就是fmt_test包因此它們必須先導入fmt包。同樣,這些文件也是在測試時被構建運行: XTestGoFiles表示的是属于测试扩展包的测试代码也就是fmt_test包因此它们必须先导入fmt包。同样,这些文件也是在测试时被构建运行:
{% raw %} {% raw %}
@@ -49,11 +49,11 @@ $ go list -f={{.XTestGoFiles}} fmt
{% endraw %} {% 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 ```Go
package fmt package fmt
@@ -61,5 +61,5 @@ package fmt
var IsSpace = isSpace var IsSpace = isSpace
``` ```
這個測試文件併沒有定義測試代碼;它是通fmt.IsSpace簡單導出了部的isSpace函,提供給測試擴展包使用。這個技巧可以泛用於位於測試擴展包的白盒測試 这个测试文件并没有定义测试代码;它是通fmt.IsSpace简单导出了部的isSpace函,提供给测试扩展包使用。这个技巧可以广泛用于位于测试扩展包的白盒测试

View File

@@ -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 ```Go
import ( import (
@@ -25,7 +25,7 @@ func TestSplit(t *testing.T) {
} }
``` ```
從這個意義上説,斷言函犯了早抽象的錯誤:僅僅測試兩個整數是否相同,而放了根上下文提供更有意義的錯誤信息的做法。我可以根據具體的錯誤打印一更有值的錯誤信息,就像下面例子那樣。測試在隻有一次重的模式出現時引入抽象。 从这个意义上说,断言函犯了早抽象的错误:仅仅测试两个整数是否相同,而放了根上下文提供更有意义的错误信息的做法。我可以根据具体的错误打印一更有值的错误信息,就像下面例子那样。测试在只有一次重的模式出现时引入抽象。
```Go ```Go
func TestSplit(t *testing.T) { 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测试,并打印期望的输出结果。

View File

@@ -1,8 +1,8 @@
### 11.2.6. 避免的不定的測試 ### 11.2.6. 避免的不定的测试
如果一個應用程序對於新出的但有效的輸入經常失敗説明程序不夠穩健;同如果一個測試僅僅因爲聲音變化就會導致失也是不合邏輯的。就像一個不夠穩健的程序會挫敗它的用戶一樣,一脆弱性測試同樣會激怒它的維護者。最脆弱的測試代碼會在程序有任何化的時候産生不同的果,時好時壞,處理它們會耗費大量的時間但是併不會得到任何好 如果一个应用程序对于新出的但有效的输入经常失败说明程序不够稳健;同如果一个测试仅仅因为声音变化就会导致失也是不合逻辑的。就像一个不够稳健的程序会挫败它的用户一样,一脆弱性测试同样会激怒它的维护者。最脆弱的测试代码会在程序有任何化的时候产生不同的果,时好时坏,处理它们会耗费大量的时间但是并不会得到任何好
當一個測試函數産生一個複雜的輸出如一個很長的字符串,或一精心設計的數據結構或一文件,它可以用於和預設的“golden”結果數據對比,用這種簡單方式寫測試是誘人的。但是隨着項目的展,出的某些部分很可能會發生變化,管很可能是一個改進的實現導致的。而且不僅僅是輸出部分,函數複雜複製的輸入部分可能也跟着化了,因此測試使用的入也就不在有效了。 当一个测试函数产生一个复杂的输出如一个很长的字符串,或一精心设计的数据结构或一文件,它可以用于和预设的“golden”结果数据对比,用这种简单方式写测试是诱人的。但是随着项目的展,出的某些部分很可能会发生变化,管很可能是一个改进的实现导致的。而且不仅仅是输出部分,函数复杂复制的输入部分可能也跟着化了,因此测试使用的入也就不在有效了。
避免脆弱測試代碼的方法是隻檢測你眞正關心的性。保持測試代碼的簡潔和內部結構的穩定。特别是對斷言部分要有所選擇。不要檢査字符串的全匹配,但是找相的子字符串,因某些子字符串在目的展中是比較穩定不的。通常編寫一個重複雜的輸出中提取必要精信息以用於斷言是值得的,雖然這可能會帶來很多前期的工作,但是它可以助迅速及時脩複因爲項目演化而致的不合邏輯的失敗測試 避免脆弱测试代码的方法是只检测你真正关心的性。保持测试代码的简洁和内部结构的稳定。特别是对断言部分要有所选择。不要检查字符串的全匹配,但是找相的子字符串,因某些子字符串在目的展中是比较稳定不的。通常编写一个重复杂的输出中提取必要精信息以用于断言是值得的,虽然这可能会带来很多前期的工作,但是它可以助迅速及时修复因为项目演化而致的不合逻辑的失败测试

View File

@@ -1,6 +1,6 @@
## 11.2. 測試函數 ## 11.2. 测试函数
個測試函數必須導入testing包。測試函數有如下的名: 个测试函数必须导入testing包。测试函数有如下的名:
```Go ```Go
func TestName(t *testing.T) { func TestName(t *testing.T) {
@@ -8,7 +8,7 @@ func TestName(t *testing.T) {
} }
``` ```
測試函數的名字必以Test開頭,可選的後綴名必以大字母開頭 测试函数的名字必以Test开头,可选的后缀名必以大字母开头
```Go ```Go
func TestSin(t *testing.T) { /* ... */ } func TestSin(t *testing.T) { /* ... */ }
@@ -16,7 +16,7 @@ func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ } func TestLog(t *testing.T) { /* ... */ }
``` ```
其中t參數用於報告測試失敗和附加的日信息。讓我們定義一個實例包gopl.io/ch11/word1其中有一個函數IsPalindrome用於檢査一個字符串是否前向後和從後向前都是一的。(下面這個實現對於一個字符串是否是文字符串前後重複測試了兩次;我們稍後會再討論這個問題。) 其中t参数用于报告测试失败和附加的日信息。让我们定义一个实例包gopl.io/ch11/word1其中有一个函数IsPalindrome用于检查一个字符串是否前向后和从后向前都是一的。(下面这个实现对于一个字符串是否是文字符串前后重复测试了两次;我们稍后会再讨论这个问题。)
<u><i>gopl.io/ch11/word1</i></u> <u><i>gopl.io/ch11/word1</i></u>
```Go ```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 ```Go
package word 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 $ cd $GOPATH/src/gopl.io/ch11/word1
@@ -66,7 +66,7 @@ $ go test
ok gopl.io/ch11/word1 0.008s 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 ```Go
func TestFrenchPalindrome(t *testing.T) { func TestFrenchPalindrome(t *testing.T) {
@@ -83,9 +83,9 @@ func TestCanalPalindrome(t *testing.T) {
} }
``` ```
了避免兩次輸入較長的字符串,我使用了提供了有似Printf格式化功能的 Errorf函數來滙報錯誤結果。 了避免两次输入较长的字符串,我使用了提供了有似Printf格式化功能的 Errorf函数来汇报错误结果。
添加了這兩個測試用例之`go test`迴了測試失敗的信息。 添加了这两个测试用例之`go test`回了测试失败的信息。
``` ```
$ go test $ go test
@@ -97,11 +97,11 @@ FAIL
FAIL gopl.io/ch11/word1 0.014s FAIL gopl.io/ch11/word1 0.014s
``` ```
編寫測試用例併觀察到測試用例觸發了和用戶報告的錯誤相同的描述是一好的測試習慣。隻有這樣,我才能定位我們要眞正解決的問題 编写测试用例并观察到测试用例触发了和用户报告的错误相同的描述是一好的测试习惯。只有这样,我才能定位我们要真正解决的问题
寫測試用例的另外的好是,運行測試通常比手工描述告的理更快,這讓我們可以行快速地迭代。如果測試集有很多運行緩慢的測試,我可以通過隻選擇運行某些特定的測試來加快測試速度。 写测试用例的另外的好是,运行测试通常比手工描述告的理更快,这让我们可以行快速地迭代。如果测试集有很多运行缓慢的测试,我可以通过只选择运行某些特定的测试来加快测试速度。
參數`-v`可用打印每個測試函數的名字和運行時間 参数`-v`可用打印每个测试函数的名字和运行时间
``` ```
$ go test -v $ go test -v
@@ -120,7 +120,7 @@ exit status 1
FAIL gopl.io/ch11/word1 0.017s FAIL gopl.io/ch11/word1 0.017s
``` ```
參數`-run`對應一個正則表達式,隻有測試函數名被它正匹配的測試函數才會`go test`測試命令行: 参数`-run`对应一个正则表达式,只有测试函数名被它正匹配的测试函数才会`go test`测试命令行:
``` ```
$ go test -v -run="French|Canal" $ go test -v -run="French|Canal"
@@ -135,11 +135,11 @@ exit status 1
FAIL gopl.io/ch11/word1 0.014s 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我们仔细重写了函
<u><i>gopl.io/ch11/word2</i></u> <u><i>gopl.io/ch11/word2</i></u>
```Go ```Go
@@ -166,7 +166,7 @@ func IsPalindrome(s string) bool {
} }
``` ```
時我們也將之前的所有測試數據合併到了一個測試中的表格中。 时我们也将之前的所有测试数据合并到了一个测试中的表格中。
```Go ```Go
func TestIsPalindrome(t *testing.T) { func TestIsPalindrome(t *testing.T) {
@@ -196,24 +196,24 @@ func TestIsPalindrome(t *testing.T) {
} }
``` ```
在我的新測試阿都通了: 在我的新测试阿都通了:
``` ```
$ go test gopl.io/ch11/word2 $ go test gopl.io/ch11/word2
ok gopl.io/ch11/word2 0.015s 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" %} {% include "./ch11-02-1.md" %}

View File

@@ -1,12 +1,12 @@
## 11.3. 測試覆蓋 ## 11.3. 测试覆盖
就其性而言,測試不可能是完整的。計算機科學家Edsger Dijkstra曾説過:“測試可以示存在缺陷,但是不是説沒有BUG。”再多的測試也不能明一程序有BUG。在最好的情下,測試可以增強我們的信心:代在我們測試的環境是可以正常工作的。 就其性而言,测试不可能是完整的。计算机科学家Edsger Dijkstra曾说过:“测试可以示存在缺陷,但是不是说没有BUG。”再多的测试也不能明一程序有BUG。在最好的情下,测试可以增强我们的信心:代在我们测试的环境是可以正常工作的。
測試驅動觸發運行到的被測試函數的代碼數目稱爲測試的覆率。測試覆蓋率併不能量化——甚至連最簡單的動態程序也以精確測量——但是可以啟發併幫助我們編寫的有效的測試代碼 测试驱动触发运行到的被测试函数的代码数目称为测试的覆率。测试覆盖率并不能量化——甚至连最简单的动态程序也以精确测量——但是可以启发并帮助我们编写的有效的测试代码
這些幫助信息中句的覆率是最簡單和最泛使用的。句的覆率是指在測試中至少被行一次的代碼占總代碼數的比例。在本中,我使用`go test`命令中集成的測試覆蓋率工具,度量下面代碼的測試覆蓋率,助我們識别測試和我期望的差距。 这些帮助信息中句的覆率是最简单和最广泛使用的。句的覆率是指在测试中至少被行一次的代码占总代码数的比例。在本中,我使用`go test`命令中集成的测试覆盖率工具,度量下面代码的测试覆盖率,助我们识别测试和我期望的差距。
下面的代是一表格驅動的測試,用於測試第七章的表式求值程序: 下面的代是一表格驱动的测试,用于测试第七章的表式求值程序:
<u><i>gopl.io/ch7/eval</i></u> <u><i>gopl.io/ch7/eval</i></u>
```Go ```Go
@@ -45,7 +45,7 @@ func TestCoverage(t *testing.T) {
} }
``` ```
首先,我們要確保所有的測試都正常通 首先,我们要确保所有的测试都正常通
``` ```
$ go test -v -run=Coverage gopl.io/ch7/eval $ go test -v -run=Coverage gopl.io/ch7/eval
@@ -55,7 +55,7 @@ PASS
ok gopl.io/ch7/eval 0.011s ok gopl.io/ch7/eval 0.011s
``` ```
下面這個命令可以顯示測試覆蓋率工具的使用用法: 下面这个命令可以显示测试覆盖率工具的使用用法:
``` ```
$ go tool cover $ 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 $ go test -run=Coverage -coverprofile=c.out gopl.io/ch7/eval
ok gopl.io/ch7/eval 0.032s coverage: 68.5% of statements 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 $ go tool cover -html=c.out
@@ -89,12 +89,12 @@ $ go tool cover -html=c.out
![](../images/ch11-03.png) ![](../images/ch11-03.png)
色的代碼塊被測試覆蓋到了,色的表示有被覆到。了清晰起,我們將的背景色文本的背景置成了影效果。我可以馬上發現unary操作的Eval方法併沒有被行到。如果我們針對這部分未被覆的代添加下面的測試用例,然重新行上面的命令,那麽我們將會看到那個紅色部分的代碼也變成緑色了: 绿色的代码块被测试覆盖到了,色的表示有被覆到。了清晰起,我们将的背景色文本的背景置成了影效果。我可以马上发现unary操作的Eval方法并没有被行到。如果我们针对这部分未被覆的代添加下面的测试用例,然重新行上面的命令,那么我们将会看到那个红色部分的代码也变成绿色了:
``` ```
{"-x * -x", eval.Env{"x": 2}, "4"} {"-x * -x", eval.Env{"x": 2}, "4"}
``` ```
過兩個panic句依然是色的。這是沒有問題的,因爲這兩個語句併不會被執行到。 过两个panic句依然是色的。这是没有问题的,因为这两个语句并不会被执行到。
實現100%的測試覆蓋率聽起來很美,但是在具體實踐中通常是不可行的,也不是值得推的做法。因爲那隻能説明代碼被執行過而已,不意味着代就是有BUG的爲對於邏輯複雜的語句需要針對不同的輸入執行多次。有一些例如上面的panic語句則永遠都不會被執行到。另外,有一些晦的錯誤在現實中很少遇到也很難編寫對應的測試代碼。測試從本質上來説是一個比較務實的工作,編寫測試代碼和編寫應用代的成本比是需要考的。測試覆蓋率工具可以助我快速識别測試薄弱的地方,但是設計好的測試用例和編寫應用代碼一樣需要密的思考。 实现100%的测试覆盖率听起来很美,但是在具体实践中通常是不可行的,也不是值得推的做法。因为那只能说明代码被执行过而已,不意味着代就是有BUG的为对于逻辑复杂的语句需要针对不同的输入执行多次。有一些例如上面的panic语句则永远都不会被执行到。另外,有一些晦的错误在现实中很少遇到也很难编写对应的测试代码。测试从本质上来说是一个比较务实的工作,编写测试代码和编写应用代的成本比是需要考的。测试覆盖率工具可以助我快速识别测试薄弱的地方,但是设计好的测试用例和编写应用代码一样需要密的思考。

View File

@@ -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 ```Go
import "testing" import "testing"
@@ -14,7 +14,7 @@ func BenchmarkIsPalindrome(b *testing.B) {
} }
``` ```
用下面的命令行基準測試。和普通測試不同的是,默認情況下不行任何基準測試。我需要通`-bench`命令行標誌參數手工指定要行的基準測試函數。該參數是一個正則表達式,用匹配要行的基準測試函數的名字,默值是空的。其中“.”模式可以匹配所有基準測試函數,但是這里總共隻有一個基準測試函數,因此和`-bench=IsPalindrome`參數是等的效果。 用下面的命令行基准测试。和普通测试不同的是,默认情况下不行任何基准测试。我需要通`-bench`命令行标志参数手工指定要行的基准测试函数。该参数是一个正则表达式,用匹配要行的基准测试函数的名字,默值是空的。其中“.”模式可以匹配所有基准测试函数,但是这里总共只有一个基准测试函数,因此和`-bench=IsPalindrome`参数是等的效果。
``` ```
$ cd $GOPATH/src/gopl.io/ch11/word2 $ cd $GOPATH/src/gopl.io/ch11/word2
@@ -24,13 +24,13 @@ BenchmarkIsPalindrome-8 1000000 1035 ns/op
ok gopl.io/ch11/word2 2.179s 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 ```Go
n := len(letters)/2 n := len(letters)/2
@@ -42,7 +42,7 @@ for i := 0; i < n; i++ {
return true return true
``` ```
很多情下,一個明顯的優化併不一定就能代碼預期的效果。這個改進在基準測試中隻帶來了4%的性能提 很多情下,一个明显的优化并不一定就能代码预期的效果。这个改进在基准测试中只带来了4%的性能提
``` ```
$ go test -bench=. $ go test -bench=.
@@ -51,7 +51,7 @@ BenchmarkIsPalindrome-8 1000000 992 ns/op
ok gopl.io/ch11/word2 2.093s ok gopl.io/ch11/word2 2.093s
``` ```
另一個改進想法是在開始爲每個字符先分配一個足夠大的數組,這樣就可以避免在append調用時可能會導致內存的多次重新分配。明一letters數組變量,指定合的大小,像下面這樣 另一个改进想法是在开始为每个字符先分配一个足够大的数组,这样就可以避免在append调用时可能会导致内存的多次重新分配。明一letters数组变量,指定合的大小,像下面这样
```Go ```Go
letters := make([]rune, 0, len(s)) 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=. $ go test -bench=.
@@ -71,7 +71,7 @@ BenchmarkIsPalindrome-8 2000000 697 ns/op
ok gopl.io/ch11/word2 1.468s ok gopl.io/ch11/word2 1.468s
``` ```
這個例子所示,快的程序往往是伴隨着較少的存分配。`-benchmem`命令行標誌參數將在報告中包含存的分配數據統計。我可以比較優化前後內存的分配情 这个例子所示,快的程序往往是伴随着较少的存分配。`-benchmem`命令行标志参数将在报告中包含存的分配数据统计。我可以比较优化前后内存的分配情
``` ```
$ go test -bench=. -benchmem $ go test -bench=. -benchmem
@@ -79,7 +79,7 @@ PASS
BenchmarkIsPalindrome 1000000 1026 ns/op 304 B/op 4 allocs/op BenchmarkIsPalindrome 1000000 1026 ns/op 304 B/op 4 allocs/op
``` ```
這是優化之後的結果: 这是优化之后的结果:
``` ```
$ go test -bench=. -benchmem $ go test -bench=. -benchmem
@@ -87,11 +87,11 @@ PASS
BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op 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 ```Go
func benchmark(b *testing.B, size int) { /* ... */ } 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) } 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的实现相比有多快?

View File

@@ -1,22 +1,22 @@
## 11.5. 剖析 ## 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 $ go test -cpuprofile=cpu.out
@@ -24,13 +24,13 @@ $ go test -blockprofile=block.out
$ go test -memprofile=mem.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 \ $ 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 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”一文。

View File

@@ -1,6 +1,6 @@
## 11.6. 示例函 ## 11.6. 示例函
第三`go test`特别理的函是示例函以Example爲函數名開頭。示例函數沒有函數參數和返值。下面是IsPalindrome函數對應的示例函 第三`go test`特别理的函是示例函以Example为函数名开头。示例函数没有函数参数和返值。下面是IsPalindrome函数对应的示例函
```Go ```Go
func ExampleIsPalindrome() { 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) ![](../images/ch11-04.png)
書最後的兩掌是討論reflect和unsafe包一般的Go用很少直接使用它。因此,如果你還沒有寫過任何眞實的Go程序的話,現在可以忽略剩部分而直接編碼了。 书最后的两掌是讨论reflect和unsafe包一般的Go用很少直接使用它。因此,如果你还没有写过任何真实的Go程序的话,现在可以忽略剩部分而直接编码了。

View File

@@ -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代码过程是似的;它不需要学习新的符号、规则和工具。

View File

@@ -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 ```Go
func Sprint(x interface{}) string { 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的型,这会导致对这些库的循环依赖
有一方法來檢査未知型的表示方式,我被卡住了。就是我們爲何需要反射的原因。 有一方法来检查未知型的表示方式,我被卡住了。就是我们为何需要反射的原因。

View File

@@ -1,8 +1,8 @@
## 12.2. reflect.Type和reflect.Value ## 12.2. reflect.Type和reflect.Value
反射是由 reflect 包提供支持. 它定義了兩個重要的型, Type 和 Value. 一 Type 表示一Go型. 它是一接口, 有多方法來區分類型和檢査它們的組件, 例如一個結構體的成或一個函數的參數等. 唯一能反映 reflect.Type 實現的是接口的型描述信息(§7.5), 同樣的實體標識了動態類型的接口值. 反射是由 reflect 包提供支持. 它定义了两个重要的型, Type 和 Value. 一 Type 表示一Go型. 它是一接口, 有多方法来区分类型和检查它们的组件, 例如一个结构体的成或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的型描述信息(§7.5), 同样的实体标识了动态类型的接口值.
reflect.TypeOf 接受任意的 interface{} 型, 併返迴對應動態類型的reflect.Type: reflect.TypeOf 接受任意的 interface{} 型, 并返回对应动态类型的reflect.Type:
```Go ```Go
t := reflect.TypeOf(3) // a reflect.Type t := reflect.TypeOf(3) // a reflect.Type
@@ -10,22 +10,22 @@ fmt.Println(t.String()) // "int"
fmt.Println(t) // "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 ```Go
var w io.Writer = os.Stdout var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File" 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 ```Go
fmt.Printf("%T\n", 3) // "int" 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 ```Go
v := reflect.ValueOf(3) // a reflect.Value v := reflect.ValueOf(3) // a reflect.Value
@@ -34,16 +34,16 @@ fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int Value>" fmt.Println(v.String()) // NOTE: "<int Value>"
``` ```
和 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 ```Go
t := v.Type() // a reflect.Type t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int" fmt.Println(t.String()) // "int"
``` ```
逆操作是調用 reflect.ValueOf 對應的 reflect.Value.Interface 方法. 它返迴一個 interface{} 型表示 reflect.Value 對應類型的具值: 逆操作是用 reflect.ValueOf 对应的 reflect.Value.Interface 方法. 它返回一个 interface{} 型表示 reflect.Value 对应类型的具值:
```Go ```Go
v := reflect.ValueOf(3) // a reflect.Value v := reflect.ValueOf(3) // a reflect.Value
@@ -52,9 +52,9 @@ i := x.(int) // an int
fmt.Printf("%d\n", i) // "3" 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 无效类型.)
<u><i>gopl.io/ch12/format</i></u> <u><i>gopl.io/ch12/format</i></u>
```Go ```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 ```Go
var x int64 = 1 var x int64 = 1

View File

@@ -1,13 +1,13 @@
## 12.3. Display遞歸打印 ## 12.3. Display递归打印
接下來,讓我們看看如何改善聚合數據類型的示。我們併不想完全隆一fmt.Sprint函,我們隻是像建一個用於調式用的Display函數,給定一聚合型x打印這個值對應的完整的結構,同時記録每個發現的每元素的路徑。讓我們從一個例子始。 接下来,让我们看看如何改善聚合数据类型的示。我们并不想完全隆一fmt.Sprint函,我们只是像建一个用于调式用的Display函数,给定一聚合型x打印这个值对应的完整的结构,同时记录每个发现的每元素的路径。让我们从一个例子始。
```Go ```Go
e, _ := eval.Parse("sqrt(A / pi)") e, _ := eval.Parse("sqrt(A / pi)")
Display("e", e) Display("e", e)
``` ```
在上面的調用中,入Display函數的參數是在7.9節一個表達式求值函數返迴的語法樹。Display函數的輸出如下: 在上面的用中,入Display函数的参数是在7.9节一个表达式求值函数返回的语法树。Display函数的输出如下:
```Go ```Go
Display e (eval.call): Display e (eval.call):
@@ -20,7 +20,7 @@ e.args[0].value.y.type = eval.Var
e.args[0].value.y.value = "pi" e.args[0].value.y.value = "pi"
``` ```
在可能的情下,你應該避免在一包中暴露和反射相的接口。我們將定義一個未導出的display函數用於遞歸處理工作,出的是Display函,它是display函數簡單的包以接受interface{}型的參數 在可能的情下,你应该避免在一包中暴露和反射相的接口。我们将定义一个未导出的display函数用于递归处理工作,出的是Display函,它是display函数简单的包以接受interface{}型的参数
<u><i>gopl.io/ch12/display</i></u> <u><i>gopl.io/ch12/display</i></u>
```Go ```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 ```Go
func display(path string, v reflect.Value) { 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 ```Go
type Movie struct { type Movie struct {
@@ -99,7 +99,7 @@ type Movie struct {
} }
``` ```
讓我們聲明一個該類型的量,然看看Display函如何示它: 让我们声明一个该类型的量,然看看Display函如何示它:
```Go ```Go
strangelove := Movie{ strangelove := Movie{
@@ -125,7 +125,7 @@ strangelove := Movie{
} }
``` ```
Display("strangelove", strangelove)調用將顯strangelove電影對應的中文名是《奇博士》): Display("strangelove", strangelove)调用将显strangelove电影对应的中文名是《奇博士》):
```Go ```Go
Display strangelove (display.Movie): Display strangelove (display.Movie):
@@ -146,7 +146,7 @@ strangelove.Oscars[3] = "Best Picture (Nomin.)"
strangelove.Sequel = nil strangelove.Sequel = nil
``` ```
也可以使用Display函數來顯示標準庫中類型的內部結構,例如`*os.File`型: 也可以使用Display函数来显示标准库中类型的内部结构,例如`*os.File`型:
```Go ```Go
Display("os.Stderr", os.Stderr) Display("os.Stderr", os.Stderr)
@@ -157,7 +157,7 @@ Display("os.Stderr", os.Stderr)
// (*(*os.Stderr).file).nepipe = 0 // (*(*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 ```Go
Display rV (reflect.Value): Display rV (reflect.Value):
@@ -174,7 +174,7 @@ Display rV (reflect.Value):
... ...
``` ```
察下面兩個例子的别: 察下面两个例子的别:
```Go ```Go
var i interface{} = 3 var i interface{} = 3
@@ -191,11 +191,11 @@ Display("&i", &i)
// (*&i).value = 3 // (*&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 ```Go
// a struct that points to itself // a struct that points to itself
@@ -205,7 +205,7 @@ c = Cycle{42, &c}
Display("c", c) Display("c", c)
``` ```
Display會永遠不停地行深度遞歸打印: Display会永远不停地行深度递归打印:
```Go ```Go
Display c (display.Cycle): Display c (display.Cycle):
@@ -216,10 +216,10 @@ c.Value = 42
...ad infinitum... ...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,我们会看到另一种探测数据结构是否存在的技。)

View File

@@ -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 42 integer
@@ -13,13 +13,13 @@ foo symbol (an unquoted name)
(1 2 3) list (zero or more items enclosed in parentheses) (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函数类似:
<u><i>gopl.io/ch12/sexpr</i></u> <u><i>gopl.io/ch12/sexpr</i></u>
```Go ```Go
@@ -93,7 +93,7 @@ func encode(buf *bytes.Buffer, v reflect.Value) error {
} }
``` ```
Marshal函數是對encode的保以保持和encoding/...下其它包有着相似的API Marshal函数是对encode的保以保持和encoding/...下其它包有着相似的API
```Go ```Go
// Marshal encodes a Go value in S-expression form. // Marshal encodes a Go value in S-expression form.
@@ -106,7 +106,7 @@ func Marshal(v interface{}) ([]byte, error) {
} }
``` ```
下面是Marshal12.3的strangelove變量編碼後的結果: 下面是Marshal12.3的strangelove变量编码后的结果:
``` ```
((Title "Dr. Strangelove") (Subtitle "How I Learned to Stop Worrying and Lo ((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)) omin.)" "Best Picture (Nomin.)")) (Sequel nil))
``` ```
個輸出編碼爲一行中以減少輸出的大小,但是也很難閲讀。這里有一個對S表式格式化的定。編寫一個S表式的格式化函數將作爲一個具有挑性的練習任務;不 http://gopl.io 也提供了一個簡單的版本。 个输出编码为一行中以减少输出的大小,但是也很难阅读。这里有一个对S表式格式化的定。编写一个S表式的格式化函数将作为一个具有挑性的练习任务;不 http://gopl.io 也提供了一个简单的版本。
``` ```
((Title "Dr. Strangelove") ((Title "Dr. Strangelove")
@@ -137,16 +137,16 @@ omin.)" "Best Picture (Nomin.)")) (Sequel nil))
(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** 建一個基於流式的APIS表式的解和json.Decoder(§4.5)函功能似。 **练习 12.7** 建一个基于流式的APIS表式的解和json.Decoder(§4.5)函功能似。

View File

@@ -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 ```Go
x := 2 // value type variable? x := 2 // value type variable?
@@ -14,9 +14,9 @@ c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x) 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 ```Go
fmt.Println(a.CanAddr()) // "false" fmt.Println(a.CanAddr()) // "false"
@@ -25,9 +25,9 @@ fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true" 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 ```Go
x := 2 x := 2
@@ -37,20 +37,20 @@ px := d.Addr().Interface().(*int) // px := &x
fmt.Println(x) // "3" fmt.Println(x) // "3"
``` ```
或者,不使用指,而是通過調用可取地址的reflect.Value的reflect.Value.Set方法更新對於的值: 或者,不使用指,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法更新对于的值:
```Go ```Go
d.Set(reflect.ValueOf(4)) d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4" fmt.Println(x) // "4"
``` ```
Set方法將在運行時執行和編譯時類似的可值性束的檢査。以上代碼,變量和值都是int型,但是如果量是int64型,那程序將拋出一panic常,所以關鍵問題是要保改型的量可以接受對應的值: Set方法将在运行时执行和编译时类似的可值性束的检查。以上代码,变量和值都是int型,但是如果量是int64型,那程序将抛出一panic常,所以关键问题是要保改型的量可以接受对应的值:
```Go ```Go
d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int
``` ```
通用對一個不可取地址的reflect.Value調用Set方法也會導致panic常: 通用对一个不可取地址的reflect.Value用Set方法也会导致panic常:
```Go ```Go
x := 2 x := 2
@@ -58,7 +58,7 @@ b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
``` ```
里有很多用基本數據類型的Set方法SetInt、SetUint、SetString和SetFloat等。 里有很多用基本数据类型的Set方法SetInt、SetUint、SetString和SetFloat等。
```Go ```Go
d := reflect.ValueOf(&x).Elem() d := reflect.ValueOf(&x).Elem()
@@ -66,7 +66,7 @@ d.SetInt(3)
fmt.Println(x) // "3" fmt.Println(x) // "3"
``` ```
從某種程度上説,這些Set方法總是盡可能地完成任。以SetInt例,隻要變量是某種類型的有符號整數就可以工作,使是一些命名的型,要底層數據類型是有符號整數就可以,而且如果對於變量類型值太大的話會被自動截斷。但需要慎的是:對於一個引用interface{}型的reflect.Value調用SetInt會導致panic常,使那interface{}變量對於整數類型也不行。 从某种程度上说,这些Set方法总是尽可能地完成任。以SetInt例,只要变量是某种类型的有符号整数就可以工作,使是一些命名的型,要底层数据类型是有符号整数就可以,而且如果对于变量类型值太大的话会被自动截断。但需要慎的是:对于一个引用interface{}型的reflect.Value用SetInt会导致panic常,使那interface{}变量对于整数类型也不行。
```Go ```Go
x := 1 x := 1
@@ -84,7 +84,7 @@ ry.SetString("hello") // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello" 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 ```Go
stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var 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 fd.SetInt(2) // panic: unexported field
``` ```
可取地址的reflect.Value會記録一個結構體成員是否是未出成,如果是的話則拒絶脩改操作。因此CanAddr方法不能正反映一個變量是否是可以被改的。另一個相關的方法CanSet是用於檢査對應的reflect.Value是否是可取地址可被改的: 可取地址的reflect.Value会记录一个结构体成员是否是未出成,如果是的话则拒绝修改操作。因此CanAddr方法不能正反映一个变量是否是可以被改的。另一个相关的方法CanSet是用于检查对应的reflect.Value是否是可取地址可被改的:
```Go ```Go
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false" fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"

View File

@@ -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 ```Go
data := []byte{/* ... */} data := []byte{/* ... */}
@@ -8,13 +8,13 @@ var movie Movie
err := json.Unmarshal(data, &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方法返回的记号
<u><i>gopl.io/ch12/sexpr</i></u> <u><i>gopl.io/ch12/sexpr</i></u>
```Go ```Go
@@ -34,7 +34,7 @@ func (lex *lexer) consume(want rune) {
} }
``` ```
現在讓我們轉到語法解析器。它主要包含兩個功能。第一是read函,用於讀取S表式的當前標記,然後根據S表式的當前標記更新可取地址的reflect.Value對應的變量v。 现在让我们转到语法解析器。它主要包含两个功能。第一是read函,用于读取S表式的当前标记,然后根据S表式的当前标记更新可取地址的reflect.Value对应的变量v。
```Go ```Go
func read(lex *lexer, v reflect.Value) { 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函數處理。對於mapkey可能是任意型,元素的理方式和slice似,我們創建一新的量,然後遞歸填充它,最後將新解析到的key/value添加到map。 在循环处理结构体和map每元素时必须解码一个(key value)格式的对应子列表。对于结构体key部分对于成员的名字。和数组类似,我使用FieldByName找到结构体对应成员的变量然后递归调用read函数处理。对于mapkey可能是任意型,元素的理方式和slice似,我们创建一新的量,然后递归填充它,最后将新解析到的key/value添加到map。
```Go ```Go
func readList(lex *lexer, v reflect.Value) { 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 ```Go
// Unmarshal parses S-expression data and populates the variable // 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.Decoder7.14)的格。你需要五種類型的標記Symbol、String、Int、StartList和EndList。 **练习 12.9** 编写一个基于标记的API用于解码S表式,考xml.Decoder7.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。

View File

@@ -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将请求参数填充到合适的结构体成员中,这样我们可以方便地通过合适的类型类来访问这些参数
<u><i>gopl.io/ch12/search</i></u> <u><i>gopl.io/ch12/search</i></u>
```Go ```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:"..."形式的子串。
<u><i>gopl.io/ch12/params</i></u> <u><i>gopl.io/ch12/params</i></u>
```Go ```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 ```Go
func populate(v reflect.Value, value string) error { 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 $ 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 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:"..."字串。

View File

@@ -1,6 +1,6 @@
## 12.8. 示一個類型的方法集 ## 12.8. 示一个类型的方法集
的最後一個例子是使用reflect.Type打印任意值的型和枚它的方法: 的最后一个例子是使用reflect.Type打印任意值的型和枚它的方法:
<u><i>gopl.io/ch12/methods</i></u> <u><i>gopl.io/ch12/methods</i></u>
```Go ```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 ```Go
methods.Print(time.Hour) methods.Print(time.Hour)

View File

@@ -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 ```Go
fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)" fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)"
``` ```
反射同降低了程序的安全性,還影響了自化重和分析工具的準確性,因爲它們無法識别運行時才能確認的類型信息。 反射同降低了程序的安全性,还影响了自化重和分析工具的准确性,因为它们无法识别运行时才能确认的类型信息。
避免使用反射的第二原因是,卽使對應類型提供了相同文,但是反射的操作不能做靜態類型檢査,而且大量反射的代通常以理解。是需要小心翼翼地爲每個導出的型和其它接受interface{}或reflect.Value類型參數的函數維護説明文 避免使用反射的第二原因是,即使对应类型提供了相同文,但是反射的操作不能做静态类型检查,而且大量反射的代通常以理解。是需要小心翼翼地为每个导出的型和其它接受interface{}或reflect.Value类型参数的函数维护说明文
第三原因,基反射的代通常比正常的代碼運行速度慢一到兩個數量級。對於一個典型的目,大部分函的性能和程序的整性能關繫不大,所以使用反射可能使程序更加清晰。測試是一特别合使用反射的景,因爲每個測試的數據集都很小。但是對於性能關鍵路徑的函,最好避免使用反射。 第三原因,基反射的代通常比正常的代码运行速度慢一到两个数量级。对于一个典型的目,大部分函的性能和程序的整性能关系不大,所以使用反射可能使程序更加清晰。测试是一特别合使用反射的景,因为每个测试的数据集都很小。但是对于性能关键路径的函,最好避免使用反射。

View File

@@ -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都有公反射相的接口。

View File

@@ -1,33 +1,33 @@
## 13.1. unsafe.Sizeof, Alignof 和 Offsetof ## 13.1. unsafe.Sizeof, Alignof 和 Offsetof
unsafe.Sizeof函數返迴操作數在內存中的字大小,參數可以是任意型的表式,但是它併不會對表達式進行求值。一Sizeof函數調用是一個對應uintptr型的常量表式,因此返迴的結果可以用作數組類型的度大小,或者用作算其他的常量。 unsafe.Sizeof函数返回操作数在内存中的字大小,参数可以是任意型的表式,但是它并不会对表达式进行求值。一Sizeof函数调用是一个对应uintptr型的常量表式,因此返回的结果可以用作数组类型的度大小,或者用作算其他的常量。
```Go ```Go
import "unsafe" import "unsafe"
fmt.Println(unsafe.Sizeof(float64(0))) // "8" 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個字節 bool | 1个字节
intN, uintN, floatN, complexN | N/8個字節(例如float64是8個字節) intN, uintN, floatN, complexN | N/8个字节(例如float64是8个字节)
int, uint, uintptr | 1個機器字 int, uint, uintptr | 1个机器字
*T | 1個機器字 *T | 1个机器字
string | 2個機器字(data,len) string | 2个机器字(data,len)
[]T | 3個機器字(data,len,cap) []T | 3个机器字(data,len,cap)
map | 1個機器字 map | 1个机器字
func | 1個機器字 func | 1个机器字
chan | 1個機器字 chan | 1个机器字
interface | 2個機器字(type,value) interface | 2个机器字(type,value)
Go言的規范併沒有要求一字段的聲明順序和存中的序是一致的,所以理上一個編譯器可以意地重新排列每字段的存位置,然在作本書的時候編譯器還沒有這麽做。下面的三個結構體雖然有着相同的字段,但是第一種寫法比另外的兩個需要多50%的存。 Go言的规范并没有要求一字段的声明顺序和存中的序是一致的,所以理上一个编译器可以意地重新排列每字段的存位置,然在作本书的时候编译器还没有这么做。下面的三个结构体虽然有着相同的字段,但是第一种写法比另外的两个需要多50%的存。
```Go ```Go
// 64-bit 32-bit // 64-bit 32-bit
@@ -36,13 +36,13 @@ struct{ float64; int16; bool } // 2 words 3words
struct{ bool; int16; float64 } // 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 ```Go
var x struct { var x struct {
@@ -52,11 +52,11 @@ var x struct {
} }
``` ```
下面示了x和它的三字段調用unsafe包相關函數的計算結果: 下面示了x和它的三字段用unsafe包相关函数的计算结果:
![](../images/ch13-01.png) ![](../images/ch13-01.png)
32位繫統 32位系统
``` ```
Sizeof(x) = 16 Alignof(x) = 4 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 Sizeof(x.c) = 12 Alignof(x.c) = 4 Offsetof(x.c) = 4
``` ```
64位繫統 64位系统
``` ```
Sizeof(x) = 32 Alignof(x) = 8 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 Sizeof(x.c) = 24 Alignof(x.c) = 8 Offsetof(x.c) = 8
``` ```
雖然這幾個函數在不安全的unsafe包但是這幾個函數調用併不是的不安全,特别在需要優化內存空間時它們返迴的結果對於理解原生的存布局很有助。 虽然这几个函数在不安全的unsafe包但是这几个函数调用并不是的不安全,特别在需要优化内存空间时它们返回的结果对于理解原生的存布局很有助。

View File

@@ -1,8 +1,8 @@
## 13.2. unsafe.Pointer ## 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 ```Go
package math package math
@@ -12,11 +12,11 @@ func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000" 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
<u><i>gopl.io/ch13/unsafeptr</i></u> <u><i>gopl.io/ch13/unsafeptr</i></u>
```Go ```Go
@@ -26,14 +26,14 @@ var x struct {
c []int c []int
} }
// 和 pb := &x.b 等 // 和 pb := &x.b 等
pb := (*int16)(unsafe.Pointer( pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b))) uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42 *pb = 42
fmt.Println(x.b) // "42" fmt.Println(x.b) // "42"
``` ```
上面的寫法盡管很繁,但在這里併不是一件事,因爲這些功能應該很謹慎地使用。不要試圖引入一uintptr型的臨時變量,因它可能會破壞代碼的安全性(譯註:這是眞正可以體會unsafe包何不安全的例子)。下面段代碼是錯誤的: 上面的写法尽管很繁,但在这里并不是一件事,因为这些功能应该很谨慎地使用。不要试图引入一uintptr型的临时变量,因它可能会破坏代码的安全性(译注:这是真正可以体会unsafe包何不安全的例子)。下面段代码是错误的:
```Go ```Go
// NOTE: subtly incorrect! // NOTE: subtly incorrect!
@@ -42,21 +42,21 @@ pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42 *pb = 42
``` ```
産生錯誤的原因很微妙。有候垃圾收器會移動一些量以降低存碎片等問題。這類垃圾收器被稱爲移動GC。當一個變量被移,所有的保存改變量舊地址的指針必須同時被更新爲變量移動後的新地址。垃圾收集器的視角來看,一unsafe.Pointer是一指向量的指,因此當變量被移動是對應的指也必被更新但是uintptr型的臨時變量隻是一普通的字,所以其值不應該被改。上面錯誤的代碼因爲引入一非指針的臨時變量tmp致垃圾收集器法正確識别這個是一指向量x的指針。當第二個語句執行時變量x可能已經被轉移這時候臨時變量tmp也就不再是在的`&x.b`地址。第三向之前效地址空間的賦值語句將徹底摧譭整個程序! 产生错误的原因很微妙。有候垃圾收器会移动一些量以降低存碎片等问题。这类垃圾收器被称为移动GC。当一个变量被移,所有的保存改变量旧地址的指针必须同时被更新为变量移动后的新地址。垃圾收集器的视角来看,一unsafe.Pointer是一指向量的指,因此当变量被移动是对应的指也必被更新但是uintptr型的临时变量只是一普通的字,所以其值不应该被改。上面错误的代码因为引入一非指针的临时变量tmp致垃圾收集器法正确识别这个是一指向量x的指针。当第二个语句执行时变量x可能已经被转移这时候临时变量tmp也就不再是在的`&x.b`地址。第三向之前效地址空间的赋值语句将彻底摧毁整个程序!
有很多似原因致的錯誤。例如這條語句: 有很多似原因致的错误。例如这条语句:
```Go ```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 ```Go
package reflect package reflect

View File

@@ -1,6 +1,6 @@
## 13.3. 示例: 深度相等判 ## 13.3. 示例: 深度相等判
自reflect包的DeepEqual函可以對兩個值進行深度相等判。DeepEqual函使用建的==比操作符對基礎類型進行相等判斷,對於複合類型則遞歸該變量的每個基礎類型然後做類似的比較判斷。因它可以工作在任意的型上,甚至對於一些不支持==操作算符的型也可以工作,因此在一些測試代碼中廣泛地使用該函數。比如下面的代是用DeepEqual函數比較兩個字符串數組是否相等。 自reflect包的DeepEqual函可以对两个值进行深度相等判。DeepEqual函使用建的==比操作符对基础类型进行相等判断,对于复合类型则递归该变量的每个基础类型然后做类似的比较判断。因它可以工作在任意的型上,甚至对于一些不支持==操作算符的型也可以工作,因此在一些测试代码中广泛地使用该函数。比如下面的代是用DeepEqual函数比较两个字符串数组是否相等。
```Go ```Go
func TestSplit(t *testing.T) { 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 ```Go
var a, b []string = nil, []string{} 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" fmt.Println(reflect.DeepEqual(c, d)) // "false"
``` ```
希望在這里實現一個自己的Equal函,用於比較類型的值。和DeepEqual函數類似的地方是它也是基slice和map的每元素進行遞歸比較,不同之是它nil值的slicemap和非nil值但是空的slice作相等的值。基部分的比可以基reflect包完成和12.3章的Display函數的實現方法似。同,我也定了一個內部函equal於內部的遞歸比較。讀者目前不用心seen參數的具體含義。對於每一需要比的x和yequal函首先檢測它們是否都有效(或都效),然後檢測它們是否是相同的型。剩下的部分是一個鉅大的switch分支相同基礎類型的元素比。因爲頁面空的限,我省略了一些相似的分支。 希望在这里实现一个自己的Equal函,用于比较类型的值。和DeepEqual函数类似的地方是它也是基slice和map的每元素进行递归比较,不同之是它nil值的slicemap和非nil值但是空的slice作相等的值。基部分的比可以基reflect包完成和12.3章的Display函数的实现方法似。同,我也定了一个内部函equal于内部的递归比较。读者目前不用心seen参数的具体含义。对于每一需要比的x和yequal函首先检测它们是否都有效(或都效),然后检测它们是否是相同的型。剩下的部分是一个巨大的switch分支相同基础类型的元素比。因为页面空的限,我省略了一些相似的分支。
<u><i>gopl.io/ch13/equal</i></u> <u><i>gopl.io/ch13/equal</i></u>
```Go ```Go
@@ -63,7 +63,7 @@ func equal(x, y reflect.Value, seen map[comparison]bool) bool {
} }
``` ```
和前面的建議一樣,我們併不公reflect包相的接口,所以出的函需要在部自己將變量轉爲reflect.Value型。 和前面的建议一样,我们并不公reflect包相的接口,所以出的函需要在部自己将变量转为reflect.Value型。
```Go ```Go
// Equal reports whether x and y are deeply equal. // 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]也是對應相同的地址,可以用於區分xy之的比或x[0]y[0]之的比是否進行過了。 为了确保算法对于有环的数据结构也能正常退出,我们必须记录每次已经比较的变量,而避免入第二次的比。Equal函分配了一组用于比较的结构体,包含每对比较对象的地址unsafe.Pointer形式保存型。我们要记录类型的原因是,有些不同的量可能对应相同的地址。例如如果x和y都是数组类型,那x和x[0]将对应相同的地址y和y[0]也是对应相同的地址,可以用于区分xy之的比或x[0]y[0]之的比是否进行过了。
```Go ```Go
// cycle check // cycle check
@@ -96,7 +96,7 @@ if x.CanAddr() && y.CanAddr() {
} }
``` ```
是Equal函用法的例子: 是Equal函用法的例子:
```Go ```Go
fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true" 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" fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
``` ```
Equal函甚至可以處理類似12.3章中致Display陷入陷入死循環的帶有環的數據 Equal函甚至可以处理类似12.3章中致Display陷入陷入死循环的带有环的数据
```Go ```Go
// Circular linked lists a -> b -> a and c -> c. // 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" fmt.Println(Equal(a, c)) // "false"
``` ```
**練習 13.1**義一個深比較函數,對於十億以內的數字比,忽略型差 **练习 13.1**义一个深比较函数,对于十亿以内的数字比,忽略型差
**練習 13.2** 編寫一個函數,報告其參數是否循環數據結構 **练习 13.2** 编写一个函数,报告其参数是否循环数据结构

View File

@@ -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 ```Go
package gzip // compress/gzip package gzip // compress/gzip
@@ -12,11 +12,11 @@ func NewWriter(w io.Writer) io.WriteCloser
func NewReader(r io.Reader) (io.ReadCloser, error) 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.6Go言已經明確規定了哪些Go言指可以之間傳入C言函。新代碼重點是增加了bz2alloc和bz2free的兩個函數,用bz_stream象空的申請和釋放操作。下面是新代中增加的註釋,説明這個問題 译注:本章采用的代都是最新的。因之前已出版的中包含的代码只能在Go1.5之前使用。Go1.6Go言已经明确规定了哪些Go言指可以之间传入C言函。新代码重点是增加了bz2alloc和bz2free的两个函数,用bz_stream象空的申请和释放操作。下面是新代中增加的注释,说明这个问题
```Go ```Go
// The version of this program that appeared in the first and second // The version of this program that appeared in the first and second
@@ -37,9 +37,9 @@ bzip2壓縮算法是基於優雅的Burrows-Wheeler變換算法運行速度
// pointers to Go variables. // 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代码,对应一个独立的文件。
<u><i>gopl.io/ch13/bzip</i></u> <u><i>gopl.io/ch13/bzip</i></u>
```C ```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 ```Go
// Package bzip provides a writer that uses bzip2 compression (bzip.org). // 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 ```Go
func (w *writer) Write(data []byte) (int, error) { 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 ```Go
// Close flushes the compressed data and closes the stream. // 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命令似。
<u><i>gopl.io/ch13/bzipper</i></u> <u><i>gopl.io/ch13/bzipper</i></u>
```Go ```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 $ go build gopl.io/ch13/bzipper
@@ -204,8 +204,8 @@ $ ./bzipper < /usr/share/dict/words | bunzip2 | sha256sum
126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed - 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命令其他操作系统可能无法运行)。

View File

@@ -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言。

View File

@@ -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包的。

View File

@@ -1,8 +1,8 @@
## 2.1. 命名 ## 2.1. 命名
Go言中的函名、量名、常量名、型名、語句標號和包名等所有的命名,都遵循一個簡單的命名規則:一名字必以一字母Unicode字母或下劃線開頭,後面可以跟任意量的字母、字或下劃線。大字母和小字母是不同的heapSort和Heapsort是兩個不同的名字。 Go言中的函名、量名、常量名、型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一名字必以一字母Unicode字母或下划线开头,后面可以跟任意量的字母、字或下划线。大字母和小字母是不同的heapSort和Heapsort是两个不同的名字。
Go言中似if和switch的關鍵字有25個;關鍵字不能用自定名字,能在特定語法結構中使用。 Go言中似if和switch的关键字有25个;关键字不能用自定名字,能在特定语法结构中使用。
``` ```
break default func interface select break default func interface select
@@ -12,25 +12,25 @@ const fallthrough if range type
continue for import return var 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 uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64 float32 float64 complex128 complex64
bool byte rune string error bool byte rune string error
建函: make len cap new append copy close delete 建函: make len cap new append copy close delete
complex real imag complex real imag
panic recover 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。

View File

@@ -1,8 +1,8 @@
## 2.2. ## 2.2.
聲明語句定了程序的各種實體對象以及部分或全部的性。Go言主要有四種類型的聲明語var、const、type和func分别對應變量、常量、型和函數實體對象的明。一章我們重點討論變量和型的明,第三章將討論常量的明,第五章將討論函數的聲明。 声明语句定了程序的各种实体对象以及部分或全部的性。Go言主要有四种类型的声明语var、const、type和func分别对应变量、常量、型和函数实体对象的明。一章我们重点讨论变量和型的明,第三章将讨论常量的明,第五章将讨论函数的声明。
Go語言編寫的程序對應一個或多以.go文件後綴名的源文件中。每源文件以包的聲明語句開始,説明該源文件是屬於哪個包。包聲明語句之是import語句導入依的其它包,然是包一級的類型、量、常量、函數的聲明語句,包一的各種類型的聲明語句的順序無關緊要(譯註:函數內部的名字則必須先聲明之才能使用)。例如,下面的例子中明了一常量、一個函數和兩個變量: Go语言编写的程序对应一个或多以.go文件后缀名的源文件中。每源文件以包的声明语句开始,说明该源文件是属于哪个包。包声明语句之是import语句导入依的其它包,然是包一级的类型、量、常量、函数的声明语句,包一的各种类型的声明语句的顺序无关紧要(译注:函数内部的名字则必须先声明之才能使用)。例如,下面的例子中明了一常量、一个函数和两个变量:
<u><i>gopl.io/ch2/boiling</i></u> <u><i>gopl.io/ch2/boiling</i></u>
```Go ```Go
@@ -22,11 +22,11 @@ func main() {
} }
``` ```
其中常量boilingF是在包一級范圍聲明語句聲明的,然f和c兩個變量是在main函數內部聲明的聲明語句聲明的。在包一級聲明語句聲明的名字可在整個包對應的每源文件中訪問,而不是僅僅在其聲明語句所在的源文件中訪問。相比之下,局部明的名字就能在函數內部很小的范圍被訪問 其中常量boilingF是在包一级范围声明语句声明的,然f和c两个变量是在main函数内部声明的声明语句声明的。在包一级声明语句声明的名字可在整个包对应的每源文件中访问,而不是仅仅在其声明语句所在的源文件中访问。相比之下,局部明的名字就能在函数内部很小的范围被访问
個函數的聲明由一個函數名字、參數列表(由函數的調用者提供參數變量的具值)、一個可選的返值列表和包含函數定義的函數體組成。如果函數沒有返值,那麽返迴值列表是省略的。行函數從函數的第一個語句開始,依次順序執行直到遇到renturn返迴語句,如果有返迴語句則是執行到函末尾,然後返迴到函數調用者。 个函数的声明由一个函数名字、参数列表(由函数的调用者提供参数变量的具值)、一个可选的返值列表和包含函数定义的函数体组成。如果函数没有返值,那么返回值列表是省略的。行函数从函数的第一个语句开始,依次顺序执行直到遇到renturn返回语句,如果有返回语句则是执行到函末尾,然后返回到函数调用者。
們已經看到很多函數聲明和函數調用的例子了,在第五章深入討論函數的相關細節這里隻簡單解釋下。下面的fToC函數封裝了溫度轉換的處理邏輯這樣它隻需要被定一次,就可以在多地方多次被使用。在這個例子中main函數就調用了次fToC函,分别是使用在局部定義的兩個常量作爲調用函數的參數 们已经看到很多函数声明和函数调用的例子了,在第五章深入讨论函数的相关细节这里只简单解释下。下面的fToC函数封装了温度转换的处理逻辑这样它只需要被定一次,就可以在多地方多次被使用。在这个例子中main函数就调用了次fToC函,分别是使用在局部定义的两个常量作为调用函数的参数
<u><i>gopl.io/ch2/ftoc</i></u> <u><i>gopl.io/ch2/ftoc</i></u>
```Go ```Go

View File

@@ -1,6 +1,6 @@
### 2.3.1. 簡短變量聲 ### 2.3.1. 简短变量声
在函數內部,有一種稱爲簡短變量聲明語句的形式可用於聲明和初始化局部量。它以“名字 := 表式”形式聲明變量,量的型根據表達式來自動推導。下面是lissajous函中的三個簡短變量聲明語§1.4 在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部量。它以“名字 := 表式”形式声明变量,量的型根据表达式来自动推导。下面是lissajous函中的三个简短变量声明语§1.4
```Go ```Go
anim := gif.GIF{LoopCount: nframes} anim := gif.GIF{LoopCount: nframes}
@@ -8,7 +8,7 @@ freq := rand.Float64() * 3.0
t := 0.0 t := 0.0
``` ```
爲簡潔和靈活的特點,簡短變量聲明被泛用大部分的局部量的明和初始化。var形式的聲明語句往往是用需要式指定變量類型地方,或者因爲變量稍後會被重新值而初始值無關緊要的地方。 为简洁和灵活的特点,简短变量声明被广泛用大部分的局部量的明和初始化。var形式的声明语句往往是用需要式指定变量类型地方,或者因为变量稍后会被重新值而初始值无关紧要的地方。
```Go ```Go
i := 100 // an int i := 100 // an int
@@ -18,21 +18,21 @@ var err error
var p Point var p Point
``` ```
和var形式聲明變語句一樣,簡短變量聲明語句也可以用來聲明和初始化一組變量: 和var形式声明变语句一样,简短变量声明语句也可以用来声明和初始化一组变量:
```Go ```Go
i, j := 0, 1 i, j := 0, 1
``` ```
但是這種同時聲明多個變量的方式應該限製隻在可以提高代碼可讀性的地方使用比如for句的循的初始化句部分。 但是这种同时声明多个变量的方式应该限制只在可以提高代码可读性的地方使用比如for句的循的初始化句部分。
請記住“:=”是一個變量聲明語句,而“=‘是一個變量賦值操作。也不要混淆多個變量的明和元的多重§2.4.1者是將右邊各個的表式值賦值給左邊對應位置的各個變量: 请记住“:=”是一个变量声明语句,而“=‘是一个变量赋值操作。也不要混淆多个变量的明和元的多重§2.4.1者是将右边各个的表式值赋值给左边对应位置的各个变量:
```Go ```Go
i, j = j, i // 交 i 和 j 的值 i, j = j, i // 交 i 和 j 的值
``` ```
和普通var形式的變量聲明語句一樣,簡短變量聲明語句也可以用函的返迴值來聲明和初始化像下面的os.Open函數調用將返迴兩個值: 和普通var形式的变量声明语句一样,简短变量声明语句也可以用函的返回值来声明和初始化像下面的os.Open函数调用将返回两个值:
```Go ```Go
f, err := os.Open(name) f, err := os.Open(name)
@@ -43,9 +43,9 @@ if err != nil {
f.Close() f.Close()
``` ```
里有一個比較微妙的地方:簡短變量聲明左邊的變量可能不是全部都是剛剛聲明的。如果有一些已在相同的法域聲明過§2.7),那麽簡短變量聲明語句對這些已經聲明過的變量就隻有賦值行了。 里有一个比较微妙的地方:简短变量声明左边的变量可能不是全部都是刚刚声明的。如果有一些已在相同的法域声明过§2.7),那么简短变量声明语句对这些已经声明过的变量就只有赋值行了。
在下面的代中,第一個語句聲明了in和err兩個變量。在第二個語句隻聲明了out一個變量,然後對已經聲明的err行了值操作。 在下面的代中,第一个语句声明了in和err两个变量。在第二个语句只声明了out一个变量,然后对已经声明的err行了值操作。
```Go ```Go
in, err := os.Open(infile) in, err := os.Open(infile)
@@ -53,7 +53,7 @@ in, err := os.Open(infile)
out, err := os.Create(outfile) out, err := os.Create(outfile)
``` ```
簡短變量聲明語句中必至少要明一新的量,下面的代碼將不能編譯通過 简短变量声明语句中必至少要明一新的量,下面的代码将不能编译通过
```Go ```Go
f, err := os.Open(infile) f, err := os.Open(infile)
@@ -61,9 +61,9 @@ f, err := os.Open(infile)
f, err := os.Create(outfile) // compile error: no new variables f, err := os.Create(outfile) // compile error: no new variables
``` ```
的方法是第二個簡短變量聲明語句改用普通的多重賦值語言。 的方法是第二个简短变量声明语句改用普通的多重赋值语言。
簡短變量聲明語句隻有對已經在同級詞法域聲明過的變量才和值操作句等,如果量是在外部法域明的,那麽簡短變量聲明語句將會在當前詞法域重新明一新的量。我在本章後面將會看到似的例子。 简短变量声明语句只有对已经在同级词法域声明过的变量才和值操作句等,如果量是在外部法域明的,那么简短变量声明语句将会在当前词法域重新明一新的量。我在本章后面将会看到似的例子。

View File

@@ -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 ```Go
x := 1 x := 1
@@ -14,18 +14,18 @@ fmt.Println(*p) // "1"
fmt.Println(x) // "2" fmt.Println(x) // "2"
``` ```
對於聚合型每個成員——比如結構體的每字段、或者是數組的每元素——也都是對應一個變量,因此可以被取地址。 对于聚合型每个成员——比如结构体的每字段、或者是数组的每元素——也都是对应一个变量,因此可以被取地址。
量有候被稱爲可尋址的值。卽使變量由表達式臨時生成,那麽表達式也必能接受`&`取地址操作。 量有候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必能接受`&`取地址操作。
任何型的指的零值都是nil。如果`p != nil`測試爲眞,那p是指向某有效量。指針之間也是可以行相等測試的,隻有當它們指向同一個變量或全部是nil才相等。 任何型的指的零值都是nil。如果`p != nil`测试为真,那p是指向某有效量。指针之间也是可以行相等测试的,只有当它们指向同一个变量或全部是nil才相等。
```Go ```Go
var x, y int var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false" fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
``` ```
在Go言中,返迴函數中局部量的地址也是安全的。例如下面的代調用f函數時創建局部量v在局部量地址被返迴之後依然有效,因爲指針p依然引用這個變量。 在Go言中,返回函数中局部量的地址也是安全的。例如下面的代调用f函数时创建局部量v在局部量地址被返回之后依然有效,因为指针p依然引用这个变量。
```Go ```Go
var p = f() var p = f()
@@ -36,17 +36,17 @@ func f() *int {
} }
``` ```
每次調用f函數都將返迴不同的果: 每次用f函数都将返回不同的果:
```Go ```Go
fmt.Println(f() == f()) // "false" fmt.Println(f() == f()) // "false"
``` ```
爲指針包含了一個變量的地址,因此如果將指針作爲參數調用函,那可以在函中通過該指針來更新量的值。例如下面這個例子就是通過指針來更新量的值,然後返迴更新的值,可用在一個表達式中譯註這是對C語言中`++v`操作的模擬,這里隻是爲了説明指的用法incr函數模擬的做法不推 为指针包含了一个变量的地址,因此如果将指针作为参数调用函,那可以在函中通过该指针来更新量的值。例如下面这个例子就是通过指针来更新量的值,然后返回更新的值,可用在一个表达式中译注这是对C语言中`++v`操作的模拟,这里只是为了说明指的用法incr函数模拟的做法不推
```Go ```Go
func incr(p *int) int { func incr(p *int) int {
*p++ // 非常重要:是增加p指向的量的值,不改p指 *p++ // 非常重要:是增加p指向的量的值,不改p指
return *p return *p
} }
@@ -55,9 +55,9 @@ incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3) 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。
<u><i>gopl.io/ch2/echo4</i></u> <u><i>gopl.io/ch2/echo4</i></u>
```Go ```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 $ go build gopl.io/ch2/echo4

View File

@@ -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 ```Go
p := new(int) // p, *int 型, 指向匿名的 int p := new(int) // p, *int 型, 指向匿名的 int
fmt.Println(*p) // "0" fmt.Println(*p) // "0"
*p = 2 // 置 int 匿名量的值 2 *p = 2 // 置 int 匿名量的值 2
fmt.Println(*p) // "2" fmt.Println(*p) // "2"
``` ```
用new創建變量和普通變量聲明語句方式創建變量沒有什麽區别,除了不需要明一個臨時變量的名字外,我們還可以在表式中使用new(T)。言之new函數類似是一種語法糖,而不是一新的基概念。 用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要明一个临时变量的名字外,我们还可以在表式中使用new(T)。言之new函数类似是一种语法糖,而不是一新的基概念。
下面的兩個newInt函有着相同的行 下面的两个newInt函有着相同的行
```Go ```Go
func newInt() *int { func newInt() *int {
@@ -24,7 +24,7 @@ func newInt() *int {
} }
``` ```
每次調用new函都是返迴一個新的量的地址,因此下面兩個地址是不同的: 每次用new函都是返回一个新的量的地址,因此下面两个地址是不同的:
```Go ```Go
p := new(int) p := new(int)
@@ -32,15 +32,15 @@ q := new(int)
fmt.Println(p == q) // "false" 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 ```Go
func delta(old, new int) int { return new - old } func delta(old, new int) int { return new - old }
``` ```
new被定義爲int型的量名因此在delta函數內部是法使用置的new函的。 new被定义为int型的量名因此在delta函数内部是法使用置的new函的。

View File

@@ -1,8 +1,8 @@
### 2.3.4. 量的生命 ### 2.3.4. 量的生命
量的生命期指的是在程序行期間變量有效存在的時間間隔。對於在包一級聲明的變量來説,它的生命期和整程序的運行週期是一致的。而相比之下,在局部量的聲明週期則是動態的:每次建一個新變量的聲明語句開始,直到該變量不再被引用止,然後變量的存儲空間可能被收。函數的參數變量和返迴值變量都是局部量。它在函每次被調用的時候創建。 量的生命期指的是在程序行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它的生命期和整程序的运行周期是一致的。而相比之下,在局部量的声明周期则是动态的:每次建一个新变量的声明语句开始,直到该变量不再被引用止,然后变量的存储空间可能被收。函数的参数变量和返回值变量都是局部量。它在函每次被用的时候创建。
例如,下面是1.4的Lissajous程序摘的代片段: 例如,下面是1.4的Lissajous程序摘的代片段:
```Go ```Go
for t := 0.0; t < cycles*2*math.Pi; t += res { 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 ```Go
for t := 0.0; t < cycles*2*math.Pi; t += res { 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) y := math.Sin(t*freq + phase)
img.SetColorIndex( img.SetColorIndex(
size+int(x*size+0.5), size+int(y*size+0.5), 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 ```Go
var global *int 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言的自垃圾收集器对编写正确的代是一个巨大的助,但也不是你完全不用考虑内存了。你然不需要式地分配和释放内存,但是要编写高效的程序你依然需要了解量的生命期。例如,如果指向短生命周期对象的指保存到具有生命期的象中,特别是保存到全局变量时,会阻止短生命周期对象的垃圾收(而可能影程序的性能)。

View File

@@ -1,32 +1,32 @@
## 2.3. ## 2.3.
var聲明語句可以建一特定型的量,然後給變量附加一名字,併且設置變量的初始值。變量聲明的一般法如下: var声明语句可以建一特定型的量,然后给变量附加一名字,并且设置变量的初始值。变量声明的一般法如下:
```Go ```Go
var 量名字 = var 量名字 =
``` ```
其中“*型*”或“*= 表式*”兩個部分可以省略其中的一。如果省略的是型信息,那麽將根據初始化表達式來推導變量的型信息。如果初始化表式被省略,那麽將用零值初始化該變量。 數值類型變量對應的零值是0布爾類型變量對應的零值是false字符串類型對應的零值是空字符串,接口或引用包括slice、map、chan和函數)變量對應的零值是nil。數組或結構體等聚合類型對應的零值是每元素或字段都是對應該類型的零值。 其中“*型*”或“*= 表式*”两个部分可以省略其中的一。如果省略的是型信息,那么将根据初始化表达式来推导变量的型信息。如果初始化表式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0布尔类型变量对应的零值是false字符串类型对应的零值是空字符串,接口或引用包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每元素或字段都是对应该类型的零值。
零值初始化機製可以保每個聲明的變量總是有一良好定的值因此在Go言中不存在未初始化的量。這個特性可以化很多代,而且可以在有增加外工作的前提下確保邊界條件下的合理行。例如: 零值初始化机制可以保每个声明的变量总是有一良好定的值因此在Go言中不存在未初始化的量。这个特性可以化很多代,而且可以在有增加外工作的前提下确保边界条件下的合理行。例如:
```Go ```Go
var s string var s string
fmt.Println(s) // "" fmt.Println(s) // ""
``` ```
段代碼將打印一空字符串,而不是導致錯誤或産生不可知的行。Go言程序員應該讓一些聚合型的零值也具有意義,這樣可以保不管任何型的變量總是有一合理有效的零值狀態 段代码将打印一空字符串,而不是导致错误或产生不可知的行。Go言程序员应该让一些聚合型的零值也具有意义,这样可以保不管任何型的变量总是有一合理有效的零值状态
也可以在一個聲明語句中同時聲明一組變量,或用一初始化表達式聲明併初始化一組變量。如果省略每個變量的型,可以明多個類型不同的量(型由初始化表式推 也可以在一个声明语句中同时声明一组变量,或用一初始化表达式声明并初始化一组变量。如果省略每个变量的型,可以明多个类型不同的量(型由初始化表式推
```Go ```Go
var i, j, k int // int, int, int var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string var b, f, s = true, 2.3, "four" // bool, float64, string
``` ```
初始化表式可以是字面量或任意的表式。在包級别聲明的變量會在main入口函數執行前完成初始化§2.6.2),局部變量將在聲明語句被行到的候完成初始化。 初始化表式可以是字面量或任意的表式。在包级别声明的变量会在main入口函数执行前完成初始化§2.6.2),局部变量将在声明语句被行到的候完成初始化。
組變量也可以通過調用一個函數,由函數返迴的多個返迴值初始化: 组变量也可以通过调用一个函数,由函数返回的多个返回值初始化:
```Go ```Go
var f, err = os.Open(name) // os.Open returns a file and an error var f, err = os.Open(name) // os.Open returns a file and an error

View File

@@ -1,6 +1,6 @@
### 2.4.1. 元組賦 ### 2.4.1. 元组赋
組賦值是另一形式的賦值語句,它允許同時更新多個變量的值。在值之前,賦值語句右的所有表達式將會先進行求值,然後再統一更新左邊對應變量的值。這對於處理有些同時出現在元組賦值語句左右兩邊的變量很有助,例如我可以這樣交換兩個變量的值: 组赋值是另一形式的赋值语句,它允许同时更新多个变量的值。在值之前,赋值语句右的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。这对于处理有些同时出现在元组赋值语句左右两边的变量很有助,例如我可以这样交换两个变量的值:
```Go ```Go
x, y = y, x x, y = y, x
@@ -8,7 +8,7 @@ x, y = y, x
a[i], a[j] = a[j], a[i] a[i], a[j] = a[j], a[i]
``` ```
或者是計算兩個整數值的的最大公約數GCD譯註GCD不是那敏感字而是greatest common divisor的縮寫,歐幾里德的GCD是最早的非平凡算法 或者是计算两个整数值的的最大公约数GCD译注GCD不是那敏感字而是greatest common divisor的缩写,欧几里德的GCD是最早的非平凡算法
```Go ```Go
func gcd(x, y int) int { func gcd(x, y int) int {
@@ -19,7 +19,7 @@ func gcd(x, y int) int {
} }
``` ```
或者是算斐波納契數Fibonacci的第N個數 或者是算斐波纳契数Fibonacci的第N个数
```Go ```Go
func fib(n int) int { func fib(n int) int {
@@ -31,21 +31,21 @@ func fib(n int) int {
} }
``` ```
組賦值也可以使一繫列瑣碎賦值更加緊湊(譯註: 特别是在for循的初始化部分), 组赋值也可以使一系列琐碎赋值更加紧凑(译注: 特别是在for循的初始化部分),
```Go ```Go
i, j, k = 2, 3, 5 i, j, k = 2, 3, 5
``` ```
但如果表式太複雜的話,應該盡量避免度使用元組賦值;因爲每個變量單獨賦值語句的法可讀性會更好。 但如果表式太复杂的话,应该尽量避免度使用元组赋值;因为每个变量单独赋值语句的法可读性会更好。
有些表達式會産生多值,比如調用一有多個返迴值的函數。當這樣一個函數調用出在元組賦值右的表式中時(譯註:右不能再有其它表式),左邊變量的目必和右一致。 有些表达式会产生多值,比如用一有多个返回值的函数。当这样一个函数调用出在元组赋值右的表式中时(译注:右不能再有其它表式),左边变量的目必和右一致。
```Go ```Go
f, err = os.Open("foo.txt") // function call returns two values 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 ```Go
v, ok = m[key] // map lookup v, ok = m[key] // map lookup
@@ -53,22 +53,22 @@ v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive 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 ```Go
v = m[key] // map找,失敗時返迴零值 v = m[key] // map找,失败时返回零值
v = x.(T) // type言,失敗時panic v = x.(T) // type言,失败时panic
v = <-ch // 管道接收,失敗時返迴零值(阻塞不算是失 v = <-ch // 管道接收,失败时返回零值(阻塞不算是失
_, ok = m[key] // map返迴2個 _, ok = m[key] // map返回2个
_, ok = mm[""], false // map返迴1個 _, ok = mm[""], false // map返回1个
_ = mm[""] // map返迴1個 _ = mm[""] // map返回1个
``` ```
變量聲明一,我可以用下劃線空白標識`_`來丟棄不需要的值。 变量声明一,我可以用下划线空白标识`_`来丢弃不需要的值。
```Go ```Go
_, err = io.Copy(dst, src) // 丟棄字節數 _, err = io.Copy(dst, src) // 丢弃字节数
_, ok = x.(T) // 隻檢測類型,忽略具 _, ok = x.(T) // 只检测类型,忽略具
``` ```

View File

@@ -1,12 +1,12 @@
### 2.4.2. 可值性 ### 2.4.2. 可值性
賦值語句是式的值形式,但是程序中有很多地方會發生隱式的值行:函數調用會隱式地將調用參數的值賦值給函數的參數變量,一個返迴語句將隱式地將返迴操作的值賦值給結果變量,一個複合類型的字面量§4.2)也會産生賦值行。例如下面的句: 赋值语句是式的值形式,但是程序中有很多地方会发生隐式的值行:函数调用会隐式地将调用参数的值赋值给函数的参数变量,一个返回语句将隐式地将返回操作的值赋值给结果变量,一个复合类型的字面量§4.2)也会产生赋值行。例如下面的句:
```Go ```Go
medals := []string{"gold", "silver", "bronze"} medals := []string{"gold", "silver", "bronze"}
``` ```
式地slice的每元素進行賦值操作,類似這樣寫的行 式地slice的每元素进行赋值操作,类似这样写的行
```Go ```Go
medals[0] = "gold" medals[0] = "gold"
@@ -14,12 +14,12 @@ medals[1] = "silver"
medals[2] = "bronze" medals[2] = "bronze"
``` ```
map和chan的元素然不是普通的量,但是也有似的隱式賦值行 map和chan的元素然不是普通的量,但是也有似的隐式赋值行
不管是隱式還是顯式地值,在賦值語句左邊的變量和右邊最終的求到的值必有相同的數據類型。更直白地説,隻有右的值對於左邊的變量是可值的,賦值語句才是允的。 不管是隐式还是显式地值,在赋值语句左边的变量和右边最终的求到的值必有相同的数据类型。更直白地说,只有右的值对于左边的变量是可值的,赋值语句才是允的。
值性的規則對於不同型有着不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:型必完全匹配nil可以賦值給任何指或引用型的量。常量§3.6有更活的賦值規則,因爲這樣可以避免不必要的式的類型轉換 值性的规则对于不同型有着不同要求,对每个新类型特殊的地方我们会专门解释。对于目前我们已经讨论过的类型,它的规则是简单的:型必完全匹配nil可以赋值给任何指或引用型的量。常量§3.6有更活的赋值规则,因为这样可以避免不必要的式的类型转换
對於兩個值是否可以用`==``!=`行相等比的能力也和可值能力有關繫:對於任何型的值的相等比,第二值必須是對第一個值類型對應的變量是可值的,反之依然。和前面一,我們會對每個新類型比特殊的地方做專門的解 对于两个值是否可以用`==``!=`行相等比的能力也和可值能力有关系:对于任何型的值的相等比,第二值必须是对第一个值类型对应的变量是可值的,反之依然。和前面一,我们会对每个新类型比特殊的地方做专门的解

View File

@@ -1,28 +1,28 @@
## 2.4. ## 2.4.
使用賦值語句可以更新一個變量的值,最簡單的賦值語句是要被值的量放在=的左,新值的表式放在=的右 使用赋值语句可以更新一个变量的值,最简单的赋值语句是要被值的量放在=的左,新值的表式放在=的右
```Go ```Go
x = 1 // 命名量的 x = 1 // 命名量的
*p = true // 通過指針間接賦 *p = true // 通过指针间接赋
person.name = "bob" // 結構體字段 person.name = "bob" // 结构体字段
count[x] = count[x] * scale // 數組、slice或map的元素 count[x] = count[x] * scale // 数组、slice或map的元素
``` ```
特定的二元算術運算符和賦值語句的合操作有一個簡潔形式,例如上面最後的語句可以重寫爲 特定的二元算术运算符和赋值语句的合操作有一个简洁形式,例如上面最后的语句可以重写为
```Go ```Go
count[x] *= scale count[x] *= scale
``` ```
這樣可以省去對變量表式的重複計算。 这样可以省去对变量表式的重复计算。
數值變量也可以支持`++`增和`--`遞減語句(譯註:自增和自減是語句,而不是表式,因此`x = i++`的表式是錯誤的): 数值变量也可以支持`++`增和`--`递减语句(译注:自增和自减是语句,而不是表式,因此`x = i++`的表式是错误的):
```Go ```Go
v := 1 v := 1
v++ // 等方式 v = v + 1v 成 2 v++ // 等方式 v = v + 1v 成 2
v-- // 等方式 v = v - 1v 成 1 v-- // 等方式 v = v - 1v 成 1
``` ```
{% include "./ch2-04-1.md" %} {% include "./ch2-04-1.md" %}

View File

@@ -1,24 +1,24 @@
## 2.5. ## 2.5.
量或表式的型定義了對應存儲值的性特,例如值在存的存大小或者是元素的bit個數),它們在內部是如何表的,是否支持一些操作符,以及它自己關聯的方法集等。 量或表式的型定义了对应存储值的性特,例如值在存的存大小或者是元素的bit个数),它们在内部是如何表的,是否支持一些操作符,以及它自己关联的方法集等。
在任何程序中都存在一些量有着相同的內部結構,但是表示完全不同的概念。例如,一int型的量可以用表示一個循環的迭代索引、或者一個時間戳、或者一文件描述符、或者一月份;一float64型的量可以用表示每秒移動幾米的速度、或者是不同溫度單位下的度;一字符串可以用表示一個密碼或者一個顔色的名 在任何程序中都存在一些量有着相同的内部结构,但是表示完全不同的概念。例如,一int型的量可以用表示一个循环的迭代索引、或者一个时间戳、或者一文件描述符、或者一月份;一float64型的量可以用表示每秒移动几米的速度、或者是不同温度单位下的度;一字符串可以用表示一个密码或者一个颜色的名
個類型聲明語句創建了一新的型名,和現有類型具有相同的底層結構。新命名的型提供了一方法,用分隔不同概念的型,這樣卽使它們底層類型相同也是不兼容的。 个类型声明语句创建了一新的型名,和现有类型具有相同的底层结构。新命名的型提供了一方法,用分隔不同概念的型,这样即使它们底层类型相同也是不兼容的。
```Go ```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: > 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.
爲了説明類型聲明,我們將不同溫度單位分别定義爲不同的型: 为了说明类型声明,我们将不同温度单位分别定义为不同的型:
<u><i>gopl.io/ch2/tempconv0</i></u> <u><i>gopl.io/ch2/tempconv0</i></u>
```Go ```Go
@@ -27,13 +27,13 @@ package tempconv
import "fmt" import "fmt"
type Celsius float64 // 攝氏溫 type Celsius float64 // 摄氏温
type Fahrenheit float64 // 華氏溫 type Fahrenheit float64 // 华氏温
const ( const (
AbsoluteZeroC Celsius = -273.15 // 絶對零度 AbsoluteZeroC Celsius = -273.15 // 绝对零度
FreezingC Celsius = 0 // 結冰點溫 FreezingC Celsius = 0 // 结冰点温
BoilingC Celsius = 100 // 沸水 BoilingC Celsius = 100 // 沸水
) )
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } 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) } 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 ```Go
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C 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 fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
``` ```
較運算符`==``<`也可以用來比較一個命名型的量和另一有相同型的量,或有着相同底層類型的未命名型的值之做比。但是如果兩個值有着不同的型,不能直接行比 较运算符`==``<`也可以用来比较一个命名型的量和另一有相同型的量,或有着相同底层类型的未命名型的值之做比。但是如果两个值有着不同的型,不能直接行比
```Go ```Go
var c Celsius var c Celsius
@@ -67,19 +67,19 @@ fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"! 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 ```Go
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
``` ```
許多類型都會定義一個String方法爲當使用fmt包的打印方法時,將會優先使用該類型對應的String方法返迴的結果打印,我們將在7.1節講述。 许多类型都会定义一个String方法为当使用fmt包的打印方法时,将会优先使用该类型对应的String方法返回的结果打印,我们将在7.1节讲述。
```Go ```Go
c := FToC(212.0) c := FToC(212.0)

View File

@@ -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包需要先入:
<u><i>gopl.io/ch2/cf</i></u> <u><i>gopl.io/ch2/cf</i></u>
```Go ```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 $ go build gopl.io/ch2/cf
@@ -48,8 +48,8 @@ $ ./cf -40
-40°F = -40°C, -40°C = -40°F -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的单位转换,长度单位可以对应英尺和米,重量位可以对应磅和公斤等。

View File

@@ -1,28 +1,28 @@
### 2.6.2. 包的初始化 ### 2.6.2. 包的初始化
包的初始化首先是解決包級變量的依賴順序,然安照包級變量聲明出現的順序依次初始化: 包的初始化首先是解决包级变量的依赖顺序,然安照包级变量声明出现的顺序依次初始化:
```Go ```Go
var a = b + c // a 第三初始化, 3 var a = b + c // a 第三初始化, 3
var b = f() // b 第二初始化, 2, 通過調用 f (依c) var b = f() // b 第二初始化, 2, 通过调用 f (依c)
var c = 1 // c 第一初始化, 1 var c = 1 // c 第一初始化, 1
func f() int { return c + 1 } func f() int { return c + 1 }
``` ```
如果包中含有多.go源文件們將按照發給編譯器的順序進行初始化Go言的建工具首先會將.go文件根文件名排序,然依次調用編譯器編譯 如果包中含有多.go源文件们将按照发给编译器的顺序进行初始化Go言的建工具首先会将.go文件根文件名排序,然依次调用编译器编译
對於在包級别聲明的量,如果有初始化表達式則用表式初始化,有一些有初始化表式的,例如某些表格數據初始化不是一個簡單的賦值過程。在這種情況下,我可以用一特殊的init初始化函數來簡化初始化工作。每文件都可以包含多init初始化函 对于在包级别声明的量,如果有初始化表达式则用表式初始化,有一些有初始化表式的,例如某些表格数据初始化不是一个简单的赋值过程。在这种情况下,我可以用一特殊的init初始化函数来简化初始化工作。每文件都可以包含多init初始化函
```Go ```Go
func init() { /* ... */ } func init() { /* ... */ }
``` ```
這樣的init初始化函除了不能被調用或引用外,其他行和普通函數類似。在每文件中的init初始化函,在程序開始執行時按照它們聲明的序被自動調用。 这样的init初始化函除了不能被用或引用外,其他行和普通函数类似。在每文件中的init初始化函,在程序开始执行时按照它们声明的序被自动调用。
包在解決依賴的前提下,以導入聲明的序初始化,每個包隻會被初始化一次。因此,如果一p包入了q包在p包初始化的候可以認爲q包必然已初始化了。初始化工作是自下而上行的main包最被初始化。以這種方式,可以保在main函數執行之前,所有依然的包都已完成初始化工作了。 包在解决依赖的前提下,以导入声明的序初始化,每个包只会被初始化一次。因此,如果一p包入了q包在p包初始化的候可以认为q包必然已初始化了。初始化工作是自下而上行的main包最被初始化。以这种方式,可以保在main函数执行之前,所有依然的包都已完成初始化工作了。
下面的代碼定義了一PopCount函,用於返迴一個數字中含二進製1bit的個數。它使用init初始化函數來生成助表格pcpc表格用於處理每8bit度的字含二進製的1bit的bit個數,這樣的話在處理64bit度的數字時就沒有必要循64次需要8次表就可以了。(這併不是最快的統計1bit目的算法但是它可以方便演示init函的用法,且演示了如果生成助表格,這是編程中常用的技)。 下面的代码定义了一PopCount函,用于返回一个数字中含二进制1bit的个数。它使用init初始化函数来生成助表格pcpc表格用于处理每8bit度的字含二进制的1bit的bit个数,这样的话在处理64bit度的数字时就没有必要循64次需要8次表就可以了。(这并不是最快的统计1bit目的算法但是它可以方便演示init函的用法,且演示了如果生成助表格,这是编程中常用的技)。
<u><i>gopl.io/ch2/popcount</i></u> <u><i>gopl.io/ch2/popcount</i></u>
```Go ```Go
@@ -50,7 +50,7 @@ func PopCount(x uint64) int {
} }
``` ```
譯註對於pc這類需要複雜處理的初始化,可以通過將初始化邏輯包裝爲一個匿名函數處理,像下面這樣 译注对于pc这类需要复杂处理的初始化,可以通过将初始化逻辑包装为一个匿名函数处理,像下面这样
```Go ```Go
// pc[i] is the population count of i. // 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 ```Go
for i, _ := range pc { 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函,然后比较性能。

View File

@@ -1,16 +1,16 @@
## 2.6. 包和文件 ## 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源文件中
<u></i>gopl.io/ch2/tempconv</i></u> <u></i>gopl.io/ch2/tempconv</i></u>
```Go ```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) } func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
``` ```
轉換函數則放在另一conv.go源文件中 转换函数则放在另一conv.go源文件中
```Go ```Go
package tempconv 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) } 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 ```Go
fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C" fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C"
``` ```
將攝氏溫度轉換爲華氏溫需要先用import語句導入gopl.io/ch2/tempconv包就可以使用下面的代碼進行轉換了: 将摄氏温度转换为华氏温需要先用import语句导入gopl.io/ch2/tempconv包就可以使用下面的代码进行转换了:
```Go ```Go
fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F" 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°CKelvin絶對溫度1K和氏度1°C的單位間隔是一的。 **练习 2.1** 向tempconv包添加型、常量和函数用来处理Kelvin绝对温度的转换Kelvin 绝对零度是273.15°CKelvin绝对温度1K和氏度1°C的单位间隔是一的。
{% include "./ch2-06-1.md" %} {% include "./ch2-06-1.md" %}

View File

@@ -1,18 +1,18 @@
## 2.7. 作用域 ## 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 ```Go
func f() {} func f() {}
@@ -27,7 +27,7 @@ func main() {
} }
``` ```
在函數中詞法域可以深度嵌套,因此部的一個聲明可能屏蔽外部的明。還有許多語法塊是if或for等控製流語句構造的。下面的代有三不同的量x爲它們是定在不同的法域(這個例子隻是爲了演示作用域規則,但不是好的編程風格)。 在函数中词法域可以深度嵌套,因此部的一个声明可能屏蔽外部的明。还有许多语法块是if或for等控制流语句构造的。下面的代有三不同的量x为它们是定在不同的法域(这个例子只是为了演示作用域规则,但不是好的编程风格)。
```Go ```Go
func main() { 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 ```Go
func main() { func main() {
@@ -58,7 +58,7 @@ func main() {
} }
``` ```
和for循環類if和switch句也會在條件部分創建隱式詞法域,有它們對應的執行體詞法域。下面的if-else測試鏈演示了x和y的有效作用域范 和for循环类if和switch句也会在条件部分创建隐式词法域,有它们对应的执行体词法域。下面的if-else测试链演示了x和y的有效作用域范
```Go ```Go
if x := f(); x == 0 { 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 fmt.Println(x, y) // compile error: x and y are not visible here
``` ```
第二if句嵌套在第一個內部,因此第一if語句條件初始化法域明的量在第二if中也可以訪問。switch句的每分支也有似的法域規則:條件部分爲一個隱式詞法域,然後每個是每分支的法域。 第二if句嵌套在第一个内部,因此第一if语句条件初始化法域明的量在第二if中也可以访问。switch句的每分支也有似的法域规则:条件部分为一个隐式词法域,然后每个是每分支的法域。
在包别,明的順序併不會影響作用域范,因此一個先聲明的可以引用它自身或者是引用面的一個聲明,可以讓我們定義一些相互嵌套或遞歸的類型或函。但是如果一個變量或常量遞歸引用了自身,則會産生編譯錯誤 在包别,明的顺序并不会影响作用域范,因此一个先声明的可以引用它自身或者是引用面的一个声明,可以让我们定义一些相互嵌套或递归的类型或函。但是如果一个变量或常量递归引用了自身,则会产生编译错误
這個程序中: 这个程序中:
```Go ```Go
if f, err := os.Open(fname); err != nil { // compile error: unused: f 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.Close() // compile error: undefined f
``` ```
量f的作用域有在if語句內,因此面的語句將無法引入它,這將導致編譯錯誤。你可能收到一局部量f沒有聲明的錯誤提示,具體錯誤信息依賴編譯器的實現 量f的作用域有在if语句内,因此面的语句将无法引入它,这将导致编译错误。你可能收到一局部量f没有声明的错误提示,具体错误信息依赖编译器的实现
通常需要在if之前聲明變量,這樣可以確保後面的句依然可以訪問變量: 通常需要在if之前声明变量,这样可以确保后面的句依然可以访问变量:
```Go ```Go
f, err := os.Open(fname) f, err := os.Open(fname)
@@ -98,7 +98,7 @@ f.ReadByte()
f.Close() f.Close()
``` ```
你可能會考慮通過將ReadByte和Close移到if的else塊來解決這個問題 你可能会考虑通过将ReadByte和Close移到if的else块来解决这个问题
```Go ```Go
if f, err := os.Open(fname); err != nil { 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 ```Go
var cwd string var cwd string
@@ -125,9 +125,9 @@ func init() {
} }
``` ```
然cwd在外部已經聲明過,但是`:=`語句還是將cwd和err重新聲明爲新的局部量。因爲內部聲明的cwd屏蔽外部的明,因此上面的代碼併不會正確更新包級聲明的cwd量。 然cwd在外部已经声明过,但是`:=`语句还是将cwd和err重新声明为新的局部量。因为内部声明的cwd屏蔽外部的明,因此上面的代码并不会正确更新包级声明的cwd量。
於當前的編譯器會檢測到局部明的cwd併沒有本使用,然後報告這可能是一個錯誤,但是這種檢測併不可靠。因一些小的代碼變更,例如增加一局部cwd的打印句,就可能導致這種檢測失效。 于当前的编译器会检测到局部明的cwd并没有本使用,然后报告这可能是一个错误,但是这种检测并不可靠。因一些小的代码变更,例如增加一局部cwd的打印句,就可能导致这种检测失效。
```Go ```Go
var cwd string var cwd string
@@ -141,9 +141,9 @@ func init() {
} }
``` ```
全局的cwd量依然是有被正初始化的,而且看似正常的日誌輸出更是讓這個BUG更加晦。 全局的cwd量依然是有被正初始化的,而且看似正常的日志输出更是让这个BUG更加晦。
多方式可以避免出現類似潛在的問題。最直接的方法是通過單獨聲明err量,避免使用`:=`簡短聲明方式: 多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明err量,避免使用`:=`简短声明方式:
```Go ```Go
var cwd string var cwd string
@@ -157,5 +157,5 @@ func init() {
} }
``` ```
們已經看到包、文件、明和句如何來表達一個程序結構。在下面的兩個章節,我們將探討數據的結構 们已经看到包、文件、明和句如何来表达一个程序结构。在下面的两个章节,我们将探讨数据的结构

View File

@@ -1,5 +1,5 @@
# 第2章 程序結構 # 第2章 程序结构
Go言和其他編程語言一,一大的程序是由很多小的基礎構件組成的。量保存值,簡單的加法和減法運算被合成較複雜的表式。基礎類型被聚合爲數組或結構體等更複雜的數據結構。然使用if和for之的控製語句來組織和控製表達式的行流程。然後多個語句被組織到一個個函數中,以便代的隔離和複用。函以源文件和包的方式被組織 Go言和其他编程语言一,一大的程序是由很多小的基础构件组成的。量保存值,简单的加法和减法运算被合成较复杂的表式。基础类型被聚合为数组或结构体等更复杂的数据结构。然使用if和for之的控制语句来组织和控制表达式的行流程。然后多个语句被组织到一个个函数中,以便代的隔离和复用。函以源文件和包的方式被组织
們已經在前面章的例子中看到了很多例子。在本章中,我們將深入討論Go程序基礎結構方面的一些細節。每示例程序都是刻意寫的簡單,這樣我們可以減少複雜的算法或數據結構等不相關的問題帶來的榦擾,從而可以專註於Go言本身的學習 们已经在前面章的例子中看到了很多例子。在本章中,我们将深入讨论Go程序基础结构方面的一些细节。每示例程序都是刻意写的简单,这样我们可以减少复杂的算法或数据结构等不相关的问题带来的干扰,从而可以专注于Go言本身的学习

View File

@@ -1,20 +1,20 @@
## 3.1. 整型 ## 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 ```Go
var u uint8 = 255 var u uint8 = 255
@@ -40,7 +40,7 @@ var i int8 = 127
fmt.Println(i, i+1, i*i) // "127 -128 1" fmt.Println(i, i+1, i*i) // "127 -128 1"
``` ```
兩個相同的整數類型可以使用下面的二元比較運算符行比;比較表達式的果是布爾類型。 两个相同的整数类型可以使用下面的二元比较运算符行比;比较表达式的果是布尔类型。
``` ```
== equal to == equal to
@@ -51,31 +51,31 @@ fmt.Println(i, i+1, i*i) // "127 -128 1"
>= greater than or equal to >= 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 & 位算 AND
| 位算 OR | 位算 OR
^ 位算 XOR ^ 位算 XOR
&^ 位清空 (AND NOT) &^ 位清空 (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 ```Go
var x uint8 = 1<<1 | 1<<5 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} fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
``` ```
6.5節給出了一可以遠大於一個字節的整集的實現。) 6.5节给出了一可以远大于一个字节的整集的实现。)
`x<<n``x>>n`移位算中,定了移位操作bit部分必須是無符號數被操作的x可以是有符號或無符號數。算上,一`x<<n`左移算等價於乘以$$2^n$$,一`x>>n`右移算等價於除以$$2^n$$。 `x<<n``x>>n`移位算中,定了移位操作bit部分必须是无符号数被操作的x可以是有符号或无符号数。算上,一`x<<n`左移算等价于乘以$$2^n$$,一`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 ```Go
medals := []string{"gold", "silver", "bronze"} 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 ```Go
var apples int32 = 1 var apples int32 = 1
@@ -128,19 +128,19 @@ var oranges int16 = 2
var compote int = apples + oranges // compile error var compote int = apples + oranges // compile error
``` ```
當嚐試編譯這三個語句時,將産生一個錯誤信息: 当尝试编译这三个语句时,将产生一个错误信息:
``` ```
invalid operation: apples + oranges (mismatched types int32 and int16) invalid operation: apples + oranges (mismatched types int32 and int16)
``` ```
這種類型不匹配的問題可以有幾種不同的方法脩複,最常方法是將它們都顯式轉型爲一個常見類型: 这种类型不匹配的问题可以有几种不同的方法修复,最常方法是将它们都显式转型为一个常见类型:
```Go ```Go
var compote = int(apples) + int(oranges) var compote = int(apples) + int(oranges)
``` ```
如2.5所述,對於每種類型T如果轉換允許的話類型轉換操作T(x)將x轉換爲T類型。多整形數之間的相互轉換併不會改變數值;它們隻是告訴編譯器如何解釋這個值。但是對於將一個大尺寸的整數類型轉爲一個小尺寸的整數類型,或者是將一個浮點數轉爲整數,可能會改變數值或失精度: 如2.5所述,对于每种类型T如果转换允许的话类型转换操作T(x)将x转换为T类型。多整形数之间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大尺寸的整数类型转为一个小尺寸的整数类型,或者是将一个浮点数转为整数,可能会改变数值或失精度:
```Go ```Go
f := 3.141 // a float64 f := 3.141 // a float64
@@ -150,16 +150,16 @@ f = 1.99
fmt.Println(int(f)) // "1" fmt.Println(int(f)) // "1"
``` ```
點數到整數的轉換將丟失任何小部分,然後向數軸零方向截。你應該避免可能超出目標類型表示范圍的數值類型轉換,因爲截斷的行可能依賴於具體的實現 点数到整数的转换将丢失任何小部分,然后向数轴零方向截。你应该避免可能超出目标类型表示范围的数值类型转换,因为截断的行可能依赖于具体的实现
```Go ```Go
f := 1e100 // a float64 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 ```Go
o := 0666 o := 0666
@@ -170,11 +170,11 @@ fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF // 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 ```Go
ascii := 'a' ascii := 'a'

View File

@@ -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 ```Go
var f float32 = 16777216 // 1 << 24 var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1) // "true"! fmt.Println(f == f+1) // "true"!
``` ```
點數的字面值可以直接寫小數部分,像這樣 点数的字面值可以直接写小数部分,像这样
```Go ```Go
const e = 2.71828 // (approximately) const e = 2.71828 // (approximately)
``` ```
數點前面或面的字都可能被省略(例如.707或1.)。很小或很大的最好用科學計數法書寫,通e或E指定指部分: 数点前面或面的字都可能被省略(例如.707或1.)。很小或很大的最好用科学计数法书写,通e或E指定指部分:
```Go ```Go
const Avogadro = 6.02214129e23 // 阿伏伽德羅常數 const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34 // 普朗剋常數 const Planck = 6.62606957e-34 // 普朗克常数
``` ```
用Printf函的%g參數打印浮點數,將采用更緊湊的表示形式打印,提供足的精度,但是對應表格的數據,使用%e帶指數)或%f的形式打印可能更合。所有的這三個打印形式都可以指定打印的度和控打印精度。 用Printf函的%g参数打印浮点数,将采用更紧凑的表示形式打印,提供足的精度,但是对应表格的数据,使用%e带指数)或%f的形式打印可能更合。所有的这三个打印形式都可以指定打印的度和控打印精度。
```Go ```Go
for x := 0; x < 8; x++ { 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 x = 0 e^x = 1.000
@@ -45,21 +45,21 @@ x = 6 e^x = 403.429
x = 7 e^x = 1096.633 x = 7 e^x = 1096.633
``` ```
math包中除了提供大量常用的數學函數外,提供了IEEE754浮點數標準中定的特殊值的建和測試:正無窮大和負無窮大,分别用表示太大溢出的字和除零的果;有NaN非,一般用表示效的除法操作果0/0或Sqrt(-1). math包中除了提供大量常用的数学函数外,提供了IEEE754浮点数标准中定的特殊值的建和测试:正无穷大和负无穷大,分别用表示太大溢出的字和除零的果;有NaN非,一般用表示效的除法操作果0/0或Sqrt(-1).
```Go ```Go
var z float64 var z float64
fmt.Println(z, -z, 1/z, -1/z, z/z) // "0 -0 +Inf -Inf NaN" fmt.Println(z, -z, 1/z, -1/z, z/z) // "0 -0 +Inf -Inf NaN"
``` ```
math.IsNaN用於測試一個數是否是非NaNmath.NaN則返迴非數對應的值。然可以用math.NaN表示一非法的果,但是測試一個結果是否是非NaN是充滿風險的,因NaN和任何都是不相等的(譯註:在浮點數NaN、正無窮大和負無窮大都不是唯一的,每都有非常多的bit模式表示 math.IsNaN用于测试一个数是否是非NaNmath.NaN则返回非数对应的值。然可以用math.NaN表示一非法的果,但是测试一个结果是否是非NaN是充满风险的,因NaN和任何都是不相等的(译注:在浮点数NaN、正无穷大和负无穷大都不是唯一的,每都有非常多的bit模式表示
```Go ```Go
nan := math.NaN() nan := math.NaN()
fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false" fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"
``` ```
如果一個函數返迴的浮點數結果可能失,最好的做法是用單獨的標誌報告失,像這樣 如果一个函数返回的浮点数结果可能失,最好的做法是用单独的标志报告失,像这样
```Go ```Go
func compute() (value float64, ok bool) { 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) ![](../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) ![](../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文件可能輸出無效的<polygon>形元素(雖然許多SVG渲染器妥善處理這類問題)。改程序跳過無效的多形。 **练习 3.1** 如果f函数返回的是无限制的float64值SVG文件可能输出无效的<polygon>形元素(虽然许多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.7Lissajous例子的函數,構造一web服器,用於計算函數麴面然後返迴SVG數據給客戶端。服器必須設置Content-Type部: **练习 3.4** 考1.7Lissajous例子的函数,构造一web服器,用于计算函数曲面然后返回SVG数据给客户端。服器必须设置Content-Type部:
```Go ```Go
w.Header().Set("Content-Type", "image/svg+xml") w.Header().Set("Content-Type", "image/svg+xml")
``` ```
一步在Lissajous例子中不是必的,因爲服務器使用標準的PNG像格式,可以根前面的512個字節自動輸出對應的頭部。)允許客戶端通HTTP請求參數設置高度、度和色等參數 一步在Lissajous例子中不是必的,因为服务器使用标准的PNG像格式,可以根前面的512个字节自动输出对应的头部。)允许客户端通HTTP请求参数设置高度、度和色等参数

View File

@@ -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 ```Go
var x complex128 = complex(1, 2) // 1+2i var x complex128 = complex(1, 2) // 1+2i
@@ -10,28 +10,28 @@ fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10" fmt.Println(imag(x*y)) // "10"
``` ```
如果一個浮點數面值或一個十進製整數面值面跟着一i例如3.141592i或2i將構成一個複數的虛部,複數的實部是0 如果一个浮点数面值或一个十进制整数面值面跟着一i例如3.141592i或2i将构成一个复数的虚部,复数的实部是0
```Go ```Go
fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1 fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1
``` ```
在常量算術規則下,一個複數常量可以加到另一普通值常量(整或浮點數、實部或部),我可以用自然的方式書寫複數就像1+2i或之等價的寫法2i+1。上面x和y的聲明語句還可以化: 在常量算术规则下,一个复数常量可以加到另一普通值常量(整或浮点数、实部或部),我可以用自然的方式书写复数就像1+2i或之等价的写法2i+1。上面x和y的声明语句还可以化:
```Go ```Go
x := 1 + 2i x := 1 + 2i
y := 3 + 4i y := 3 + 4i
``` ```
複數也可以用==和!=行相等比較。隻有兩個複數的實部和部都相等的候它才是相等的(譯註:浮點數的相等比是危的,需要特别小心理精度問題)。 复数也可以用==和!=行相等比较。只有两个复数的实部和部都相等的候它才是相等的(译注:浮点数的相等比是危的,需要特别小心理精度问题)。
math/cmplx包提供了複數處理的多函,例如求複數的平方根函和求冪函數 math/cmplx包提供了复数处理的多函,例如求复数的平方根函和求幂函数
```Go ```Go
fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)" fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)"
``` ```
下面的程序使用complex128複數算法生成一Mandelbrot像。 下面的程序使用complex128复数算法生成一Mandelbrot像。
<u><i>gopl.io/ch3/mandelbrot</i></u> <u><i>gopl.io/ch3/mandelbrot</i></u>
```Go ```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) ![](../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参数

View File

@@ -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 ```Go
s != "" && s[0] == 'x' s != "" && s[0] == 'x'
``` ```
其中s[0]操作如果應用於空字符串將會導致panic 其中s[0]操作如果应用于空字符串将会导致panic
`&&`優先級`||``&&`對應邏輯乘法`||`對應邏輯加法乘法比加法優先級要高下面形式的布爾表達式是不需要加小括弧的 `&&`优先级`||``&&`对应逻辑乘法`||`对应逻辑加法乘法比加法优先级要高下面形式的布尔表达式是不需要加小括弧的
```Go ```Go
if 'a' <= c && c <= 'z' || if 'a' <= c && c <= 'z' ||
@@ -20,7 +20,7 @@ if 'a' <= c && c <= 'z' ||
} }
``` ```
爾值併不會隱式轉換爲數字值0或1反之亦然使用一個顯式的if語句輔助轉換 尔值并不会隐式转换为数字值0或1反之亦然使用一个显式的if语句辅助转换
```Go ```Go
i := 0 i := 0
@@ -29,7 +29,7 @@ if b {
} }
``` ```
如果需要常做似的轉換, 成一個函數會更方便: 如果需要常做似的转换, 成一个函数会更方便:
```Go ```Go
// btoi returns 1 if b is true and 0 if false. // btoi returns 1 if b is true and 0 if false.
@@ -41,7 +41,7 @@ func btoi(b bool) int {
} }
``` ```
字到布型的逆轉換則非常簡單, 過爲了保持對稱, 也可以包裝一個函數: 字到布型的逆转换则非常简单, 过为了保持对称, 也可以包装一个函数:
```Go ```Go
// itob reports whether i is non-zero. // itob reports whether i is non-zero.

View File

@@ -1,6 +1,6 @@
### 3.5.1. 字符串面值 ### 3.5.1. 字符串面值
字符串值也可以用字符串面值方式編寫,隻要將一繫列字序列包含在雙引號卽可: 字符串值也可以用字符串面值方式编写,只要将一系列字序列包含在双引号即可:
``` ```
"Hello, 世界" "Hello, 世界"
@@ -8,28 +8,28 @@
![](../images/ch3-04.png) ![](../images/ch3-04.png)
Go言源文件是用UTF8編碼,併且Go言的文本字符串也以UTF8編碼的方式理,因此我可以Unicode碼點也寫到字符串面值中。 Go言源文件是用UTF8编码,并且Go言的文本字符串也以UTF8编码的方式理,因此我可以Unicode码点也写到字符串面值中。
在一個雙引號包含的字符串面值中,可以用以反斜`\`開頭的轉義序列插入任意的數據。下面的行、迴車和製表符等是常的ASCII控製代碼的轉義方式: 在一个双引号包含的字符串面值中,可以用以反斜`\`开头的转义序列插入任意的数据。下面的行、回车和制表符等是常的ASCII控制代码的转义方式:
``` ```
\a 響鈴 \a 响铃
\b 退格 \b 退格
\f 換頁 \f 换页
\n \n
\r 迴車 \r 回车
\t 表符 \t 表符
\v 垂直表符 \v 垂直表符
\' 單引號 (用在 '\'' 形式的rune符面值中) \' 单引号 (用在 '\'' 形式的rune符面值中)
\" 雙引號 (用在 "..." 形式的字符串面值中) \" 双引号 (用在 "..." 形式的字符串面值中)
\\ 反斜 \\ 反斜
``` ```
可以通十六進製或八進製轉義在字符串面值包含任意的字。一十六進製的轉義形式是\xhh其中兩個h表示十六進製數字(大或小都可以)。一個八進製轉義形式是\ooo包含三個八進製的o0到7但是不能超`\377`譯註:對應一個字節的范,十進製爲255。每一個單一的字節表達一個特定的值。稍後我們將看到如何將一個Unicode碼點寫到字符串面值中。 可以通十六进制或八进制转义在字符串面值包含任意的字。一十六进制的转义形式是\xhh其中两个h表示十六进制数字(大或小都可以)。一个八进制转义形式是\ooo包含三个八进制的o0到7但是不能超`\377`译注:对应一个字节的范,十进制为255。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。
原生的字符串面值形式是`...`,使用反引```代替雙引號。在原生的字符串面值中,沒有轉義操作;全部的容都是字面的意思,包含退格和行,因此一程序中的原生字符串面值可能跨越多行(譯註:在原生字符串面值部是法直接```字符的,可以用八進製或十六進製轉義或+"```"接字符串常量完成)。唯一的特殊理是會刪除迴車以保在所有平台上的值都是一的,包括那些把迴車也放入文本文件的繫統(譯註Windows繫統會把迴車和換行一起放入文本文件中)。 原生的字符串面值形式是`...`,使用反引```代替双引号。在原生的字符串面值中,没有转义操作;全部的容都是字面的意思,包含退格和行,因此一程序中的原生字符串面值可能跨越多行(译注:在原生字符串面值部是法直接```字符的,可以用八进制或十六进制转义或+"```"接字符串常量完成)。唯一的特殊理是会删除回车以保在所有平台上的值都是一的,包括那些把回车也放入文本文件的系统(译注Windows系统会把回车和换行一起放入文本文件中)。
原生字符串面值用於編寫正則表達式會很方便,因爲正則表達式往往包含很多反斜。原生字符串面值同時被廣泛應用於HTML模、JSON面值、命令行提示信息以及那些需要展到多行的景。 原生字符串面值用于编写正则表达式会很方便,因为正则表达式往往包含很多反斜。原生字符串面值同时被广泛应用于HTML模、JSON面值、命令行提示信息以及那些需要展到多行的景。
```Go ```Go
const GoUsage = `Go is a tool for managing Go source code. const GoUsage = `Go is a tool for managing Go source code.

View File

@@ -1,12 +1,12 @@
### 3.5.2. Unicode ### 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-4Unicode碼點都使用同的大小32bit表示。這種方式比較簡單統一,但是它會浪費很多存儲空間,因爲大數據計算機可讀的文本是ASCII字符來每個ASCII字符需要8bit或1字就能表示。而且使是常用的字符也遠少於65,536,也就是用16bit編碼方式就能表常用字符。但是,有其它更好的編碼方法 可以将一个符文序列表示为一个int32序列。这种编码方式叫UTF-32或UCS-4Unicode码点都使用同的大小32bit表示。这种方式比较简单统一,但是它会浪费很多存储空间,因为大数据计算机可读的文本是ASCII字符来每个ASCII字符需要8bit或1字就能表示。而且使是常用的字符也远少于65,536,也就是用16bit编码方式就能表常用字符。但是,有其它更好的编码方法

View File

@@ -1,6 +1,6 @@
### 3.5.3. UTF-8 ### 3.5.3. UTF-8
UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼。UTF8編碼由Go言之父Ken Thompson和Rob Pike共同明的,在已是Unicode的標準。UTF8編碼使用1到4個字節來表示每Unicode碼點ASCII部分字符使用1個字節常用字符部分使用2或3個字節表示。每個符號編碼後第一個字節的高端bit位用表示共有多少編碼個字節。如果第一個字節的高端bit0表示對應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位用表示共有多少编码个字节。如果第一个字节的高端bit0表示对应7bit的ASCII字符ASCII字符每字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110则说明需要2个字节;后续的每高端bit都以10开头。更大的Unicode码点也是采用似的策略理。
``` ```
0xxxxxxx runes 0-127 (ASCII) 0xxxxxxx runes 0-127 (ASCII)
@@ -9,11 +9,11 @@ UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼。UTF8編碼
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused) 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" "\U00004e16\U0000754c"
``` ```
上面三個轉義序列都第一字符串提供替代法,但是它的值都是相同的。 上面三个转义序列都第一字符串提供替代法,但是它的值都是相同的。
Unicode轉義也可以使用在rune字符中。下面三字符是等的: Unicode转义也可以使用在rune字符中。下面三字符是等的:
``` ```
'世' '\u4e16' '\U00004e16' '世' '\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 ```Go
func HasPrefix(s, prefix string) bool { func HasPrefix(s, prefix string) bool {
@@ -40,7 +40,7 @@ func HasPrefix(s, prefix string) bool {
} }
``` ```
或者是後綴測試 或者是后缀测试
```Go ```Go
func HasSuffix(s, suffix string) bool { func HasSuffix(s, suffix string) bool {
@@ -48,7 +48,7 @@ func HasSuffix(s, suffix string) bool {
} }
``` ```
或者是包含子串測試 或者是包含子串测试
```Go ```Go
func Contains(s, substr string) bool { 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 ```Go
import "unicode/utf8" import "unicode/utf8"
@@ -73,7 +73,7 @@ fmt.Println(len(s)) // "13"
fmt.Println(utf8.RuneCountInString(s)) // "9" fmt.Println(utf8.RuneCountInString(s)) // "9"
``` ```
爲了處理這些眞實的字符,我需要一UTF8解器。unicode/utf8包提供了功能,我可以這樣使用: 为了处理这些真实的字符,我需要一UTF8解器。unicode/utf8包提供了功能,我可以这样使用:
```Go ```Go
for i := 0; i < len(s); { 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) ![](../images/ch3-05.png)
@@ -93,7 +93,7 @@ for i, r := range "Hello, 世界" {
} }
``` ```
可以使用一個簡單的循環來統計字符串中字符的目,像這樣 可以使用一个简单的循环来统计字符串中字符的目,像这样
```Go ```Go
n := 0 n := 0
@@ -102,7 +102,7 @@ for _, _ = range s {
} }
``` ```
像其它形式的循環那樣,我也可以忽略不需要的量: 像其它形式的循环那样,我也可以忽略不需要的量:
```Go ```Go
n := 0 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',在印刷中這個符號通常是一黑色六角或石形,里面包含一白色的問號"<22>"。程序遇到這樣的一字符,通常是一個危險信號,説明輸入併不是一完美沒有錯誤的UTF8字符串。 每一UTF8字符解,不管是式地用utf8.DecodeRuneInString解或是在range循环中隐式地解,如果遇到一个错误的UTF8编码输入,生成一特别的Unicode字符'\uFFFD',在印刷中这个符号通常是一黑色六角或石形,里面包含一白色的问号"<22>"。程序遇到这样的一字符,通常是一个危险信号,说明输入并不是一完美没有错误的UTF8字符串。
UTF8字符串作爲交換格式是非常方便的,但是在程序部采用rune序列可能更方便rune大小一致支持數組索引和方便切割。 UTF8字符串作为交换格式是非常方便的,但是在程序部采用rune序列可能更方便rune大小一致支持数组索引和方便切割。
string接受到[]rune的類型轉換,可以將一個UTF8編碼的字符串解碼爲Unicode字符序列 string接受到[]rune的类型转换,可以将一个UTF8编码的字符串解码为Unicode字符序列
```Go ```Go
// "program" in Japanese katakana // "program" in Japanese katakana
@@ -129,22 +129,22 @@ r := []rune(s)
fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]" 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 ```Go
fmt.Println(string(r)) // "プログラム" fmt.Println(string(r)) // "プログラム"
``` ```
將一個整數轉型爲字符串意思是生成以包含對應Unicode碼點字符的UTF8字符串 将一个整数转型为字符串意思是生成以包含对应Unicode码点字符的UTF8字符串
```Go ```Go
fmt.Println(string(65)) // "A", not "65" fmt.Println(string(65)) // "A", not "65"
fmt.Println(string(0x4eac)) // "京" fmt.Println(string(0x4eac)) // "京"
``` ```
如果對應碼點的字符是效的,用'\uFFFD'效字符作爲替換 如果对应码点的字符是效的,用'\uFFFD'效字符作为替换
```Go ```Go
fmt.Println(string(1234567)) // "<22>" fmt.Println(string(1234567)) // "<22>"

View File

@@ -1,14 +1,14 @@
### 3.5.4. 字符串和Byte切片 ### 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 ```Go
fmt.Println(basename("a/b/c.go")) // "c" 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" fmt.Println(basename("abc")) // "abc"
``` ```
第一版本併沒有使用任何,全部手工硬編碼實現 第一版本并没有使用任何,全部手工硬编码实现
<u><i>gopl.io/ch3/basename1</i></u> <u><i>gopl.io/ch3/basename1</i></u>
```Go ```Go
@@ -41,7 +41,7 @@ func basename(s string) string {
} }
``` ```
簡化個版本使用了strings.LastIndex庫函數 简化个版本使用了strings.LastIndex库函数
<u><i>gopl.io/ch3/basename2</i></u> <u><i>gopl.io/ch3/basename2</i></u>
```Go ```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”。这个版本只适用于整数类型;支持浮点数类型的支持留作练习
<u><i>gopl.io/ch3/comma</i></u> <u><i>gopl.io/ch3/comma</i></u>
```Go ```Go
@@ -71,11 +71,11 @@ func comma(s string) string {
} }
``` ```
入comma函數的參數是一字符串。如果入字符串的度小或等3的話,則不需要插入逗分隔符。否comma函數將在最後三個字符前位置字符串切割爲兩個兩個子串插入逗分隔符,然後通過遞歸調用自身出前面的子串。 入comma函数的参数是一字符串。如果入字符串的度小或等3的话,则不需要插入逗分隔符。否comma函数将在最后三个字符前位置字符串切割为两个两个子串插入逗分隔符,然后通过递归调用自身出前面的子串。
字符串是包含的隻讀字節數組,一旦建,是不可的。相比之下,一個字節slice的元素可以自由地改。 字符串是包含的只读字节数组,一旦建,是不可的。相比之下,一个字节slice的元素可以自由地改。
字符串和字slice之可以相互轉換 字符串和字slice之可以相互转换
```Go ```Go
s := "abc" s := "abc"
@@ -83,9 +83,9 @@ b := []byte(s)
s2 := string(b) 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 ```Go
func Contains(s, substr string) bool func Contains(s, substr string) bool
@@ -96,7 +96,7 @@ func Index(s, sep string) int
func Join(a []string, sep string) string func Join(a []string, sep string) string
``` ```
bytes包中也對應的六個函數 bytes包中也对应的六个函数
```Go ```Go
func Contains(b, subslice []byte) bool func Contains(b, subslice []byte) bool
@@ -107,9 +107,9 @@ func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte 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变量并不需要理化,因零值也是有效的:
<u><i>gopl.io/ch3/printints</i></u> <u><i>gopl.io/ch3/printints</i></u>
```Go ```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** 编写一个函数,判断两个字符串是否是是相互打的,也就是说它们有着相同的字符,但是对应不同的序。

View File

@@ -1,8 +1,8 @@
### 3.5.5. 字符串和字的轉換 ### 3.5.5. 字符串和字的转换
除了字符串、字符、字節之間的轉換,字符串和值之間的轉換也比較常見。由strconv包提供這類轉換功能。 除了字符串、字符、字节之间的转换,字符串和值之间的转换也比较常见。由strconv包提供这类转换功能。
將一個整數轉爲字符串,一方法是用fmt.Sprintf返迴一個格式化的字符串;另一方法是用strconv.Itoa(“整到ASCII”) 将一个整数转为字符串,一方法是用fmt.Sprintf返回一个格式化的字符串;另一方法是用strconv.Itoa(“整到ASCII”)
```Go ```Go
x := 123 x := 123
@@ -10,28 +10,28 @@ y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123" fmt.Println(y, strconv.Itoa(x)) // "123 123"
``` ```
FormatInt和FormatUint函可以用不同的進製來格式化字: FormatInt和FormatUint函可以用不同的进制来格式化字:
```Go ```Go
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011" 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 ```Go
s := fmt.Sprintf("x=%b", x) // "x=1111011" s := fmt.Sprintf("x=%b", x) // "x=1111011"
``` ```
如果要將一個字符串解析爲整數可以使用strconv包的Atoi或ParseInt函數,還有用解析無符號整數的ParseUint函 如果要将一个字符串解析为整数可以使用strconv包的Atoi或ParseInt函数,还有用解析无符号整数的ParseUint函
```Go ```Go
x, err := strconv.Atoi("123") // x is an int x, err := strconv.Atoi("123") // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits
``` ```
ParseInt函的第三個參數是用指定整型的大小例如16表示int160表示int。在任何情下,返迴的結果y是int64型,你可以通過強製類型轉換將它轉爲更小的整數類型。 ParseInt函的第三个参数是用指定整型的大小例如16表示int160表示int。在任何情下,返回的结果y是int64型,你可以通过强制类型转换将它转为更小的整数类型。
候也使用fmt.Scanf解析入的字符串和字,特别是字符串和字混合在一行的候,它可以靈活處理不完整或不規則的輸入。 候也使用fmt.Scanf解析入的字符串和字,特别是字符串和字混合在一行的候,它可以灵活处理不完整或不规则的输入。

View File

@@ -1,8 +1,8 @@
## 3.5. 字符串 ## 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 ```Go
s := "hello, world" s := "hello, world"
@@ -10,23 +10,23 @@ fmt.Println(len(s)) // "12"
fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w') fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w')
``` ```
如果試圖訪問超出字符串索引范的字節將會導致panic 如果试图访问超出字符串索引范的字节将会导致panic
```Go ```Go
c := s[len(s)] // panic: index out of range 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 ```Go
fmt.Println(s[0:5]) // "hello" fmt.Println(s[0:5]) // "hello"
``` ```
如果索引超出字符串范或者j小i的話將導致panic 如果索引超出字符串范或者j小i的话将导致panic
不管i是j都可能被忽略當它們被忽略時將采用0作爲開始位置采用len(s)爲結束的位置 不管i是j都可能被忽略当它们被忽略时将采用0作为开始位置采用len(s)为结束的位置
```Go ```Go
fmt.Println(s[:5]) // "hello" fmt.Println(s[:5]) // "hello"
@@ -34,15 +34,15 @@ fmt.Println(s[7:]) // "world"
fmt.Println(s[:]) // "hello, world" fmt.Println(s[:]) // "hello, world"
``` ```
其中+操作符將兩個字符串鏈接構造一新字符串 其中+操作符将两个字符串链接构造一新字符串
```Go ```Go
fmt.Println("goodbye" + s[5:]) // "goodbye, world" fmt.Println("goodbye" + s[5:]) // "goodbye, world"
``` ```
字符串可以用==和<行比;比較通過逐個字節比較完成的,因此比較的結果是字符串自然編碼的順序。 字符串可以用==和<行比;比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺序。
字符串的值是不可字符串包含的字序列永遠不會被改然我也可以給一個字符串量分配一新字符串值可以像下面這樣將一個字符串追加到另一字符串 字符串的值是不可字符串包含的字序列永远不会被改然我也可以给一个字符串量分配一新字符串值可以像下面这样将一个字符串追加到另一字符串
```Go ```Go
s := "left foot" s := "left foot"
@@ -50,20 +50,20 @@ t := s
s += ", right foot" s += ", right foot"
``` ```
這併不會導致原始的字符串值被改但是量s將因爲+=句持有一新的字符串值但是t依然是包含原先的字符串值 这并不会导致原始的字符串值被改但是量s将因为+=句持有一新的字符串值但是t依然是包含原先的字符串值
```Go ```Go
fmt.Println(s) // "left foot, right foot" fmt.Println(s) // "left foot, right foot"
fmt.Println(t) // "left foot" fmt.Println(t) // "left foot"
``` ```
字符串是不可改的因此嚐試脩改字符串內部數據的操作也是被禁止的 字符串是不可改的因此尝试修改字符串内部数据的操作也是被禁止的
```Go ```Go
s[0] = 'L' // compile error: cannot assign to s[0] 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" %} {% include "./ch3-05-1.md" %}

View File

@@ -1,8 +1,8 @@
### 3.6.1. iota 常量生成器 ### 3.6.1. iota 常量生成器
常量明可以使用iota常量生成器初始化它用生成一以相似規則初始化的常量,但是不用每行都一遍初始化表式。在一const聲明語句中,在第一個聲明的常量所在的行iota將會被置0在每一有常量明的行加一。 常量明可以使用iota常量生成器初始化它用生成一以相似规则初始化的常量,但是不用每行都一遍初始化表式。在一const声明语句中,在第一个声明的常量所在的行iota将会被置0在每一有常量明的行加一。
下面是自time包的例子它首先定了一Weekday命名型,然後爲一週的每天定了一常量,從週日0始。在其它編程語言中,這種類型一般被稱爲枚舉類型。 下面是自time包的例子它首先定了一Weekday命名型,然后为一周的每天定了一常量,从周日0始。在其它编程语言中,这种类型一般被称为枚举类型。
```Go ```Go
type Weekday int type Weekday int
@@ -18,9 +18,9 @@ const (
) )
``` ```
週一將對應0週一爲1如此等等。 周一将对应0周一为1如此等等。
也可以在複雜的常量表式中使用iota下面是自net包的例子於給一個無符號整數的最低5bit的每bit指定一名字: 也可以在复杂的常量表式中使用iota下面是自net包的例子于给一个无符号整数的最低5bit的每bit指定一名字:
```Go ```Go
type Flags uint type Flags uint
@@ -34,7 +34,7 @@ const (
) )
``` ```
着iota的增,每常量對應表達式1 << iota連續的2的分别對應一個bit位置使用些常量可以用於測試置或清除對應的bit位的值 着iota的增,每常量对应表达式1 << iota连续的2的分别对应一个bit位置使用些常量可以用于测试置或清除对应的bit位的值
<u><i>gopl.io/ch3/netflag</i></u> <u><i>gopl.io/ch3/netflag</i></u>
```Go ```Go
@@ -54,7 +54,7 @@ unc main() {
} }
``` ```
下面是一個更複雜的例子,每常量都是1024的 下面是一个更复杂的例子,每常量都是1024的
```Go ```Go
const ( 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。

View File

@@ -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 ```Go
fmt.Println(YiB/ZiB) // "1024" fmt.Println(YiB/ZiB) // "1024"
``` ```
另一例子math.Pi無類型的浮點數常量,可以直接用任意需要浮點數或複數的地方: 另一例子math.Pi无类型的浮点数常量,可以直接用任意需要浮点数或复数的地方:
```Go ```Go
var x float32 = math.Pi var x float32 = math.Pi
@@ -16,7 +16,7 @@ var y float64 = math.Pi
var z complex128 = math.Pi var z complex128 = math.Pi
``` ```
如果math.Pi被確定爲特定比如float64麽結果精度可能不一,同時對於需要float32或complex128型值的地方則會強製需要一個明確的類型轉換 如果math.Pi被确定为特定比如float64么结果精度可能不一,同时对于需要float32或complex128型值的地方则会强制需要一个明确的类型转换
```Go ```Go
const Pi64 float64 = math.Pi const Pi64 float64 = math.Pi
@@ -26,9 +26,9 @@ var y float64 = Pi64
var z complex128 = complex128(Pi64) var z complex128 = complex128(Pi64)
``` ```
對於常量面值,不同的法可能會對應不同的型。例如0、0.0、0i和'\u0000'然有着相同的常量值,但是它分别對應無類型的整數、無類型的浮點數、無類型的複數和無類型的字符等不同的常量型。同true和false也是無類型的布爾類型,字符串面值常量是無類型的字符串型。 对于常量面值,不同的法可能会对应不同的型。例如0、0.0、0i和'\u0000'然有着相同的常量值,但是它分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量型。同true和false也是无类型的布尔类型,字符串面值常量是无类型的字符串型。
前面説過除法算符/會根據操作數的類型生成對應類型的果。因此,不同法的常量除法表式可能對應不同的果: 前面说过除法算符/会根据操作数的类型生成对应类型的果。因此,不同法的常量除法表式可能对应不同的果:
```Go ```Go
var f float64 = 212 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 fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float
``` ```
有常量可以是無類型的。當一個無類型的常量被賦值給一個變量的候,就像上面的第一行句,或者是像其餘三個語句中右邊表達式中含有明確類型的值,無類型的常量將會被隱式轉換爲對應的類型,如果轉換合法的 有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的候,就像上面的第一行句,或者是像其余三个语句中右边表达式中含有明确类型的值,无类型的常量将会被隐式转换为对应的类型,如果转换合法的
```Go ```Go
var f float64 = 3 + 0i // untyped complex -> float64 var f float64 = 3 + 0i // untyped complex -> float64
@@ -46,7 +46,7 @@ f = 1e123 // untyped floating-point -> float64
f = 'a' // untyped rune -> float64 f = 'a' // untyped rune -> float64
``` ```
上面的句相當於: 上面的句相当于:
```Go ```Go
var f float64 = float64(3 + 0i) var f float64 = float64(3 + 0i)
@@ -55,7 +55,7 @@ f = float64(1e123)
f = float64('a') f = float64('a')
``` ```
無論是隱式或顯式轉換,將一種類型轉換爲另一種類型都要求目可以表示原始值。對於浮點數和複數,可能有舍入理: 无论是隐式或显式转换,将一种类型转换为另一种类型都要求目可以表示原始值。对于浮点数和复数,可能有舍入理:
```Go ```Go
const ( const (
@@ -69,7 +69,7 @@ const (
) )
``` ```
對於一個沒有顯式類型的變量聲明語法(包括短變量聲明語法),無類型的常量會被隱式轉爲默認的變量類型,就像下面的例子: 对于一个没有显式类型的变量声明语法(包括短变量声明语法),无类型的常量会被隐式转为默认的变量类型,就像下面的例子:
```Go ```Go
i := 0 // untyped integer; implicit int(0) 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) c := 0i // untyped complex; implicit complex128(0i)
``` ```
意默認類型是規則的:無類型的整常量默認轉換爲int對應不確定的存大小,但是浮點數和複數常量則默認轉換爲float64和complex128。Go言本身併沒有不確定內存大小的浮點數和複數類型,而且如果不知道浮點數類型的話將很難寫出正確的數值算法。 意默认类型是规则的:无类型的整常量默认转换为int对应不确定的存大小,但是浮点数和复数常量则默认转换为float64和complex128。Go言本身并没有不确定内存大小的浮点数和复数类型,而且如果不知道浮点数类型的话将很难写出正确的数值算法。
如果要給變量一不同的型,我們必須顯式地將無類型的常量轉化爲所需的型,或給聲明的量指定明確的類型,像下面例子這樣 如果要给变量一不同的型,我们必须显式地将无类型的常量转化为所需的型,或给声明的量指定明确的类型,像下面例子这样
```Go ```Go
var i = int8(0) var i = int8(0)
var i int8 = 0 var i int8 = 0
``` ```
當嚐試將這些無類型的常量轉爲一個接口值時(見第7章些默認類型將顯得尤重要,因要靠它們明確接口對應的動態類型。 当尝试将这些无类型的常量转为一个接口值时(见第7章些默认类型将显得尤重要,因要靠它们明确接口对应的动态类型。
```Go ```Go
fmt.Printf("%T\n", 0) // "int" fmt.Printf("%T\n", 0) // "int"
@@ -96,7 +96,7 @@ fmt.Printf("%T\n", 0i) // "complex128"
fmt.Printf("%T\n", '\000') // "int32" (rune) fmt.Printf("%T\n", '\000') // "int32" (rune)
``` ```
在我們已經講述了Go言中全部的基礎數據類型。下一步演示如何用基礎數據類型組合成數組或結構體等複雜數據類型,然後構建用於解決實際編程問題的數據結構,這將是第四章的討論主題 在我们已经讲述了Go言中全部的基础数据类型。下一步演示如何用基础数据类型组合成数组或结构体等复杂数据类型,然后构建用于解决实际编程问题的数据结构,这将是第四章的讨论主题

View File

@@ -1,14 +1,14 @@
## 3.6. 常量 ## 3.6. 常量
常量表式的值在編譯期計算,而不是在行期。每常量的潛在類型都是基礎類boolean、string或字。 常量表式的值在编译期计算,而不是在行期。每常量的潜在类型都是基础类boolean、string或字。
常量的聲明語句定了常量的名字,和量的聲明語法類似,常量的值不可改,這樣可以防止在行期被意外或意的改。例如,常量比量更合用於表達像π之類的數學常數,因爲它們的值不會發生變化: 常量的声明语句定了常量的名字,和量的声明语法类似,常量的值不可改,这样可以防止在行期被意外或意的改。例如,常量比量更合用于表达像π之类的数学常数,因为它们的值不会发生变化:
```Go ```Go
const pi = 3.14159 // approximately; math.Pi is a better approximation const pi = 3.14159 // approximately; math.Pi is a better approximation
``` ```
變量聲明一,可以批量明多常量;這比較適合聲明一組相關的常量: 变量声明一,可以批量明多常量;这比较适合声明一组相关的常量:
```Go ```Go
const ( 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 ```Go
const IPv4Len = 4 const IPv4Len = 4
@@ -33,7 +33,7 @@ func parseIPv4(s string) IP {
} }
``` ```
常量的明也可以包含一個類型和一值,但是如果沒有顯式指明型,那麽將從右邊的表式推斷類型。在下面的代time.Duration是一命名型,底層類型是int64time.Minute是對應類型的常量。下面明的兩個常量都是time.Duration型,可以通%T參數打印型信息: 常量的明也可以包含一个类型和一值,但是如果没有显式指明型,那么将从右边的表式推断类型。在下面的代time.Duration是一命名型,底层类型是int64time.Minute是对应类型的常量。下面明的两个常量都是time.Duration型,可以通%T参数打印型信息:
```Go ```Go
const noDelay time.Duration = 0 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" fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s"
``` ```
如果是批量明的常量,除了第一外其它的常量右的初始化表式都可以省略,如果省略初始化表達式則表示使用前面常量的初始化表達式寫法,對應的常量型也一的。例如: 如果是批量明的常量,除了第一外其它的常量右的初始化表式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量型也一的。例如:
```Go ```Go
const ( const (
@@ -56,7 +56,7 @@ const (
fmt.Println(a, b, c, d) // "1 1 2 2" fmt.Println(a, b, c, d) // "1 1 2 2"
``` ```
如果隻是簡單地複製右邊的常量表式,其實併沒有太用的值。但是它可以帶來其它的特性那就是iota常量生成器法。 如果只是简单地复制右边的常量表式,其实并没有太用的值。但是它可以带来其它的特性那就是iota常量生成器法。
{% include "./ch3-06-1.md" %} {% include "./ch3-06-1.md" %}

View File

@@ -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章介接口型。

View File

@@ -1,8 +1,8 @@
## 4.1. 數組 ## 4.1. 数组
數組是一由固定度的特定型元素成的序列,一個數組可以由零或多元素成。因爲數組的長度是固定的因此在Go言中很少直接使用數組。和數組對應的類型是Slice切片它是可以增和收縮動態序列slice功能也更但是要理解slice工作原理的需要先理解數組 数组是一由固定度的特定型元素成的序列,一个数组可以由零或多元素成。因为数组的长度是固定的因此在Go言中很少直接使用数组。和数组对应的类型是Slice切片它是可以增和收缩动态序列slice功能也更但是要理解slice工作原理的需要先理解数组
數組的每元素可以通索引下標來訪問,索引下的范圍是從0開始到數組長度減1的位置。置的len函數將返迴數組中元素的個數 数组的每元素可以通索引下标来访问,索引下的范围是从0开始到数组长度减1的位置。置的len函数将返回数组中元素的个数
```Go ```Go
var a [3]int // array of 3 integers var a [3]int // array of 3 integers
@@ -20,7 +20,7 @@ for _, v := range a {
} }
``` ```
認情況下,數組的每元素都被初始化元素類型對應的零值,對於數字類型來説就是0。我也可以使用數組字面值法用一組值來初始化數組 认情况下,数组的每元素都被初始化元素类型对应的零值,对于数字类型来说就是0。我也可以使用数组字面值法用一组值来初始化数组
```Go ```Go
var q [3]int = [3]int{1, 2, 3} 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" fmt.Println(r[2]) // "0"
``` ```
數組字面值中,如果在數組的長度位置出的是“...”省略號,則表示數組的長度是根初始化值的個數來計算。因此上面q數組的定可以簡化爲 数组字面值中,如果在数组的长度位置出的是“...”省略号,则表示数组的长度是根初始化值的个数来计算。因此上面q数组的定可以简化为
```Go ```Go
q := [...]int{1, 2, 3} q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int" fmt.Printf("%T\n", q) // "[3]int"
``` ```
數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必是常量表式,因爲數組的長度需要在編譯階段確定。 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必是常量表式,因为数组的长度需要在编译阶段确定。
```Go ```Go
q := [3]int{1, 2, 3} q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
``` ```
們將會發現,數組、slice、map和結構體字面值的法都很相似。上面的形式是直接提供序初始化值序列,但是也可以指定一索引和對應值列表的方式初始化,就像下面這樣 们将会发现,数组、slice、map和结构体字面值的法都很相似。上面的形式是直接提供序初始化值序列,但是也可以指定一索引和对应值列表的方式初始化,就像下面这样
```Go ```Go
type Currency int type Currency int
const ( const (
USD Currency = iota // 美元 USD Currency = iota // 美元
EUR // EUR //
GBP // 英 GBP // 英
RMB // 人民 RMB // 人民
) )
symbol := [...]string{USD: "$", 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 ¥" fmt.Println(RMB, symbol[RMB]) // "3 ¥"
``` ```
這種形式的數組字面值形式中,初始化索引的序是無關緊要的,而且用到的索引可以省略,和前面提到的規則一樣,未指定初始值的元素用零值初始化。例如, 这种形式的数组字面值形式中,初始化索引的序是无关紧要的,而且用到的索引可以省略,和前面提到的规则一样,未指定初始值的元素用零值初始化。例如,
```Go ```Go
r := [...]int{99: -1} r := [...]int{99: -1}
``` ```
了一含有100元素的數組r後一個元素被初始化-1其它元素都是用0初始化。 了一含有100元素的数组r后一个元素被初始化-1其它元素都是用0初始化。
如果一個數組的元素型是可以相互比的,那麽數組類型也是可以相互比的,這時候我可以直接通==比較運算符來比較兩個數組,隻有當兩個數組的所有元素都是相等的時候數組才是相等的。不相等比較運算符!=遵循同樣的規則 如果一个数组的元素型是可以相互比的,那么数组类型也是可以相互比的,这时候我可以直接通==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则
```Go ```Go
a := [2]int{1, 2} 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 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”两个信息的摘要:
<u><i>gopl.io/ch4/sha256</i></u> <u><i>gopl.io/ch4/sha256</i></u>
```Go ```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 ```Go
func zero(ptr *[32]byte) { 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 ```Go
func zero(ptr *[32]byte) { 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哈希算法。

View File

@@ -1,6 +1,6 @@
### 4.2.1. append函 ### 4.2.1. append函
置的append函數用於向slice追加元素 置的append函数用于向slice追加元素
```Go ```Go
var runes []rune var runes []rune
@@ -10,9 +10,9 @@ for _, r := range "Hello, 世界" {
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']" 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
<u><i>gopl.io/ch4/append</i></u> <u><i>gopl.io/ch4/append</i></u>
```Go ```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 ```Go
func main() { func main() {
@@ -56,7 +56,7 @@ func main() {
} }
``` ```
每一次容量的化都會導致重新分配存和copy操作 每一次容量的化都会导致重新分配存和copy操作
``` ```
0 cap=1 [0] 0 cap=1 [0]
@@ -71,21 +71,21 @@ func main() {
9 cap=16 [0 1 2 3 4 5 6 7 8 9] 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) ![](../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) ![](../images/ch4-03.png)
置的append函可能使用比appendInt更複雜的內存擴展策略。因此,通常我們併不知道append調用是否致了存的重新分配,因此我也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。同,我不能確認在原先的slice上的操作是否會影響到新的slice。因此通常是append返迴的結果直接賦值給輸入的slice量: 置的append函可能使用比appendInt更复杂的内存扩展策略。因此,通常我们并不知道append用是否致了存的重新分配,因此我也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。同,我不能确认在原先的slice上的操作是否会影响到新的slice。因此通常是append返回的结果直接赋值给输入的slice量:
```Go ```Go
runes = append(runes, r) runes = append(runes, r)
``` ```
更新slice量不僅對調用append函是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正地使用slice需要記住盡管底層數組的元素是間接訪問但是slice對應結構體本身的指針、長度和容量部分是直接訪問的。要更新些信息需要像上面例子那樣一個顯式的值操作。從這個角度看slice不是一個純粹的引用型,它實際上是一個類似下面結構體的聚合型: 更新slice量不仅对调用append函是必要的,实际上对应任何可能导致长度、容量或底层数组变化的操作都是必要的。要正地使用slice需要记住尽管底层数组的元素是间接访问但是slice对应结构体本身的指针、长度和容量部分是直接访问的。要更新些信息需要像上面例子那样一个显式的值操作。从这个角度看slice不是一个纯粹的引用型,它实际上是一个类似下面结构体的聚合型:
```Go ```Go
type IntSlice struct { type IntSlice struct {
@@ -94,7 +94,7 @@ type IntSlice struct {
} }
``` ```
的appendInt函每次能向slice追加一元素,但是置的append函數則可以追加多元素,甚至追加一slice。 的appendInt函每次能向slice追加一元素,但是置的append函数则可以追加多元素,甚至追加一slice。
```Go ```Go
var x []int 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]" 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 ```Go
func appendInt(x []int, y ...int) []int { func appendInt(x []int, y ...int) []int {
@@ -117,4 +117,4 @@ func appendInt(x []int, y ...int) []int {
} }
``` ```
了避免重,和前面相同的代碼併沒有顯示。 了避免重,和前面相同的代码并没有显示。

View File

@@ -1,6 +1,6 @@
### 4.2.2. Slice存技巧 ### 4.2.2. Slice存技巧
讓我們看看更多的例子,比如镟轉slice、反slice或在slice原有存空間脩改元素。定一字符串列表下面的nonempty函數將在原有slice存空之上返不包含空字符串的列表: 让我们看看更多的例子,比如旋转slice、反slice或在slice原有存空间修改元素。定一字符串列表下面的nonempty函数将在原有slice存空之上返不包含空字符串的列表:
<u><i>gopl.io/ch4/nonempty</i></u> <u><i>gopl.io/ch4/nonempty</i></u>
```Go ```Go
@@ -23,7 +23,7 @@ func nonempty(strings []string) []string {
} }
``` ```
微妙的地方是,入的slice和出的slice共享一個底層數組。這可以避免分配另一個數組,不過原來的數據將可能被覆,正如下面兩個打印句看到的那 微妙的地方是,入的slice和出的slice共享一个底层数组。这可以避免分配另一个数组,不过原来的数据将可能被覆,正如下面两个打印句看到的那
```Go ```Go
data := []string{"one", "", "three"} data := []string{"one", "", "three"}
@@ -31,9 +31,9 @@ fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
fmt.Printf("%q\n", data) // `["one" "three" "three"]` fmt.Printf("%q\n", data) // `["one" "three" "three"]`
``` ```
因此我通常會這樣使用nonempty函`data = nonempty(data)` 因此我通常会这样使用nonempty函`data = nonempty(data)`
nonempty函也可以使用append函數實現 nonempty函也可以使用append函数实现
```Go ```Go
func nonempty2(strings []string) []string { 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 ```Go
stack = append(stack, v) // push v stack = append(stack, v) // push v
``` ```
stack的部位置對應slice的最後一個元素: stack的部位置对应slice的最后一个元素:
```Go ```Go
top := stack[len(stack)-1] // top of stack top := stack[len(stack)-1] // top of stack
``` ```
過收縮stack可以彈出棧頂的元素 过收缩stack可以弹出栈顶的元素
```Go ```Go
stack = stack[:len(stack)-1] // pop stack = stack[:len(stack)-1] // pop
``` ```
除slice中的某元素保存原有的元素序,可以通過內置的copy函數將後面的子slice向前依次移一位完成: 除slice中的某元素保存原有的元素序,可以通过内置的copy函数将后面的子slice向前依次移一位完成:
```Go ```Go
func remove(slice []int, i int) []int { func remove(slice []int, i int) []int {
@@ -81,7 +81,7 @@ func main() {
} }
``` ```
如果除元素不用保持原來順序的,我可以簡單的用最後一個元素覆蓋被刪除的元素: 如果除元素不用保持原来顺序的,我可以简单的用最后一个元素覆盖被删除的元素:
```Go ```Go
func remove(slice []int, i int) []int { 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。是否可以不用分配外的存?

View File

@@ -1,18 +1,18 @@
## 4.2. Slice ## 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 ```Go
months := [...]string{1: "January", /* ... */, 12: "December"} 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) ![](../images/ch4-01.png)
@@ -23,7 +23,7 @@ fmt.Println(Q2) // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"] fmt.Println(summer) // ["June" "July" "August"]
``` ```
兩個slice都包含了六月份下面的代是一包含相同月份的測試(性能低): 两个slice都包含了六月份下面的代是一包含相同月份的测试(性能低):
```Go ```Go
for _, s := range summer { 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 ```Go
fmt.Println(summer[:20]) // panic: out of range 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]" 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。
<u><i>gopl.io/ch4/rev</i></u> <u><i>gopl.io/ch4/rev</i></u>
```Go ```Go
@@ -58,7 +58,7 @@ func reverse(s []int) {
} }
``` ```
里我們反轉數組的應用: 里我们反转数组的应用:
```Go ```Go
a := [...]int{0, 1, 2, 3, 4, 5} a := [...]int{0, 1, 2, 3, 4, 5}
@@ -66,7 +66,7 @@ reverse(a[:])
fmt.Println(a) // "[5 4 3 2 1 0]" fmt.Println(a) // "[5 4 3 2 1 0]"
``` ```
種將slice元素循向左镟轉n個元素的方法是三次調用reverse反轉函數,第一次是反轉開頭的n元素,然是反剩下的元素,最是反轉整個slice的元素。如果是向右循環镟轉,則將第三個函數調用移到第一個調用位置就可以了。) 种将slice元素循向左旋转n个元素的方法是三次用reverse反转函数,第一次是反转开头的n元素,然是反剩下的元素,最是反转整个slice的元素。如果是向右循环旋转,则将第三个函数调用移到第一个调用位置就可以了。)
```Go ```Go
s := []int{0, 1, 2, 3, 4, 5} s := []int{0, 1, 2, 3, 4, 5}
@@ -77,9 +77,9 @@ reverse(s)
fmt.Println(s) // "[2 3 4 5 0 1]" 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 ```Go
func equal(x, y []string) bool { 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 ```Go
if summer == nil { /* ... */ } 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 ```Go
var s []int // len(s) == 0, s == nil 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 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 ```Go
make([]T, len) make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[: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" %} {% include "./ch4-02-1.md" %}

Some files were not shown because too many files have changed in this diff Show More