Merge branch 'master' of https://github.com/golang-china/gopl-zh
3
Makefile
@ -37,8 +37,11 @@ cover:
|
||||
composite cover_patch.png cover_bgd.png cover.jpg
|
||||
convert -resize 1800x2360! cover.jpg cover.jpg
|
||||
convert -resize 200x262! cover.jpg cover_small.jpg
|
||||
convert -resize 400x524! cover.jpg cover_middle.jpg
|
||||
convert -quality 75% cover.jpg cover.jpg
|
||||
convert -quality 75% cover_small.jpg cover_small.jpg
|
||||
convert -quality 75% cover_middle.jpg cover_middle.jpg
|
||||
convert -strip cover.jpg cover.jpg
|
||||
convert -strip cover_small.jpg cover_small.jpg
|
||||
convert -strip cover_middle.jpg cover_middle.jpg
|
||||
|
||||
|
@ -9,7 +9,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
- 在線預覽:http://golang-china.github.io/gopl-zh
|
||||
- 原版官網:http://gopl.io
|
||||
|
||||
[![](cover_small.jpg)](https://github.com/golang-china/gopl-zh)
|
||||
[![](cover_middle.jpg)](https://github.com/golang-china/gopl-zh)
|
||||
|
||||
|
||||
### 從源文件構建
|
||||
|
@ -125,11 +125,11 @@
|
||||
* [9.8.3. GOMAXPROCS](ch9/ch9-08-3.md)
|
||||
* [9.8.4. Goroutine沒有ID號](ch9/ch9-08-4.md)
|
||||
* [第十章 包和工具](ch10/ch10.md)
|
||||
* [10.1. 簡介](ch10/ch10-01.md)
|
||||
* [10.1. 包簡介](ch10/ch10-01.md)
|
||||
* [10.2. 導入路徑](ch10/ch10-02.md)
|
||||
* [10.3. 包聲明](ch10/ch10-03.md)
|
||||
* [10.4. 導入聲明](ch10/ch10-04.md)
|
||||
* [10.5. 匿名導入](ch10/ch10-05.md)
|
||||
* [10.5. 包的匿名導入](ch10/ch10-05.md)
|
||||
* [10.6. 包和命名](ch10/ch10-06.md)
|
||||
* [10.7. 工具](ch10/ch10-07.md)
|
||||
* [10.7.1. 工作區結構](ch10/ch10-07-1.md)
|
||||
@ -168,3 +168,6 @@
|
||||
* [13.4. 通過cgo調用C代碼](ch13/ch13-04.md)
|
||||
* [13.5. 幾點忠告](ch13/ch13-05.md)
|
||||
* [附録](appendix/appendix.md)
|
||||
* [附録A:原文勘誤](appendix/appendix-a-errata.md)
|
||||
* [附録B:作者譯者](appendix/appendix-b-author.md)
|
||||
* [附録C:譯文授權](appendix/appendix-c-cpoyright.md)
|
||||
|
@ -92,11 +92,11 @@
|
||||
* [示例: 併發的非阻塞緩存](ch9/ch9-07.md)
|
||||
* [Goroutines和線程](ch9/ch9-08.md)
|
||||
* [包和工具](ch10/ch10.md)
|
||||
* [簡介](ch10/ch10-01.md)
|
||||
* [包簡介](ch10/ch10-01.md)
|
||||
* [導入路徑](ch10/ch10-02.md)
|
||||
* [包聲明](ch10/ch10-03.md)
|
||||
* [導入聲明](ch10/ch10-04.md)
|
||||
* [匿名導入](ch10/ch10-05.md)
|
||||
* [包的匿名導入](ch10/ch10-05.md)
|
||||
* [包和命名](ch10/ch10-06.md)
|
||||
* [工具](ch10/ch10-07.md)
|
||||
* [測試](ch11/ch11.md)
|
||||
@ -123,3 +123,6 @@
|
||||
* [通過cgo調用C代碼](ch13/ch13-04.md)
|
||||
* [幾點忠告](ch13/ch13-05.md)
|
||||
* [附録](appendix/appendix.md)
|
||||
* [附録A:原文勘誤](appendix/appendix-a-errata.md)
|
||||
* [附録B:作者譯者](appendix/appendix-b-author.md)
|
||||
* [附録C:譯文授權](appendix/appendix-c-cpoyright.md)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 原文勘誤
|
||||
## 附録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.)
|
||||
|
||||
@ -23,10 +23,10 @@
|
||||
**p.68:** the table of UTF-8 encodings is missing a bit from each first byte. The corrected table is shown below. (Thanks to Akshay Kumar, 2015-11-02. Corrected in the second printing.)
|
||||
|
||||
```
|
||||
0xxxxxxx runes 0?127 (ASCII)
|
||||
110xxxxx 10xxxxxx 128?2047 (values <128 unused)
|
||||
1110xxxx 10xxxxxx 10xxxxxx 2048?65535 (values <2048 unused)
|
||||
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536?0x10ffff (other values unused)
|
||||
0xxxxxxx runes 0−127 (ASCII)
|
||||
110xxxxx 10xxxxxx 128−2047 (values <128 unused)
|
||||
1110xxxx 10xxxxxx 10xxxxxx 2048−65535 (values <2048 unused)
|
||||
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536−0x10ffff (other values unused)
|
||||
```
|
||||
|
||||
**p.73, ¶1:** For "a exercise", read "an exercise". (Thanks to vrajmohan, 2015-12-28.)
|
||||
@ -44,8 +44,13 @@ appears on the right-hand side of a variable declaration with an explicit type,
|
||||
|
||||
**p.166, ¶2:** for "way", read "a way".
|
||||
|
||||
**p.222. Exercise 8.1:** The port numbers for `London` and `Tokyo` should be swapped in the final command to match the earlier commands. (Thanks to Kiyoshi Kamishima, 2016-01-08.)
|
||||
|
||||
**p.288, code display following ¶4:** In the import declaration, for `"database/mysql"`, read `"database/sql"`. (Thanks to Jose Colon Rodriguez, 2016-01-09.)
|
||||
|
||||
**p.200, TestEval function:** the format string in the final call to t.Errorf should format test.env with %v, not %s. (Thanks to Mitsuteru Sawa, 2015-12-07.)
|
||||
|
||||
**p.347, Exercise 12.8:** for "like json.Marshal", read "like json.Unmarshal". (Thanks to @chai2010, 2016-01-01.)
|
||||
|
||||
**p.362:** the `gopl.io/ch13/bzip` program does not comply with the [proposed rules for passing pointers between Go and C code](https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) because the C function `bz2compress` temporarily stores a Go pointer (in) into the Go heap (the `bz_stream` variable). The `bz_stream` variable should be allocated, and explicitly freed after the call to `BZ2_bzCompressEnd`, by C functions. (Thanks to Joe Tsai, 2015-11-18.)
|
||||
**p.362:** the `gopl.io/ch13/bzip` program does not comply with the [proposed rules for passing pointers between Go and C code](https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) because the C function `bz2compress` temporarily stores a Go pointer (in) into the Go heap (the `bz_stream` variable). The `bz_stream` variable should be allocated, and explicitly freed after the call to `BZ2_bzCompressEnd`, by C functions. (Thanks to Joe Tsai, 2015-11-18.)
|
||||
|
17
appendix/appendix-b-author.md
Normal file
@ -0,0 +1,17 @@
|
||||
## 附録B:作者/譯者
|
||||
|
||||
### 英文作者
|
||||
|
||||
- **[Alan A. A. Donovan](https://github.com/adonovan)** is a member of [Google’s Go](https://golang.org/) team in New York. He holds computer science degrees from Cambridge and MIT and has been programming in industry since 1996. Since 2005, he has worked at Google on infrastructure projects and was the co-designer of its proprietary build system, [Blaze](http://bazel.io/). He has built many libraries and tools for static analysis of Go programs, including [oracle](https://godoc.org/golang.org/x/tools/oracle), [`godoc -analysis`](https://godoc.org/golang.org/x/tools/cmd/godoc), eg, and [gorename](https://godoc.org/golang.org/x/tools/cmd/gorename).
|
||||
- **[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)** is a professor in the Computer Science Department at Princeton University. He was a member of technical staff in the Computing Science Research Center at [Bell Labs](http://www.cs.bell-labs.com/) from 1969 until 2000, where he worked on languages and tools for [Unix](http://doc.cat-v.org/unix/). He is the co-author of several books, including [The C Programming Language, Second Edition (Prentice Hall, 1988)](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html), and [The Practice of Programming (Addison-Wesley, 1999)](https://en.wikipedia.org/wiki/The_Practice_of_Programming).
|
||||
|
||||
-------
|
||||
|
||||
### 中文譯者
|
||||
|
||||
中文譯者 | 章節
|
||||
-------------------------------------- | -------------------------
|
||||
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
|
||||
`CrazySssst` | 第5章
|
||||
`foreversmart <njutree@gmail.com>` | 第7章
|
||||
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
|
6
appendix/appendix-c-cpoyright.md
Normal file
@ -0,0 +1,6 @@
|
||||
## 附録C:譯文授權
|
||||
|
||||
除特别註明外, 本站內容均采用[知識共享-署名(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>
|
||||
|
@ -1,26 +1,6 @@
|
||||
## 附録:作者/譯者
|
||||
# 附録
|
||||
|
||||
### 英文作者
|
||||
英文原版併沒有包含附録部分,隻有一個索引部分。中文版增加附録部分主要用於收録一些和本書相關的內容,比如英文原版的勘誤(有些讀者可能會對照中文和英文原閲讀)、英文作者和中文譯者、譯文授權等內容。以後還可能會考慮增加一些習題解答相關的內容。
|
||||
|
||||
- **[Alan A. A. Donovan](https://github.com/adonovan)** is a member of [Google’s Go](https://golang.org/) team in New York. He holds computer science degrees from Cambridge and MIT and has been programming in industry since 1996. Since 2005, he has worked at Google on infrastructure projects and was the co-designer of its proprietary build system, [Blaze](http://bazel.io/). He has built many libraries and tools for static analysis of Go programs, including [oracle](https://godoc.org/golang.org/x/tools/oracle), [`godoc -analysis`](https://godoc.org/golang.org/x/tools/cmd/godoc), eg, and [gorename](https://godoc.org/golang.org/x/tools/cmd/gorename).
|
||||
- **[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)** is a professor in the Computer Science Department at Princeton University. He was a member of technical staff in the Computing Science Research Center at [Bell Labs](http://www.cs.bell-labs.com/) from 1969 until 2000, where he worked on languages and tools for [Unix](http://doc.cat-v.org/unix/). He is the co-author of several books, including [The C Programming Language, Second Edition (Prentice Hall, 1988)](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html), and [The Practice of Programming (Addison-Wesley, 1999)](https://en.wikipedia.org/wiki/The_Practice_of_Programming).
|
||||
|
||||
-------
|
||||
|
||||
### 中文譯者
|
||||
|
||||
中文譯者 | 章節
|
||||
-------------------------------------- | -------------------------
|
||||
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
|
||||
`CrazySssst` | 第5章
|
||||
`foreversmart <njutree@gmail.com>` | 第7章
|
||||
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
|
||||
|
||||
-------
|
||||
|
||||
## 譯文授權
|
||||
|
||||
除特别註明外, 本站內容均采用[知識共享-署名(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>
|
||||
需要特别説明的是,中文版附録併沒有包含英文原版的索引信息。因爲英文原版的索引信息主要是記録每個索引所在的英文頁面位置,而中文版是以GitBook方式組織的html網頁形式,將英文頁面位置轉爲章節位置可能會更合理,不過這個會涉及到繁瑣的手工操作。如果大家有更好的建議,請告知我們。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 致謝
|
||||
|
||||
[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關於類型設置的建議。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 1.1. Hello, World
|
||||
|
||||
我們以1978年出版的C語言聖經[《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)中經典的“hello world”案例來開始吧(譯註:本書作者之一Brian W. Kernighan也是C語言聖經一書的作者)。C語言對Go語言的設計産生了很多影響。用這個例子,我們來講解一些Go語言的覈心特性:
|
||||
我們以1978年出版的C語言聖經[《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)中經典的“hello world”案例來開始吧(譯註:本書作者之一Brian W. Kernighan也是C語言聖經一書的作者)。C語言對Go語言的設計産生了很多影響。用這個例子,我們來講解一些Go語言的核心特性:
|
||||
|
||||
```go
|
||||
gopl.io/ch1/helloworld
|
||||
|
@ -75,7 +75,7 @@ bla kIndex)
|
||||
|
||||
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)這個內置函數,將結果appen到anim中的幀列表末尾,併會設置一個默認的80ms的延遲值。最終循環結束,所有的延遲值也被編碼進了GIF圖片中,併將結果寫入到輸出流。out這個變量是io.Writer類型,這個類型讓我們可以可以讓我們把輸出結果寫到很多目標,很快我們就可以看到了。
|
||||
lissajous函數內部有兩層嵌套的for循環。外層循環會循環64次,每一次都會生成一個單獨的動畵幀。它生成了一個包含兩種顔色的201&201大小的圖片,白色和黑色。所有像素點都會被默認設置爲其零值(也就是palette里的第0個值),這里我們設置的是白色。每次外層循環都會生成一張新圖片,併將一些像素設置爲黑色。其結果會append到之前結果之後。這里我們用到了append(參考4.2.1)這個內置函數,將結果appen到anim中的幀列表末尾,併會設置一個默認的80ms的延遲值。最終循環結束,所有的延遲值也被編碼進了GIF圖片中,併將結果寫入到輸出流。out這個變量是io.Writer類型,這個類型讓我們可以可以讓我們把輸出結果寫到很多目標,很快我們就可以看到了。
|
||||
|
||||
內存循環設置了兩個偏振。x軸偏振使用的是一個sin函數。y軸偏振也是一個正絃波,但是其其相對x軸的偏振是一個0-3的隨機值,併且初始偏振值是一個零值,併隨着動畵的每一幀逐漸增加。循環會一直跑到x軸完成五次完整的循環。每一步它都會調用SetColorIndex來爲(x, y)點來染黑色。
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
## 10.1. 簡介
|
||||
## 10.1. 包簡介
|
||||
|
||||
任何包繫統設計的目的都是爲了使大型程序的設計和維護, 通過將一組相關的特性放進一個獨立的單元以便於理解和更新, 同時保持和程序中其他單元的相對獨立性. 這種模塊化的特性允許每個包可以被其他的不同項目共享和重用, 在項目內甚至全球統一的分發.
|
||||
任何包繫統設計的目的都是爲了簡化大型程序的設計和維護工作,通過將一組相關的特性放進一個獨立的單元以便於理解和更新,在每個單元更新的同時保持和程序中其它單元的相對獨立性。這種模塊化的特性允許每個包可以被其它的不同項目共享和重用,在項目范圍內、甚至全球范圍統一的分發和複用。
|
||||
|
||||
每個包定義了一個不同的名稱空間用於它內部的每個標識符. 每個名稱關聯到一個特定的包, 我們最好給類型, 函數等選擇簡短清晰的名字, 這樣可以避免在我們使用它們的時候減少和其他部分名字的衝突.
|
||||
|
||||
包還通過控製包內名字的可見性和是否導出來實現封裝特性. 通過限製包成員的可見性併隱藏包API的具體實現, 將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現. 通過限製包內變量的可見性, 還可以控製用戶通過某些特定函數來訪問和更新內部變量, 這樣可以保證內部變量的一致性和併發時的互斥約束.
|
||||
|
||||
當我們脩改了一個文件, 我們必鬚重新編譯改文件對應的包和所以依賴該包的其他包.卽使是從頭構建, Go的編譯器也明顯快於其他編譯語言. Go的編譯速度主要得益於三個特性. 第一點, 所有導入的包必鬚在每個文件的開頭顯式聲明, 這樣的話編譯器就沒有必要讀取分析整個文件來判斷包的依賴關繫. 第二點, 包的依賴關繫形成一個有向無環圖, 因爲沒有循環依賴, 每個包可以被獨立編譯, 很可能是併發編譯. 第三點, 編譯後包的目標文件不僅僅記録包本身的導出信息, 同時還記録了它的依賴關繫. 因此, 在編譯一個包的時候, 編譯器隻需要讀取每個直接導入包的目標文件, 而不是要遍歷所有依賴的的文件(譯註: 很多可能是間接依賴).
|
||||
每個包一般都定義了一個不同的名字空間用於它內部的每個標識符的訪問。每個名字空間關聯到一個特定的包,讓我們給類型、函數等選擇簡短明了的名字,這樣可以避免在我們使用它們的時候減少和其它部分名字的衝突。
|
||||
|
||||
每個包還通過控製包內名字的可見性和是否導出來實現封裝特性。通過限製包成員的可見性併隱藏包API的具體實現,將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現。通過限製包內變量的可見性,還可以強製用戶通過某些特定函數來訪問和更新內部變量,這樣可以保證內部變量的一致性和併發時的互斥約束。
|
||||
|
||||
當我們脩改了一個源文件,我們必鬚重新編譯該源文件對應的包和所有依賴該包的其他包。卽使是從頭構建,Go語言編譯器的編譯速度也明顯快於其它編譯語言。Go語言的閃電般的編譯速度主要得益於三個語言特性。第一點,所有導入的包必鬚在每個文件的開頭顯式聲明,這樣的話編譯器就沒有必要讀取和分析整個源文件來判斷包的依賴關繫。第二點,禁止包的環狀依賴,因爲沒有循環依賴,包的依賴關繫形成一個有向無環圖,每個包可以被獨立編譯,而且很可能是被併發編譯。第三點,編譯後包的目標文件不僅僅記録包本身的導出信息,目標文件同時還記録了包的依賴關繫。因此,在編譯一個包的時候,編譯器隻需要讀取每個直接導入包的目標文件,而不需要遍歷所有依賴的的文件(譯註:很多都是重複的間接依賴)。
|
||||
|
@ -1,7 +1,6 @@
|
||||
## 10.2. 導入路徑
|
||||
|
||||
每個包是由一個全局唯一的字符串所標識的導入路徑定位.
|
||||
出現在導入聲明中的導入路徑也是字符串.
|
||||
每個包是由一個全局唯一的字符串所標識的導入路徑定位。出現在import語句中的導入路徑也是字符串。
|
||||
|
||||
```Go
|
||||
import (
|
||||
@ -15,6 +14,6 @@ import (
|
||||
)
|
||||
```
|
||||
|
||||
就像我們在2.6.1節提到過的, Go語言的規范併沒有指明包導入路徑字符串的具體含義, 具體含義是由構建工具來解釋的. 在本章, 我們將深入討論Go工具箱的功能, 包括大家經常使用的構建測試等功能. 當然, 也有第三方擴展的工具箱存在. 例如, Google公司內部的Go碼農, 就使用內部的多語言構建繫統, 用不同的規則來處理名字和定位包, 指定單元測試等待, 這樣可以緊密適配他們內部的繫統.
|
||||
就像我們在2.6.1節提到過的,Go語言的規范併沒有指明包的導入路徑字符串的具體含義,導入路徑的具體含義是由構建工具來解釋的。在本章,我們將深入討論Go語言工具箱的功能,包括大家經常使用的構建測試等功能。當然,也有第三方擴展的工具箱存在。例如,Google公司內部的Go語言碼農,他們就使用內部的多語言構建繫統(譯註:Google公司使用的是類似[Bazel](http://bazel.io)的構建繫統,支持多種編程語言,目前該構件繫統還不能完整支持Windows環境),用不同的規則來處理包名字和定位包,用不同的規則來處理單元測試等等,因爲這樣可以更緊密適配他們內部環境。
|
||||
|
||||
如果你計劃分享或發布包, 那麽導入路徑最好是全球唯一的. 爲了避免衝突, 所有非標準庫包的導入路徑建議以所在組織的互聯網域名爲前綴; 這樣也有利於包的檢索. 例如, 上面的包導入聲明導入了Go糰隊維護的HTML解析器和一個流行的第三方維護的MySQL驅動.
|
||||
如果你計劃分享或發布包,那麽導入路徑最好是全球唯一的。爲了避免衝突,所有非標準庫包的導入路徑建議以所在組織的互聯網域名爲前綴;而且這樣也有利於包的檢索。例如,上面的import語句導入了Go糰隊維護的HTML解析器和一個流行的第三方維護的MySQL驅動。
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 10.3. 包聲明
|
||||
|
||||
在每個Go源文件的開頭都必鬚有包聲明. 主要的目的是確定當前包被其他包導入時默認的標識符(稱爲包名).
|
||||
在每個Go語音源文件的開頭都必鬚有包聲明語句。包聲明語句的主要目的是確定當前包被其它包導入時默認的標識符(也稱爲包名)。
|
||||
|
||||
例如, math/rand 包的每個文件的開頭都是 `package rand` 包聲明, 所有 當你導入這個包, 你可以用 rand.Int, rand.Float64 的方式訪問包的成員.
|
||||
例如,math/rand包的每個源文件的開頭都包含`package rand`包聲明語句,所以當你導入這個包,你就可以用rand.Int、rand.Float64類似的方式訪問包的成員。
|
||||
|
||||
```Go
|
||||
package main
|
||||
@ -17,11 +17,10 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
通常來説, 默認的包名就是包導入路徑名的最後一段, 因此卽使兩個包的導入路徑不同, 它們依然可能有一個相同的包名. 例如, math/rand 和 crypto/rand 包的名字都是 rand. 稍後我們將看到如何同時導入兩個包名字相同的包.
|
||||
通常來説,默認的包名就是包導入路徑名的最後一段,因此卽使兩個包的導入路徑不同,它們依然可能有一個相同的包名。例如,math/rand包和crypto/rand包的包名都是rand。稍後我們將看到如何同時導入兩個有相同包名的包。
|
||||
|
||||
關於默認包名一般采用導入路徑名的最後一段的約定有三種例外情況. 第一個例外是包對應一個可執行程序, 也就是 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 中介紹.
|
||||
|
||||
第三個例外是一些依賴版本號的管理工具會在導入路徑後追加版本號信息, 例如 "gopkg.in/yaml.v2". 這種情況下包的名字併不包含版本號後綴, 隻是yaml.
|
||||
第二個例外,包所在的目録中可能有一些文件名是以_test.go爲後綴的Go源文件(譯註:前面必鬚有其它的字符,因爲以`_`前綴的源文件是被忽略的),併且這些源文件聲明的包名也是以_test爲後綴名的。這種目録可以包含兩種包:一種普通包,加一種則是測試的外部擴展包。所有以_test爲後綴包名的測試外部擴展包都由go test命令獨立編譯,普通包和測試的外部擴展包是相互獨立的。測試的外部擴展包一般用來避免測試代碼中的循環導入依賴,具體細節我們將在11.2.4節中介紹。
|
||||
|
||||
第三個例外,一些依賴版本號的管理工具會在導入路徑後追加版本號信息,例如"gopkg.in/yaml.v2"。這種情況下包的名字併不包含版本號後綴,而是yaml。
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 10.4. 導入聲明
|
||||
|
||||
一個Go源文件可以在包聲明語句之後, 其他非導入聲明之前, 包含零到多個導入包聲明. 每個導入聲明可以單獨指定一個導入路徑, 通過圓括號包含指定多個導入路徑. 下面兩個導入形式是等價的, 但是第二種形式更爲常見.
|
||||
可以在一個Go語言源文件包聲明語句之後,其它非導入聲明語句之前,包含零到多個導入包聲明語句。每個導入聲明可以單獨指定一個導入路徑,也可以通過圓括號同時導入多個導入路徑。下面兩個導入形式是等價的,但是第二種形式更爲常見。
|
||||
|
||||
```Go
|
||||
import "fmt"
|
||||
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
```
|
||||
|
||||
導入的包之間可以通過添加空行來分組; 通常將來自不同組織的包獨自分組. 導入順序無關緊要, 但是一般會根據字符串順序排列. (gofmt和goimports的都可以將不同分組的包獨立排序.)
|
||||
導入的包之間可以通過添加空行來分組;通常將來自不同組織的包獨自分組。包的導入順序無關緊要,但是在每個分組中一般會根據字符串順序排列。(gofmt和goimports工具都可以將不同分組導入的包獨立排序。)
|
||||
|
||||
```Go
|
||||
import (
|
||||
@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
```
|
||||
|
||||
如果我們想同時導入兩個名字相同的包, 例如 math/rand 和 crypto/rand, 導入聲明必鬚至少爲一個同名包指定一個新的包名, 以避免衝突. 這叫做導入包重命名.
|
||||
如果我們想同時導入兩個有着名字相同的包,例如math/rand包和crypto/rand包,那麽導入聲明必鬚至少爲一個同名包指定一個新的包名以避免衝突。這叫做導入包的重命名。
|
||||
|
||||
```Go
|
||||
import (
|
||||
@ -34,8 +34,8 @@ import (
|
||||
)
|
||||
```
|
||||
|
||||
導入包重命名隻影響當前的Go源文件. 其他的Go源文件如果導入了相同的包, 可以用導入包原本的名字或重命名爲另一個完全不同的名字.
|
||||
導入包的重命名隻影響當前的源文件。其它的源文件如果導入了相同的包,可以用導入包原本默認的名字或重命名爲另一個完全不同的名字。
|
||||
|
||||
導入包重命名是一個有用的特性, 不僅僅是爲了解決名字衝突. 如果導入的一個包名很笨重, 特别是在一些自動生成的代碼中, 這時候用一個簡短名稱會更方便. 選擇用簡短名稱重命名導入包時候最好統一, 比避免包名混亂. 選擇另一個包名稱還可以幫助避免和本地普通變量名産生衝突. 例如, 如果文件中已經有了一個名爲 path 的變量, 我們可以將"path"標準包重命名爲pathpkg.
|
||||
導入包重命名是一個有用的特性,它不僅僅隻是爲了解決名字衝突。如果導入的一個包名很笨重,特别是在一些自動生成的代碼中,這時候用一個簡短名稱會更方便。選擇用簡短名稱重命名導入包時候最好統一,以避免包名混亂。選擇另一個包名稱還可以幫助避免和本地普通變量名産生衝突。例如,如果文件中已經有了一個名爲path的變量,那麽我們可以將"path"標準包重命名爲pathpkg。
|
||||
|
||||
每個導入聲明明確指定了當前包和導入包之間的依賴關繫. 如果遇到包循環導入的情況, Go的構建工具將報告錯誤.
|
||||
每個導入聲明語句都明確指定了當前包和被導入包之間的依賴關繫。如果遇到包循環導入的情況,Go語言的構建工具將報告錯誤。
|
||||
|
@ -1,14 +1,14 @@
|
||||
## 10.5. 匿名導入
|
||||
## 10.5. 包的匿名導入
|
||||
|
||||
如果隻是導入一個包而併不使用導入的包是一個編譯錯誤. 但是有時候我們隻是想利用導入包産生的副作用: 它會計算包級變量的初始化表達式和執行導入包的 init 初始化函數 (§2.6.2). 這時候我們需要抑製“未使用的導入”錯誤是合理的, 我們可以用下劃線 `_` 來重命名導入的包. 像往常一樣, 下劃線 `_` 爲空白標識符, 併不能被訪問.
|
||||
如果隻是導入一個包而併不使用導入的包將會導致一個編譯錯誤。但是有時候我們隻是想利用導入包而産生的副作用:它會計算包級變量的初始化表達式和執行導入包的init初始化函數(§2.6.2)。這時候我們需要抑製“unused import”編譯錯誤,我們可以用下劃線`_`來重命名導入的包。像往常一樣,下劃線`_`爲空白標識符,併不能被訪問。
|
||||
|
||||
```Go
|
||||
import _ "image/png" // register PNG decoder
|
||||
```
|
||||
|
||||
這個被稱爲匿名導入. 它通常是用來實現一個編譯時機製, 然後通過在main主程序入口選擇性地導入附加的包. 首先, 讓我們看看如何使用它, 然後再看看它是如何工作的:
|
||||
這個被稱爲包的匿名導入。它通常是用來實現一個編譯時機製,然後通過在main主程序入口選擇性地導入附加的包。首先,讓我們看看如何使用該特性,然後再看看它是如何工作的。
|
||||
|
||||
標準庫的 image 圖像包導入了一個 `Decode` 函數, 用於從 `io.Reader` 接口讀取數據併解碼圖像, 它調用底層註冊的圖像解碼器工作, 然後返迴 image.Image 類型的圖像. 使用 `image.Decode` 很容易編寫一個圖像格式的轉換工具, 讀取一種格式的圖像, 然後編碼爲另一種圖像格式:
|
||||
標準庫的image圖像包包含了一個`Decode`函數,用於從`io.Reader`接口讀取數據併解碼圖像,它調用底層註冊的圖像解碼器來完成任務,然後返迴image.Image類型的圖像。使用`image.Decode`很容易編寫一個圖像格式的轉換工具,讀取一種格式的圖像,然後編碼爲另一種圖像格式:
|
||||
|
||||
```Go
|
||||
gopl.io/ch10/jpeg
|
||||
@ -42,7 +42,7 @@ func toJPEG(in io.Reader, out io.Writer) error {
|
||||
}
|
||||
```
|
||||
|
||||
如果我們將 `gopl.io/ch3/mandelbrot` (§3.3) 的輸出導入到這個工具的輸入, 它將解碼輸入的PNG格式圖像, 然後轉換爲JPEG格式的圖像(圖3.3).
|
||||
如果我們將`gopl.io/ch3/mandelbrot`(§3.3)的輸出導入到這個程序的標準輸入,它將解碼輸入的PNG格式圖像,然後轉換爲JPEG格式的圖像輸出(圖3.3)。
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch3/mandelbrot
|
||||
@ -51,7 +51,7 @@ $ ./mandelbrot | ./jpeg >mandelbrot.jpg
|
||||
Input format = png
|
||||
```
|
||||
|
||||
要註意 image/png 包的匿名導入語句. 如果沒有這一行語句, 依然可以編譯和運行, 但是它將不能識别 PNG 格式的圖像:
|
||||
要註意image/png包的匿名導入語句。如果沒有這一行語句,程序依然可以編譯和運行,但是它將不能正確識别和解碼PNG格式的圖像:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch10/jpeg
|
||||
@ -59,7 +59,7 @@ $ ./mandelbrot | ./jpeg >mandelbrot.jpg
|
||||
jpeg: image: unknown format
|
||||
```
|
||||
|
||||
下面的代碼演示了它的工作機製. 標準庫提供了GIF, PNG, 和 JPEG 格式圖像的解碼器, 用戶也可以提供自己的解碼器, 但是爲了保存程序體積較小, 很多解碼器併沒有被包含盡量, 除非是明確需要支持的格式. image.Decode 函數會査詢支持的格式列表. 列表的每個入口指定了四件事情: 格式的名稱; 一個用於描述這種圖像數據開頭部分模式的字符串, 用於解碼器檢測識别; 一個 Decode 函數 用於解碼圖像; 一個 DecodeConfig 函數用於解碼圖像的大小和顔色空間的信息. 每個入口是通過調用 image.RegisterFormat 函數註冊, 一般是在每個格式包的初始化函數中調用, 例如 image/png 包是這樣的:
|
||||
下面的代碼演示了它的工作機製。標準庫還提供了GIF、PNG和JPEG等格式圖像的解碼器,用戶也可以提供自己的解碼器,但是爲了保持程序體積較小,很多解碼器併沒有被全部包含,除非是明確需要支持的格式。image.Decode函數在解碼時會依次査詢支持的格式列表。每個格式驅動列表的每個入口指定了四件事情:格式的名稱;一個用於描述這種圖像數據開頭部分模式的字符串,用於解碼器檢測識别;一個Decode函數用於完成解碼圖像工作;一個DecodeConfig函數用於解碼圖像的大小和顔色空間的信息。每個驅動入口是通過調用image.RegisterFormat函數註冊,一般是在每個格式包的init初始化函數中調用,例如image/png包是這樣註冊的:
|
||||
|
||||
```Go
|
||||
package png // image/png
|
||||
@ -73,13 +73,13 @@ func init() {
|
||||
}
|
||||
```
|
||||
|
||||
最終的效果是, 主程序值需要匿名導入需要 image.Decode 支持的格式對應解碼包就可以解碼圖像了.
|
||||
最終的效果是,主程序隻需要匿名導入特定圖像驅動包就可以用image.Decode解碼對應格式的圖像了。
|
||||
|
||||
數據庫包 database/sql 也是采用了類似的技術, 讓用戶可以根據自己需要選擇導入必要的數據庫驅動. 例如:
|
||||
數據庫包database/sql也是采用了類似的技術,讓用戶可以根據自己需要選擇導入必要的數據庫驅動。例如:
|
||||
|
||||
```Go
|
||||
import (
|
||||
"database/mysql"
|
||||
"database/sql"
|
||||
_ "github.com/lib/pq" // enable support for Postgres
|
||||
_ "github.com/go-sql-driver/mysql" // enable support for MySQL
|
||||
)
|
||||
@ -89,7 +89,6 @@ db, err = sql.Open("mysql", dbname) // OK
|
||||
db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3"
|
||||
```
|
||||
|
||||
**練習 10.1:** 擴展 jpeg 程序, 支持任意圖像格式之間的相互轉換, 使用 image.Decode 檢測支持的格式類型, 然後同步 flag 命令行標誌參數選擇輸出的格式.
|
||||
|
||||
**練習 10.2:** 設計一個通用的壓縮文件讀取框架, 用來讀取 ZIP(archive/zip) 和 POSIX tar(archive/tar) 格式壓縮的文檔. 使用類似上面的註冊機製來擴展支持不同的壓縮格式, 然後根據需要通過匿名導入選擇支持的格式.
|
||||
**練習 10.1:** 擴展jpeg程序,以支持任意圖像格式之間的相互轉換,使用image.Decode檢測支持的格式類型,然後通過flag命令行標誌參數選擇輸出的格式。
|
||||
|
||||
**練習 10.2:** 設計一個通用的壓縮文件讀取框架,用來讀取ZIP(archive/zip)和POSIX tar(archive/tar)格式壓縮的文檔。使用類似上面的註冊技術來擴展支持不同的壓縮格式,然後根據需要通過匿名導入選擇導入要支持的壓縮格式的驅動包。
|
||||
|
@ -1,25 +1,22 @@
|
||||
## 10.6. 包和命名
|
||||
|
||||
在本節中,我們將提供一些關於Go語言獨特的包和成員命名的約定。
|
||||
|
||||
在本節中, 我們將提供一些關於如何遵循Go語言獨特的包和成員的命名約定.
|
||||
當創建一個包,一般要用短小的包名,但也不能太短導致難以理解。標準庫中最常用的包有bufio、bytes、flag、fmt、http、io、json、os、sort、sync和time等包。
|
||||
|
||||
當創建一個包, 一般要用短小的包名, 但也不能太短導致難以理解.
|
||||
標準庫中最常用的包有 bufio, bytes, flag, fmt, http, io, json, os, sort, sync, 和 time 等包.
|
||||
它們的名字都簡潔明了。例如,不要將一個類似imageutil或ioutilis的通用包命名爲util,雖然它看起來很短小。要盡量避免包名使用可能被經常用於局部變量的名字,這樣可能導致用戶重命名導入包,例如前面看到的path包。
|
||||
|
||||
它們的名字都簡潔明了. 例如, 不要將一個類似 imageutil 或 ioutilis 的通用包命名爲 util,
|
||||
雖然它看起來很短小. 要盡量避免包名使用經常被用於局部變量的名字, 這樣可能導致用戶重命名導入包, 例如前面看到的 path 包.
|
||||
包名一般采用單數的形式。標準庫的bytes、errors和strings使用了複數形式,這是爲了避免和預定義的類型衝突,同樣還有go/types是爲了避免和type關鍵字衝突。
|
||||
|
||||
包名同時采用單數的形式. 標準庫的 bytes, errors, 和 strings 使用了複數是爲了避免和預定義的類型衝突, 同樣還有 go/types 是爲了避免和關鍵字衝突.
|
||||
要避免包名有其它的含義。例如,2.5節中我們的溫度轉換包最初使用了temp包名,雖然併沒有持續多久。但這是一個糟糕的嚐試,因爲temp幾乎是臨時變量的同義詞。然後我們有一段時間使用了temperature作爲包名,雖然名字併沒有表達包的眞實用途。最後我們改成了和strconv標準包類似的tempconv包名,這個名字比之前的就好多了。
|
||||
|
||||
要避免包名有其他的含義. 例如, 2.5節中我們的溫度轉換包最初使用了 temp 包名, 雖然併沒有持續多久. 這是一個糟糕的做法, 因爲 `temp` 幾乎是臨時變量的同義詞. 然後我們有一段時間使用了 temperature 作爲包名, 雖然名字併沒有表達包的眞是用途. 最後我們改成了 tempconv 包名, 和 strconv 類似也很簡潔明了.
|
||||
|
||||
現在讓我們看看如何命名包的襯衣. 由於是通過包的導入名字引入包里面的成員, 例如 fmt.Println, 同時包含了包和成名的描述信息(翻譯障礙). 我們併不需要關註Println的具體內容, 因爲 fmt 已經包含了這個信息. 當設計一個包的時候, 需要考慮包名和成員名兩個部分如何配合. 下面有一些例子:
|
||||
現在讓我們看看如何命名包的成員。由於是通過包的導入名字引入包里面的成員,例如fmt.Println,同時包含了包名和成員名信息。因此,我們一般併不需要關註Println的具體內容,因爲fmt包名已經包含了這個信息。當設計一個包的時候,需要考慮包名和成員名兩個部分如何很好地配合。下面有一些例子:
|
||||
|
||||
```
|
||||
bytes.Equal flag.Int http.Get json.Marshal
|
||||
```
|
||||
|
||||
我們可以看到一些常用的命名模式. strings 包提供了字符串相關的諸多操作:
|
||||
我們可以看到一些常用的命名模式。strings包提供了和字符串相關的諸多操作:
|
||||
|
||||
```Go
|
||||
package strings
|
||||
@ -33,9 +30,9 @@ type Reader struct{ /* ... */ }
|
||||
func NewReader(s string) *Reader
|
||||
```
|
||||
|
||||
string 本身併沒有出現在每個成員名字中. 因爲用戶會這樣引用這些成員 strings.Index, strings.Replacer 等.
|
||||
字符串string本身併沒有出現在每個成員名字中。因爲用戶會這樣引用這些成員strings.Index、strings.Replacer等。
|
||||
|
||||
其他一些包, 可能隻描述了單一的數據類型, 例如 html/template 和 math/rand 等, 隻暴露一個主要的數據結構和與它相關的方法, 還有一個 New 名字的函數用於創建實例.
|
||||
其它一些包,可能隻描述了單一的數據類型,例如html/template和math/rand等,隻暴露一個主要的數據結構和與它相關的方法,還有一個以New命名的函數用於創建實例。
|
||||
|
||||
```Go
|
||||
package rand // "math/rand"
|
||||
@ -44,11 +41,6 @@ type Rand struct{ /* ... */ }
|
||||
func New(source Source) *Rand
|
||||
```
|
||||
|
||||
這可能導致一些名字重複, 例如 template.Template 或 rand.Rand, 這就是爲什麽這些種類的包的名稱往往特别短.
|
||||
|
||||
另一個極端, 還有像 net/http 包那樣含有非常多的名字和不多的數據類型, 因爲它們是要執行一個複雜的複合任務. 盡管有將近二十種類型和更多的函數, 包中最重要的成員名字卻是簡單明了的: Get, Post, Handle, Error, Client, Server.
|
||||
|
||||
有包net/http這樣有很多名字沒有很多結構,因爲他們執行一個複雜任務。盡管二十類型和更多的功能,包最重要的成員最簡單的名字:Get、Post、處理、錯誤,客戶端,服務器。
|
||||
|
||||
|
||||
這可能導致一些名字重複,例如template.Template或rand.Rand,這就是爲什麽這些種類的包名往往特别短的原因之一。
|
||||
|
||||
在另一個極端,還有像net/http包那樣含有非常多的名字和種類不多的數據類型,因爲它們都是要執行一個複雜的複合任務。盡管有將近二十種類型和更多的函數,但是包中最重要的成員名字卻是簡單明了的:Get、Post、Handle、Error、Client、Server等。
|
||||
|
@ -1,14 +1,13 @@
|
||||
### 10.7.1. 工作區結構
|
||||
|
||||
|
||||
對於大多數的Go用戶, 隻需要配置一個名叫GOPATH的環境變量, 用來指定根工作目録卽可. 當需要切換到不同工作區的時候, 隻要更新GOPATH就可以了. 例如, 我們在編寫本書時, 將GOPATH設置爲 `$HOME/gobook`:
|
||||
對於大多數的Go語言用戶,隻需要配置一個名叫GOPATH的環境變量,用來指定當前工作目録卽可。當需要切換到不同工作區的時候,隻要更新GOPATH就可以了。例如,我們在編寫本書時將GOPATH設置爲`$HOME/gobook`:
|
||||
|
||||
```
|
||||
$ export GOPATH=$HOME/gobook
|
||||
$ go get gopl.io/...
|
||||
```
|
||||
|
||||
當你用前面介紹的命令下載本書全部的程序之後, 你的當前工作區的目録結構是這樣的:
|
||||
當你用前面介紹的命令下載本書全部的例子源碼之後,你的當前工作區的目録結構應該是這樣的:
|
||||
|
||||
```
|
||||
GOPATH/
|
||||
@ -35,11 +34,11 @@ GOPATH/
|
||||
...
|
||||
```
|
||||
|
||||
GOPATH對應的目録有三個子目録. 其中 src 子目録用於存儲源代碼. 每個包保存在$GOPATH/src的相對路徑爲包導入路徑的子目録中, 例如 gopl.io/ch1/helloworld 相對路徑. 我們看到, 一個GOPATH工作區的src目録中可能有多個獨立的版本控製, 例如 gopl.io 或 golang.org. 其中 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` 命令用於査看工具涉及的所有環境變量的值, 包括未設置環境變量的默認值. GOOS 用於指定目標操作繫統(例如 android, linux, darwin, 或 windows), GOARCH 用於指定處理器的類型, 例如 amd64, 386, 或 arm. 雖然 GOPATH 是唯一必需要設置的, 但是其它的也有偶爾用到.
|
||||
其中`go env`命令用於査看Go語音工具涉及的所有環境變量的值,包括未設置環境變量的默認值。GOOS環境變量用於指定目標操作繫統(例如android、linux、darwin或windows),GOARCH環境變量用於指定處理器的類型,例如amd64、386或arm等。雖然GOPATH環境變量是唯一必需要設置的,但是其它環境變量也會偶爾用到。
|
||||
|
||||
```
|
||||
$ go env
|
||||
@ -49,5 +48,3 @@ GOARCH="amd64"
|
||||
GOOS="darwin"
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
### 10.7.2. 下載包
|
||||
|
||||
使用Go工具, 不僅可以根據包導入路徑找到本地工作區的包, 甚至可以從互聯網上找到和更新包.
|
||||
使用Go語言工具箱的go命令,不僅可以根據包導入路徑找到本地工作區的包,甚至可以從互聯網上找到和更新包。
|
||||
|
||||
使用命令 `go get` 可以下載一個單一的包或者用 `...` 下載整個子目録里面的每個包. Go工具同時計算併下載所依賴的每個包, 這也是前一個例子中 golang.org/x/net/html 自動出現在本地工作區目録的原因.
|
||||
使用命令`go get`可以下載一個單一的包或者用`...`下載整個子目録里面的每個包。Go語言工具箱的go命令同時計算併下載所依賴的每個包,這也是前一個例子中golang.org/x/net/html自動出現在本地工作區目録的原因。
|
||||
|
||||
一旦 `go get` 命令下載了包, 然後就是安裝包或包對應的命令. 我們將在下一節再關註它的細節, 現在隻是展示下整個過程是如何的簡單. 第一個命令是獲取 golint 工具, 用於檢測Go源代碼的編程風格是否有問題. 第二個命令是用 golint 對 2.6.2節的 gopl.io/ch2/popcount 包代碼進行編碼風格檢査. 它友好地報告了忘記了包的文檔:
|
||||
一旦`go get`命令下載了包,然後就是安裝包或包對應的可執行的程序。我們將在下一節再關註它的細節,現在隻是展示整個下載過程是如何的簡單。第一個命令是獲取golint工具,它用於檢測Go源代碼的編程風格是否有問題。第二個命令是用golint命令對2.6.2節的gopl.io/ch2/popcount包代碼進行編碼風格檢査。它友好地報告了忘記了包的文檔:
|
||||
|
||||
```
|
||||
$ go get github.com/golang/lint/golint
|
||||
@ -13,9 +13,9 @@ src/gopl.io/ch2/popcount/main.go:1:1:
|
||||
package comment should be of the form "Package popcount ..."
|
||||
```
|
||||
|
||||
`go get` 命令支持當前流行的託管網站 GitHub, Bitbucket, 和 Launchpad, 可以直接從它們的版本控製繫統請求代碼. 對於其他的網站, 你可能需要指定版本控製繫統的具體路徑和協議, 例如 Git 或 Mercurial. 運行 `go help importpath` 獲取更新的信息.
|
||||
`go get`命令支持當前流行的託管網站GitHub、Bitbucket和Launchpad,可以直接向它們的版本控製繫統請求代碼。對於其它的網站,你可能需要指定版本控製繫統的具體路徑和協議,例如 Git或Mercurial。運行`go help importpath`獲取相關的信息。
|
||||
|
||||
`go get` 獲取的代碼是眞實的本地存儲倉庫, 不僅僅隻是複製文件, 因此你依然可以使用版本管理工具比較本地代碼的變更, 或者切換到其他的版本. 例如 golang.org/x/net 目録對應一個 Git 倉庫:
|
||||
`go get`命令獲取的代碼是眞實的本地存儲倉庫,而不僅僅隻是複製源文件,因此你依然可以使用版本管理工具比較本地代碼的變更或者切換到其它的版本。例如golang.org/x/net包目録對應一個Git倉庫:
|
||||
|
||||
```
|
||||
$ cd $GOPATH/src/golang.org/x/net
|
||||
@ -24,7 +24,7 @@ origin https://go.googlesource.com/net (fetch)
|
||||
origin https://go.googlesource.com/net (push)
|
||||
```
|
||||
|
||||
需要註意的是導入路徑含有的網站域名和本地Git倉庫遠程的Git服務地址併不相同, 眞實的Git地址是 go.googlesource.com. 這其實是Go工具箱的一個特性, 可以讓包用一個自定義的導入路徑, 但是眞實的代碼卻是由更通用的服務提供, 例如 googlesource.com 或 github.com. 頁面 https://golang.org/x/net/html 包含了如下的元數據, 告訴 Go 工具Git倉庫的眞實託管地址:
|
||||
需要註意的是導入路徑含有的網站域名和本地Git倉庫對應遠程服務地址併不相同,眞實的Git地址是go.googlesource.com。這其實是Go語言工具的一個特性,可以讓包用一個自定義的導入路徑,但是眞實的代碼卻是由更通用的服務提供,例如googlesource.com或github.com。因爲頁面 https://golang.org/x/net/html 包含了如下的元數據,它告訴Go語言的工具當前包眞實的Git倉庫託管地址:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch1/fetch
|
||||
@ -33,9 +33,8 @@ $ ./fetch https://golang.org/x/net/html | grep go-import
|
||||
content="golang.org/x/net git https://go.googlesource.com/net">
|
||||
```
|
||||
|
||||
如果指定 `-u` 命令行標誌參數, `go get` 將確保所有的包和依賴的包的版本都是最新的, 然後編譯和安裝它們. 如果不包含該標誌參數, 如果包已經在本地存在, 那麽將不會被更新.
|
||||
如果指定`-u`命令行標誌參數,`go get`命令將確保所有的包和依賴的包的版本都是最新的,然後重新編譯和安裝它們。如果不包含該標誌參數的話,而且如果包已經在本地存在,那麽代碼那麽將不會被自動更新。
|
||||
|
||||
`go get -u` 命令隻是簡單地保證每個包是最新版本, 如果你是第一次下載則比較很方便的; 但是如果是發布程序則可能是不合適的, 因爲本地程序可能需要對依賴的包做精確的版本依賴管理. 通常的解決方案是使用 vendor 目録存儲固定版本的代碼, 對本地依賴的包的版本更新也是謹慎和持續可控的. 在 Go 1.5 之前, 一般需要脩改包的導入路徑, 所以複製後 golang.org/x/net/html 導入路徑可能會變爲 gopl.io/vendor/golang.org/x/net/html. 最新的Go工具已經支持 vendor 特性, 但限於篇幅這里併不討論細節. 不過可以通過 `go help gopath` 目録査看 Vendor 目録的幫助.
|
||||
|
||||
**練習 10.3:** 從 http://gopl.io/ch1/helloworld?go-get=1 獲取內容, 査看本書的代碼的眞實託管的網址(`go get`請求HTML頁面時包含了 `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的幫助文檔。
|
||||
|
||||
**練習 10.3:** 從 http://gopl.io/ch1/helloworld?go-get=1 獲取內容,査看本書的代碼的眞實託管的網址(`go get`請求HTML頁面時包含了`go-get`參數,以區别普通的瀏覽器請求)。
|
||||
|
@ -1,32 +1,31 @@
|
||||
### 10.7.3. 構建包
|
||||
|
||||
`go build` 命令編譯參數指定的每個包. 如果包是一個庫, 則忽略輸出結果; 這可以用於檢測包的可以正確編譯的.
|
||||
如果包的名字是 main, `go build` 將調用連接器在當前目録創建一個可執行程序; 導入路徑的最後一段作爲可執行程序的名字.
|
||||
`go build`命令編譯命令行參數指定的每個包。如果包是一個庫,則忽略輸出結果;這可以用於檢測包的可以正確編譯的。如果包的名字是main,`go build`將調用連接器在當前目録創建一個可執行程序;以導入路徑的最後一段作爲可執行程序的名字。
|
||||
|
||||
因爲每個目録隻包含一個包, 因此每個可執行程序後者叫Unix術語中的命令, 會要求放到一個獨立的目録. 這些目録有時候會放在名叫 cmd 目録的子目録下面, 例如用於提供Go文檔服務的 golang.org/x/tools/cmd/godoc 命令 (§10.7.4).
|
||||
因爲每個目録隻包含一個包,因此每個對應可執行程序或者叫Unix術語中的命令的包,會要求放到一個獨立的目録中。這些目録有時候會放在名叫cmd目録的子目録下面,例如用於提供Go文檔服務的golang.org/x/tools/cmd/godoc命令就是放在cmd子目録(§10.7.4)。
|
||||
|
||||
每個包可以由它們的導入路徑指定, 就像前面看到的那樣, 或者有一個相對目録的路徑知道, 必鬚以 `.` 或 `..` 開頭. 如果沒有指定參數, 那麽默認指定爲當前的目録. 下面的命令用於構建同一個包, 雖然它們的寫法各不相同:
|
||||
每個包可以由它們的導入路徑指定,就像前面看到的那樣,或者用一個相對目録的路徑知指定,相對路徑必鬚以`.`或`..`開頭。如果沒有指定參數,那麽默認指定爲當前目録對應的包。 下面的命令用於構建同一個包, 雖然它們的寫法各不相同:
|
||||
|
||||
```
|
||||
$ cd $GOPATH/src/gopl.io/ch1/helloworld
|
||||
$ go build
|
||||
```
|
||||
|
||||
或者:
|
||||
或者:
|
||||
|
||||
```
|
||||
$ cd anywhere
|
||||
$ go build gopl.io/ch1/helloworld
|
||||
```
|
||||
|
||||
或者:
|
||||
或者:
|
||||
|
||||
```
|
||||
$ cd $GOPATH
|
||||
$ go build ./src/gopl.io/ch1/helloworld
|
||||
```
|
||||
|
||||
但不能這樣:
|
||||
但不能這樣:
|
||||
|
||||
```
|
||||
$ cd $GOPATH
|
||||
@ -34,7 +33,7 @@ $ go build src/gopl.io/ch1/helloworld
|
||||
Error: cannot find package "src/gopl.io/ch1/helloworld".
|
||||
```
|
||||
|
||||
也可以指定包的源文件列表, 一般這隻用於構建一些小程序或臨時性的實驗. 如果是main包, 將以第一個Go源文件的基礎文件名作爲可執行程序的名字.
|
||||
也可以指定包的源文件列表,這一般這隻用於構建一些小程序或做一些臨時性的實驗。如果是main包,將會以第一個Go源文件的基礎文件名作爲最終的可執行程序的名字。
|
||||
|
||||
```
|
||||
$ cat quoteargs.go
|
||||
@ -53,22 +52,22 @@ $ ./quoteargs one "two three" four\ five
|
||||
["one" "two three" "four five"]
|
||||
```
|
||||
|
||||
特别是對於這類一次性的程序, 我們繫統盡快的構建併運行它. `go run` 命令結合了構建和運行的兩個步驟:
|
||||
特别是對於這類一次性運行的程序,我們希望盡快的構建併運行它。`go run`命令實際上是結合了構建和運行的兩個步驟:
|
||||
|
||||
```
|
||||
$ go run quoteargs.go one "two three" four\ five
|
||||
["one" "two three" "four five"]
|
||||
```
|
||||
|
||||
第一行的參數列表中第一個不是以 .go 結尾的將作爲可執行程序的參數運行.
|
||||
第一行的參數列表中,第一個不是以`.go`結尾的將作爲可執行程序的參數運行。
|
||||
|
||||
默認情況下, `go build` 命令構建指定的包和它依賴的包, 然後丟棄所有除了最後的可執行文件之外的中間編譯結果. 依賴分析和編譯都是很快的, 但是隨着項目增加到幾十個包和成韆上萬行代碼, 依賴關繫分析和編譯時間的消耗將變的可觀, 可能需要幾秒種, 卽使這些依賴項沒有改變.
|
||||
默認情況下,`go build`命令構建指定的包和它依賴的包,然後丟棄除了最後的可執行文件之外所有的中間編譯結果。依賴分析和編譯過程雖然都是很快的,但是隨着項目增加到幾十個包和成韆上萬行代碼,依賴關繫分析和編譯時間的消耗將變的可觀,有時候可能需要幾秒種,卽使這些依賴項沒有改變。
|
||||
|
||||
`go install` 命令和 `go build` 命令很相似, 但是它保存每個包的編譯成果, 而不是將它們都丟棄. 被編譯的包被保存到 $GOPATH/pkg 目録下和 src 目録對應, 可執行程序被保存到 $GOPATH/bin 目録. (很多用戶將 $GOPATH/bin 添加到可執行程序的蒐索列表中.) 還有, `go install` 命令和 `go build` 命令都不會重新編譯沒有發生變化的包, 這可以使後續構建更快捷. 爲了方便, `go build -i` 將安裝每個目標所依賴的包.
|
||||
`go install`命令和`go build`命令很相似,但是它會保存每個包的編譯成果,而不是將它們都丟棄。被編譯的包會被保存到$GOPATH/pkg目録下,目録路徑和 src目録路徑對應,可執行程序被保存到$GOPATH/bin目録。(很多用戶會將$GOPATH/bin添加到可執行程序的蒐索列表中。)還有,`go install`命令和`go build`命令都不會重新編譯沒有發生變化的包,這可以使後續構建更快捷。爲了方便編譯依賴的包,`go build -i`命令將安裝每個目標所依賴的包。
|
||||
|
||||
因爲編譯對應不同的操作繫統平台和CPU架構, `go install` 會將編譯結果安裝到 GOOS 和 GOARCH 對應的目録. 例如, 在 Mac 繫統 golang.org/x/net/html 包將被安裝到 $GOPATH/pkg/darwin_amd64 目録下的 golang.org/x/net/html.a 文件.
|
||||
因爲編譯對應不同的操作繫統平台和CPU架構,`go install`命令會將編譯結果安裝到GOOS和GOARCH對應的目録。例如,在Mac繫統,golang.org/x/net/html包將被安裝到$GOPATH/pkg/darwin_amd64目録下的golang.org/x/net/html.a文件。
|
||||
|
||||
針對不同操作繫統或CPU的交叉構建也是很簡單的. 隻需要設置好目標對應的GOOS 和 GOARCH, 然後運行構建目録卽可. 下面交叉編譯的程序將輸出它在編譯時操作繫統和CPU類型:
|
||||
針對不同操作繫統或CPU的交叉構建也是很簡單的。隻需要設置好目標對應的GOOS和GOARCH,然後運行構建命令卽可。下面交叉編譯的程序將輸出它在編譯時操作繫統和CPU類型:
|
||||
|
||||
```Go
|
||||
gopl.io/ch10/cross
|
||||
@ -78,7 +77,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
下面以64位和32位環境分别執行程序:
|
||||
下面以64位和32位環境分别執行程序:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch10/cross
|
||||
@ -89,24 +88,20 @@ $ ./cross
|
||||
darwin 386
|
||||
```
|
||||
|
||||
有些包可能需要針對不同平台和處理器類型輸出不同版本的代碼, 以便於處理底層的可移植性問題或提供爲一些特點代碼提供優化. 如果一個文件名包含了一個操作繫統或處理器類型名字, 例如 net_linux.go 或 asm_amd64.s, Go工具將隻在對應的平台編譯這些文件. 還有一個特别的構建註釋註釋可以提供更多的構建控製. 例如, 文件中如果包含下面的註釋:
|
||||
有些包可能需要針對不同平台和處理器類型使用不同版本的代碼文件,以便於處理底層的可移植性問題或提供爲一些特定代碼提供優化。如果一個文件名包含了一個操作繫統或處理器類型名字,例如net_linux.go或asm_amd64.s,Go語言的構建工具將隻在對應的平台編譯這些文件。還有一個特别的構建註釋註釋可以提供更多的構建過程控製。例如,文件中可能包含下面的註釋:
|
||||
|
||||
```Go
|
||||
// +build linux darwin
|
||||
```
|
||||
|
||||
在包聲明的前面(含包的註釋), 告訴 `go build` 隻在針對 Linux 或 Mac OS X 是才編譯這個文件. 下面的構建註釋表示不編譯這個文件:
|
||||
在包聲明和包註釋的前面,該構建註釋參數告訴`go build`隻在編譯程序對應的目標操作繫統是Linux或Mac OS X時才編譯這個文件。下面的構建註釋則表示不編譯這個文件:
|
||||
|
||||
```Go
|
||||
// +build ignore
|
||||
```
|
||||
|
||||
For more details, see the Build Constraints section of the go/build package’s documentation:
|
||||
|
||||
更多細節, 可以參考 go/build 包的構建約束部分的文檔.
|
||||
更多細節,可以參考go/build包的構建約束部分的文檔。
|
||||
|
||||
```
|
||||
$ go doc go/build
|
||||
```
|
||||
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
### 10.7.4. 包文檔
|
||||
|
||||
Go的編碼風格鼓勵爲每個包提供良好的文檔. 包中每個導出的成員和包聲明前都應該包含添加目的和用法説明的註釋.
|
||||
Go語言的編碼風格鼓勵爲每個包提供良好的文檔。包中每個導出的成員和包聲明前都應該包含目的和用法説明的註釋。
|
||||
|
||||
Go中包文檔註釋一般是完整的句子, 第一行是包的摘要説明, 註釋後僅跟着包聲明語句. 函數的參數或其他的標識符併不需要額外的引號或其他標記註明. 例如, 下面是 fmt.Fprintf 的文檔註釋.
|
||||
Go語言中包文檔註釋一般是完整的句子,第一行是包的摘要説明,註釋後僅跟着包聲明語句。註釋中函數的參數或其它的標識符併不需要額外的引號或其它標記註明。例如,下面是fmt.Fprintf的文檔註釋。
|
||||
|
||||
```Go
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
@ -10,13 +10,13 @@ Go中包文檔註釋一般是完整的句子, 第一行是包的摘要説明,
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (int, error)
|
||||
```
|
||||
|
||||
Fprintf 函數格式化的細節在 fmt 包文檔中描述. 如果註釋後僅跟着包聲明語句, 那註釋對應整個包的文檔. 包文檔對應的註釋隻能有一個(譯註: 其實可以多個, 它們會組合成一個包文檔註釋.), 可以出現在任何一個源文件中. 如果包的註釋內容比較長, 可以當到一個獨立的文件中; fmt 包註釋就有 300 行之多. 這個專門用於保證包文檔的文件通常叫 doc.go.
|
||||
Fprintf函數格式化的細節在fmt包文檔中描述。如果註釋後僅跟着包聲明語句,那註釋對應整個包的文檔。包文檔對應的註釋隻能有一個(譯註:其實可以有多個,它們會組合成一個包文檔註釋),包註釋可以出現在任何一個源文件中。如果包的註釋內容比較長,一般會放到一個獨立的源文件中;fmt包註釋就有300行之多。這個專門用於保存包文檔的源文件通常叫doc.go。
|
||||
|
||||
好的文檔併不需要面面俱到, 文檔本身應該是簡潔但可不忽略的. 事實上, Go的風格喜歡簡潔的文檔, 併且文檔也是需要想代碼一樣維護的. 對於一組聲明語句, 可以同一個精鍊的句子描述, 如果是顯而易見的功能則併不需要註釋.
|
||||
好的文檔併不需要面面俱到,文檔本身應該是簡潔但可不忽略的。事實上,Go語言的風格更喜歡簡潔的文檔,併且文檔也是需要像代碼一樣維護的。對於一組聲明語句,可以用一個精鍊的句子描述,如果是顯而易見的功能則併不需要註釋。
|
||||
|
||||
在本書中, 隻要空間允許, 我們之前很多包聲明都包含了註釋文檔, 但你可以從標準庫中發現很多更好的例子. 有兩個工具可以幫到你.
|
||||
在本書中,隻要空間允許,我們之前很多包聲明都包含了註釋文檔,但你可以從標準庫中發現很多更好的例子。有兩個工具可以幫到你。
|
||||
|
||||
`go doc` 命令打印包的聲明和每個成員的文檔註釋, 下面是整個包的文檔:
|
||||
首先是`go doc`命令,該命令打印包的聲明和每個成員的文檔註釋,下面是整個包的文檔:
|
||||
|
||||
```
|
||||
$ go doc time
|
||||
@ -34,7 +34,7 @@ type Time struct { ... }
|
||||
...many more...
|
||||
```
|
||||
|
||||
或者是包的一個成員的註釋文檔:
|
||||
或者是某個具體包成員的註釋文檔:
|
||||
|
||||
```
|
||||
$ go doc time.Since
|
||||
@ -44,7 +44,7 @@ func Since(t Time) Duration
|
||||
It is shorthand for time.Now().Sub(t).
|
||||
```
|
||||
|
||||
或者是包的一個方法的註釋文檔:
|
||||
或者是某個具體包的一個方法的註釋文檔:
|
||||
|
||||
```
|
||||
$ go doc time.Duration.Seconds
|
||||
@ -53,7 +53,7 @@ func (d Duration) Seconds() float64
|
||||
Seconds returns the duration as a floating-point number of seconds.
|
||||
```
|
||||
|
||||
該工具併不需要輸入完整的包導入路徑或正確的大小寫. 下面的命令打印 encoding/json 包的 (*json.Decoder).Decode 方法的文檔:
|
||||
該命令併不需要輸入完整的包導入路徑或正確的大小寫。下面的命令將打印encoding/json包的`(*json.Decoder).Decode`方法的文檔:
|
||||
|
||||
```
|
||||
$ go doc json.decode
|
||||
@ -63,15 +63,12 @@ func (dec *Decoder) Decode(v interface{}) error
|
||||
it in the value pointed to by v.
|
||||
```
|
||||
|
||||
第二個工具, 令人睏惑的也是名叫 godoc, 提供可以相互交叉引用的 HTML 頁面, 但是包含和 `go doc` 相同以及更多的信息. 10.1 節演示了 time 包的文檔, 11.6 節將看到godoc演示可以交互的示例程序. godoc 的在線服務 https://godoc.org, 包含了成韆上萬的開源包的檢索工具.
|
||||
第二個工具,名字也叫godoc,它提供可以相互交叉引用的HTML頁面,但是包含和`go doc`命令相同以及更多的信息。10.1節演示了time包的文檔,11.6節將看到godoc演示可以交互的示例程序。godoc的在線服務 https://godoc.org ,包含了成韆上萬的開源包的檢索工具。
|
||||
|
||||
You can also run an instance of godoc in your workspace if you want to browse your own packages. Visit http://localhost:8000/pkg in your browser while running this command:
|
||||
|
||||
你也可以在自己的工作區目録允許 godoc 服務. 運行下面的命令, 然後在瀏覽器査看 http://localhost:8000/pkg 頁面:
|
||||
你也可以在自己的工作區目録運行godoc服務。運行下面的命令,然後在瀏覽器査看 http://localhost:8000/pkg 頁面:
|
||||
|
||||
```
|
||||
$ godoc -http :8000
|
||||
```
|
||||
|
||||
其中 `-analysis=type` 和 `-analysis=pointer` 命令行標誌參數用於打開文檔和代碼中關於靜態分析的結果.
|
||||
|
||||
其中`-analysis=type`和`-analysis=pointer`命令行標誌參數用於打開文檔和代碼中關於靜態分析的結果。
|
||||
|
@ -1,12 +1,12 @@
|
||||
### 10.7.5. 內部包
|
||||
|
||||
在Go程序中, 包的封裝機製是一個重要的特性. 爲導出的標識符隻在同一個包內部可以訪問, 導出的標識符則是面向全世界可見.
|
||||
在Go語音程序中,包的封裝機製是一個重要的特性。沒有導出的標識符隻在同一個包內部可以訪問,而導出的標識符則是面向全宇宙都是可見的。
|
||||
|
||||
有時候, 一個中間的狀態可能也是有用的, 對於一小部分信任的包是可見的, 但併不是對所有調用者都可見. 例如, 當我們計劃將一個大的包拆分爲很多小的更容易管理的子包, 但是我們併不想將內部的子包結構也完全暴露出去. 同時, 我們肯呢個還希望在內部子包之間共享一些通用的處理包. 或者我們隻是想實驗一個新包的還併不穩定的接口, 暫時隻暴露給一些受限製的客戶端.
|
||||
有時候,一個中間的狀態可能也是有用的,對於一小部分信任的包是可見的,但併不是對所有調用者都可見。例如,當我們計劃將一個大的包拆分爲很多小的更容易維護的子包,但是我們併不想將內部的子包結構也完全暴露出去。同時,我們可能還希望在內部子包之間共享一些通用的處理包,或者我們隻是想實驗一個新包的還併不穩定的接口,暫時隻暴露給一些受限製的用戶使用。
|
||||
|
||||
![](../images/ch10-01.png)
|
||||
|
||||
爲了滿足這些需求, Go構建工具支持包含 internal 名字的路徑段的包導入路徑. 這種包叫 internal 包, 一個 internal 包隻能被有和internal目録有同一個父目録的包所導入. 例如, net/http/internal/chunked 內部包隻能被 net/http/httputil 或 net/http 導入, 但是不能被 net/url 包導入. 但是 net/url 包 可以導入 net/http/httputil.
|
||||
爲了滿足這些需求,Go語言的構建工具對包含internal名字的路徑段的包導入路徑做了特殊處理。這種包叫internal包,一個internal包隻能被和internal目録有同一個父目録的包所導入。例如,net/http/internal/chunked內部包隻能被net/http/httputil或net/http包導入,但是不能被net/url包導入。不過net/url包卻可以導入net/http/httputil包。
|
||||
|
||||
```
|
||||
net/http
|
||||
@ -14,4 +14,3 @@ net/http/internal/chunked
|
||||
net/http/httputil
|
||||
net/url
|
||||
```
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
### 10.7.6. 査詢包
|
||||
|
||||
`go list` 工具可以報告可用包的信息. 其最簡單的形式, 可以測試包是否在工作區併打印他的導入路徑:
|
||||
`go list`命令可以査詢可用包的信息。其最簡單的形式,可以測試包是否在工作區併打印它的導入路徑:
|
||||
|
||||
```
|
||||
$ go list github.com/go-sql-driver/mysql
|
||||
github.com/go-sql-driver/mysql
|
||||
```
|
||||
|
||||
`go list` 參數還可以用 `"..."` 表示匹配任意的包的導入路徑. 我們可以用它來列表工作區中的所有包:
|
||||
`go list`命令的參數還可以用`"..."`表示匹配任意的包的導入路徑。我們可以用它來列表工作區中的所有包:
|
||||
|
||||
```
|
||||
$ go list ...
|
||||
@ -20,7 +20,7 @@ cmd/api
|
||||
...many more...
|
||||
```
|
||||
|
||||
或者是特定子目録下的所有包:
|
||||
或者是特定子目録下的所有包:
|
||||
|
||||
```
|
||||
$ go list gopl.io/ch3/...
|
||||
@ -33,7 +33,7 @@ gopl.io/ch3/printints
|
||||
gopl.io/ch3/surface
|
||||
```
|
||||
|
||||
或者是和某個主體相關的:
|
||||
或者是和某個主題相關的所有包:
|
||||
|
||||
```
|
||||
$ go list ...xml...
|
||||
@ -41,7 +41,7 @@ encoding/xml
|
||||
gopl.io/ch7/xmlselect
|
||||
```
|
||||
|
||||
`go list` 可以獲取每個包完整的元信息, 而不僅僅隻是導入路徑, 這些信息可以以不同格式提供給用戶. 其中 `-json` 標誌參數表示用JSON格式打印每個包的元信息.
|
||||
`go list`命令還可以獲取每個包完整的元信息,而不僅僅隻是導入路徑,這些元信息可以以不同格式提供給用戶。其中`-json`命令行參數表示用JSON格式打印每個包的元信息。
|
||||
|
||||
```
|
||||
$ go list -json hash
|
||||
@ -71,7 +71,7 @@ $ go list -json hash
|
||||
}
|
||||
```
|
||||
|
||||
參數 `-f` 允許用戶使用 text/template (§4.6) 的模闆語言定義輸出文本的格式. 下面的命令打印 strconv 包的依賴的包, 然後用 join 模闆函數鏈接爲一行, 用一個空格分隔:
|
||||
命令行參數`-f`則允許用戶使用text/template包(§4.6)的模闆語言定義輸出文本的格式。下面的命令將打印strconv包的依賴的包,然後用join模闆函數將結果鏈接爲一行,連接時每個結果之間用一個空格分隔:
|
||||
|
||||
{% raw %}
|
||||
```
|
||||
@ -80,7 +80,7 @@ errors math runtime unicode/utf8 unsafe
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
譯註: 上面的命令在 Windows 的命令行運行會遇到 `template: main:1: unclosed action` 的錯誤. 産生錯誤的原因是因爲命令行對里面的 `" "` 參數進行轉義了. 按照下面的方法解決轉義字符串的問題:
|
||||
譯註:上面的命令在Windows的命令行運行會遇到`template: main:1: unclosed action`的錯誤。産生這個錯誤的原因是因爲命令行對命令中的`" "`參數進行了轉義處理。可以按照下面的方法解決轉義字符串的問題:
|
||||
|
||||
{% raw %}
|
||||
```
|
||||
@ -88,7 +88,7 @@ $ go list -f "{{join .Deps \" \"}}" strconv
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
下面的命令打印 compress 子目録下所有包的依賴包列表:
|
||||
下面的命令打印compress子目録下所有包的依賴包列表:
|
||||
|
||||
{% raw %}
|
||||
```
|
||||
@ -101,7 +101,7 @@ compress/zlib -> bufio compress/flate errors fmt hash hash/adler32 io
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
譯註: Windows 下同樣有問題, 要避免轉義字符串的問題:
|
||||
譯註:Windows下有同樣有問題,要避免轉義字符串的榦擾:
|
||||
|
||||
{% raw %}
|
||||
```
|
||||
@ -109,11 +109,8 @@ $ go list -f "{{.ImportPath}} -> {{join .Imports \" \"}}" compress/...
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
go list 命令對於一次性的交互式査詢或自動化構建和測試腳本都很有幫助. 我們將在 11.2.4節 中再次使用它. 更多的信息, 包括可設置的字段和意義, 可以用 `go help list` 命令査看.
|
||||
|
||||
在本章, 我們解釋了Go工具箱除了測試命令之外的所有重要的命令. 在下一章, 我們將看到如何用 `go test` 命令去測試Go程序.
|
||||
|
||||
**練習10.4:** 創建一個工具, 根據命令行指定的參數, 報告工作區所有依賴指定包的其他包集合. 提示: 你需要運行 `go list` 命令兩次, 一次用於初始化包, 一次用於所有包. 你可能需要用 encoding/json (§4.5) 包來分析輸出的 JSON 格式的信息.
|
||||
|
||||
`go list`命令對於一次性的交互式査詢或自動化構建或測試腳本都很有幫助。我們將在11.2.4節中再次使用它。每個子命令的更多信息,包括可設置的字段和意義,可以用`go help list`命令査看。
|
||||
|
||||
在本章,我們解釋了Go語言工具中除了測試命令之外的所有重要的子命令。在下一章,我們將看到如何用`go test`命令去運行Go語言程序中的測試代碼。
|
||||
|
||||
**練習 10.4:** 創建一個工具,根據命令行指定的參數,報告工作區所有依賴指定包的其它包集合。提示:你需要運行`go list`命令兩次,一次用於初始化包,一次用於所有包。你可能需要用encoding/json(§4.5)包來分析輸出的JSON格式的信息。
|
||||
|
@ -1,11 +1,10 @@
|
||||
## 10.7. 工具
|
||||
|
||||
本章剩下的部分將討論Go語言工具箱的具體功能,包括如何下載、格式化、構建、測試和安裝Go語言編寫的程序。
|
||||
|
||||
本章剩下的部分將討論Go工具箱的特性, 包括如何 下載, 格式化, 構建, 測試 和 安裝 Go 程序.
|
||||
Go語言的工具箱集合了一繫列的功能的命令集。它可以看作是一個包管理器(類似於Linux中的apt和rpm工具),用於包的査詢、計算的包依賴關繫、從遠程版本控製繫統和下載它們等任務。它也是一個構建繫統,計算文件的依賴關繫,然後調用編譯器、滙編器和連接器構建程序,雖然它故意被設計成沒有標準的make命令那麽複雜。它也是一個單元測試和基準測試的驅動程序,我們將在第11章討論測試話題。
|
||||
|
||||
Go的工具箱集合了一繫列的功能到一個命令集. 它可以看作是一個包管理器(類似於Linux中的apt和rpm工具), 用於包的査詢, 計算的包依賴關繫, 從遠程版本控製繫統和下載它們等任務. 它也是一個構建繫統, 計算文件的依賴關繫, 然後調用編譯器, 滙編器 和 連接器 構建程序, 雖然它故意被設計成沒有標準的make命令那麽複雜. 它也是一個測試驅動程序, 我們在第11章討論測試話題.
|
||||
|
||||
Go工具箱的命令有着類似"瑞士軍刀"的風格, 帶着一打子的子命令, 有一些我們經常用到, 例如 get, run, build, 和 fmt 等. 你可以運行 `go help` 命令査看內置的溫度, 爲了査詢方便, 我們列出了最常用的命令:
|
||||
Go語言工具箱的命令有着類似“瑞士軍刀”的風格,帶着一打子的子命令,有一些我們經常用到,例如get、run、build和fmt等。你可以運行go或go help命令査看內置的幫助文檔,爲了査詢方便,我們列出了最常用的命令:
|
||||
|
||||
```
|
||||
$ go
|
||||
@ -27,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" %}
|
||||
|
||||
@ -40,5 +39,3 @@ Use "go help [command]" for more information about a command.
|
||||
{% include "./ch10-07-5.md" %}
|
||||
|
||||
{% include "./ch10-07-6.md" %}
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 第十章 包和工具
|
||||
|
||||
現在隨便一個小程序的實現都可能包含超過10000個函數. 然後作者一般隻需要考慮其中很小的一部分和做很少的設計, 因爲絶大部分代碼都是由他人編寫的, 它們通過類似包的方式被重用.
|
||||
現在隨便一個小程序的實現都可能包含超過10000個函數。然而作者一般隻需要考慮其中很小的一部分和做很少的設計,因爲絶大部分代碼都是由他人編寫的,它們通過類似包或模塊的方式被重用。
|
||||
|
||||
Go語言有超過100個的標準包, 爲大多數的程序提供了基礎構件. 在Go的社區, 有很多成熟的包被設計,共享,重用和改進, 目前已經發布了非常多的開源包, 它們可以通過 http://godoc.org 檢索. 在本章, 我們將演示如果使用已有的包和創建新的包.
|
||||
Go語言有超過100個的標準包(譯註:可以用`go list std | wc -l`命令査看標準包的具體數目),標準庫爲大多數的程序提供了必要的基礎構件。在Go的社區,有很多成熟的包被設計、共享、重用和改進,目前互聯網上已經發布了非常多的Go語音開源包,它們可以通過 http://godoc.org 檢索。在本章,我們將演示如果使用已有的包和創建新的包。
|
||||
|
||||
Go還自帶了工具箱, 里面有很多用來簡化工作區和包管理的小工具. 在本身開始的時候, 我們已經見識過如果使用工具箱自帶的工具來下載, 構件 和 運行我們的演示程序了. 在本章, 我們將看看這些工具的基本設計理論和嚐試更多的功能, 例如打印工作區中包的文檔和査詢相關的元數據等. 在下一章, 我們將探討探索包的單元測試用法.
|
||||
Go還自帶了工具箱,里面有很多用來簡化工作區和包管理的小工具。在本書開始的時候,我們已經見識過如何使用工具箱自帶的工具來下載、構件和運行我們的演示程序了。在本章,我們將看看這些工具的基本設計理論和嚐試更多的功能,例如打印工作區中包的文檔和査詢相關的元數據等。在下一章,我們將探討探索包的單元測試用法。
|
||||
|
@ -153,7 +153,7 @@ func Unmarshal(data []byte, out interface{}) (err error) {
|
||||
|
||||
生産實現不應該對任何輸入問題都用panic形式報告,而且應該報告一些錯誤相關的信息,例如出現錯誤輸入的行號和位置等。盡管如此,我們希望通過這個例子來展示類似encoding/json等包底層代碼的實現思路,以及如何使用反射機製來填充數據結構。
|
||||
|
||||
**練習 12.8:** sexpr.Unmarshal函數和json.Marshal一樣(譯註:這可能是筆誤,我覺得應該是指`json.Unmarshal`函數),都要求在解碼前輸入完整的字節slice。定義一個和json.Decoder類似的sexpr.Decoder類型,支持從一個io.Reader流解碼。脩改sexpr.Unmarshal函數,使用這個新的類型實現。
|
||||
**練習 12.8:** sexpr.Unmarshal函數和json.Unmarshal一樣,都要求在解碼前輸入完整的字節slice。定義一個和json.Decoder類似的sexpr.Decoder類型,支持從一個io.Reader流解碼。脩改sexpr.Unmarshal函數,使用這個新的類型實現。
|
||||
|
||||
**練習 12.9:** 編寫一個基於標記的API用於解碼S表達式,參考xml.Decoder(7.14)的風格。你將需要五種類型的標記:Symbol、String、Int、StartList和EndList。
|
||||
|
||||
|
@ -2,5 +2,5 @@
|
||||
|
||||
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都沒有公開反射相關的接口.
|
||||
|
||||
|
@ -56,7 +56,7 @@ fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
|
||||
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
|
||||
```
|
||||
|
||||
比較運算符`==`和`<`也可以用來比較一個命名類型的變量和另一個有相同類型的變量或有相同底層類型的值做比較。但是如果兩個值有着不同的類型,則不能直接進行比較:
|
||||
比較運算符`==`和`<`也可以用來比較一個命名類型的變量和另一個有相同類型的變量,或有着相同底層類型的未命名類型的值之間做比較。但是如果兩個值有着不同的類型,則不能直接進行比較:
|
||||
|
||||
```Go
|
||||
var c Celsius
|
||||
|
@ -34,7 +34,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
導入語句將導入的包綁定到一個短小的名字,然後通過該短小的名字就可以引用包中導出的全部內容。上面的導入聲明將允許我們以tempconv.CToF的形式來訪問gopl.io/ch2/tempconv包中的內容。在默認情況下,導入的包綁定到tempconv名字(譯註:這包聲明語句指定的名字),但是我們也可以綁定到另一個名稱,以避免名字衝突(§10.3)。
|
||||
導入語句將導入的包綁定到一個短小的名字,然後通過該短小的名字就可以引用包中導出的全部內容。上面的導入聲明將允許我們以tempconv.CToF的形式來訪問gopl.io/ch2/tempconv包中的內容。在默認情況下,導入的包綁定到tempconv名字(譯註:這包聲明語句指定的名字),但是我們也可以綁定到另一個名稱,以避免名字衝突(§10.4)。
|
||||
|
||||
cf程序將命令行輸入的一個溫度在Celsius和Fahrenheit溫度單位之間轉換:
|
||||
|
||||
|
@ -28,7 +28,7 @@ Unicode字符rune類型是和int32等價的類型,通常用於表示一個Unic
|
||||
|
||||
對於上表中前兩行的運算符,例如+運算符還有一個與賦值相結合的對應運算符+=,可以用於簡化賦值語句。
|
||||
|
||||
整數的算術運算符+、-、`*`和`/`可以適用與於整數、浮點數和複數,但是取模運算符%僅用於整數間的運算。對於不同編程語言,%取模運算的行爲可能併不相同。在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的例子:
|
||||
|
||||
|
@ -116,7 +116,7 @@ bytes包還提供了Buffer類型用於字節slice的緩存。一個Buffer開始
|
||||
```Go
|
||||
gopl.io/ch3/printints
|
||||
|
||||
// intsToString is like fmt.Sprintf(values) but adds commas.
|
||||
// intsToString is like fmt.Sprint(values) but adds commas.
|
||||
func intsToString(values []int) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('[')
|
||||
|
@ -16,7 +16,7 @@ FormatInt和FormatUint函數可以用不同的進製來格式化數字:
|
||||
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
|
||||
```
|
||||
|
||||
fmt.Printf函數的%b、%d、%u和%x等參數提供功能往往比strconv包的Format函數方便很多,特别是在需要包含附加額外信息的時候:
|
||||
fmt.Printf函數的%b、%d、%o和%x等參數提供功能往往比strconv包的Format函數方便很多,特别是在需要包含附加額外信息的時候:
|
||||
|
||||
```Go
|
||||
s := fmt.Sprintf("x=%b", x) // "x=1111011"
|
||||
|
@ -39,7 +39,7 @@ func parseIPv4(s string) IP {
|
||||
const noDelay time.Duration = 0
|
||||
const timeout = 5 * time.Minute
|
||||
fmt.Printf("%T %[1]v\n", noDelay) // "time.Duration 0"
|
||||
fmt.Printf("%T %[1]v\n", timeout) // "time.Duration 5m0s
|
||||
fmt.Printf("%T %[1]v\n", timeout) // "time.Duration 5m0s"
|
||||
fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s"
|
||||
```
|
||||
|
||||
|
@ -97,7 +97,7 @@ for _, name := range names {
|
||||
names := make([]string, 0, len(ages))
|
||||
```
|
||||
|
||||
在上面的第一個range循環中,我們隻關心map中的key,所以我們忽略了第二個循環變量。在第二個循環中,我們隻關繫names中的名字,所以我們使用“_”空白標識符來忽略第一個循環變量,也就是迭代slice時的索引。
|
||||
在上面的第一個range循環中,我們隻關心map中的key,所以我們忽略了第二個循環變量。在第二個循環中,我們隻關心names中的名字,所以我們使用“_”空白標識符來忽略第一個循環變量,也就是迭代slice時的索引。
|
||||
|
||||
map類型的零值是nil,也就是沒有引用任何哈希表。
|
||||
|
||||
@ -153,7 +153,7 @@ func equal(x, y map[string]int) bool {
|
||||
equal(map[string]int{"A": 0}, map[string]int{"B": 42})
|
||||
```
|
||||
|
||||
Go語言中併沒有提供一個set類型,但是map中的key也是不相同的,可以用map實現類似的功能。爲了説明這一點,下面的dedup程序讀取多行輸入,但是隻打印第一次出現的行。(它是1.3節中出現的dup程序的變體。)dedup程序通過map來表示所有的輸入行所對應的set集合,以確保已經在集合存在的行不會被重複打印。
|
||||
Go語言中併沒有提供一個set類型,但是map中的key也是不相同的,可以用map實現類似set的功能。爲了説明這一點,下面的dedup程序讀取多行輸入,但是隻打印第一次出現的行。(它是1.3節中出現的dup程序的變體。)dedup程序通過map來表示所有的輸入行所對應的set集合,以確保已經在集合存在的行不會被重複打印。
|
||||
|
||||
```Go
|
||||
gopl.io/ch4/dedup
|
||||
@ -178,7 +178,7 @@ func main() {
|
||||
|
||||
Go程序員將這種忽略value的map當作一個字符串集合,併非所有`map[string]bool`類型value都是無關緊要的;有一些則可能會同時包含tue和false的值。
|
||||
|
||||
有時候我們需要一個map或set的key是slice類型,但是map的key必鬚是可比較的,但是slice併不滿足這個條件。不過,我們可以通過兩個步驟繞過這個限製。第一步,定義一個輔助函數k,將slice轉爲map對應的string類型的key,確保隻有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map,在每次對map操作時先用k輔助函數將slice轉化爲string類型。
|
||||
有時候我們需要一個map或set的key是slice類型,但是map的key必鬚是可比較的類型,但是slice併不滿足這個條件。不過,我們可以通過兩個步驟繞過這個限製。第一步,定義一個輔助函數k,將slice轉爲map對應的string類型的key,確保隻有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map,在每次對map操作時先用k輔助函數將slice轉化爲string類型。
|
||||
|
||||
下面的例子演示了如何使用map來記録提交相同的字符串列表的次數。它使用了fmt.Sprintf函數將字符串列表轉換爲一個字符串以用於map的key,通過%q參數忠實地記録每個字符串元素的信息:
|
||||
|
||||
@ -191,9 +191,9 @@ func Add(list []string) { m[k(list)]++ }
|
||||
func Count(list []string) int { return m[k(list)] }
|
||||
```
|
||||
|
||||
使用同樣的技術可以處理任何不可比較的key類型,而不僅僅是slice類型。它對於想使用自定義key比較函數的時候也很有用,例如在比較字符串的時候忽略大小寫。同時,輔助函數k(x)也不一定是字符串類型,它可以返迴任何可比較的類型,例如整數、數組或結構體等。
|
||||
使用同樣的技術可以處理任何不可比較的key類型,而不僅僅是slice類型。這種技術對於想使用自定義key比較函數的時候也很有用,例如在比較字符串的時候忽略大小寫。同時,輔助函數k(x)也不一定是字符串類型,它可以返迴任何可比較的類型,例如整數、數組或結構體等。
|
||||
|
||||
這是map的另一個例子,下面的程序用於統計輸入中每個Unicode碼點出現的次數。雖然Unicode全部碼點的數量鉅大,但是出現在特點文檔中的字符併沒有多少,使用map可以用比較自然的方式來跟蹤那些見過字符次數。
|
||||
這是map的另一個例子,下面的程序用於統計輸入中每個Unicode碼點出現的次數。雖然Unicode全部碼點的數量鉅大,但是出現在特定文檔中的字符種類併沒有多少,使用map可以用比較自然的方式來跟蹤那些出現過字符的次數。
|
||||
|
||||
```Go
|
||||
gopl.io/ch4/charcount
|
||||
@ -248,11 +248,11 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
ReadRune方法執行UTF-8解碼併返迴三個值:解碼的rune字符的值,字符UTF-8編碼後的長度,和一個錯誤值。我們可預期的錯誤值隻有對應文件結尾的io.EOF。如果輸入的是無效的UTF-8編碼的字符,返迴的將是unicode.ReplacementChar無效字符,併且編碼長度是1。
|
||||
ReadRune方法執行UTF-8解碼併返迴三個值:解碼的rune字符的值,字符UTF-8編碼後的長度,和一個錯誤值。我們可預期的錯誤值隻有對應文件結尾的io.EOF。如果輸入的是無效的UTF-8編碼的字符,返迴的將是unicode.ReplacementChar表示無效字符,併且編碼長度是1。
|
||||
|
||||
charcount程序同時打印不同UTF-8編碼長度的字符數目。對此,map併不是一個合適的數據結構;因爲UTF-8編碼的長度總是從1到utf8.UTFMax(最大是4個字節),使用數組將更有效。
|
||||
|
||||
作爲一個實驗,我們用charcount程序對本身的字符進行了統計。雖然大部分是英語,但是也有一些非ASCII字符。下面是排名前10的非ASCII字符:
|
||||
作爲一個實驗,我們用charcount程序對英文版原稿的字符進行了統計。雖然大部分是英語,但是也有一些非ASCII字符。下面是排名前10的非ASCII字符:
|
||||
|
||||
![](../images/ch4-xx-01.png)
|
||||
|
||||
@ -287,7 +287,7 @@ func hasEdge(from, to string) bool {
|
||||
}
|
||||
```
|
||||
|
||||
其中addEdge函數惰性初始化map是一個慣用方式,也就是説在每個值首次作爲key是才初始化。addEdge函數顯示了如何讓map的零值也能正常工作;卽使from到to的邊不存在,graph[from][to]依然可以返迴一個有意義的結果。
|
||||
其中addEdge函數惰性初始化map是一個慣用方式,也就是説在每個值首次作爲key時才初始化。addEdge函數顯示了如何讓map的零值也能正常工作;卽使from到to的邊不存在,graph[from][to]依然可以返迴一個有意義的結果。
|
||||
|
||||
**練習 4.8:** 脩改charcount程序,使用unicode.IsLetter等相關的函數,統計字母、數字等Unicode中不同的字符類别。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 4.4.1. 結構體面值
|
||||
|
||||
結構體值可以用結構體面值表示,結構體面值可以指定每個成員的值。
|
||||
結構體值也可以用結構體面值表示,結構體面值可以指定每個成員的值。
|
||||
|
||||
```Go
|
||||
type Point struct{ X, Y int }
|
||||
@ -8,7 +8,7 @@ type Point struct{ X, Y int }
|
||||
p := Point{1, 2}
|
||||
```
|
||||
|
||||
這里有兩種形式的結構體面值語法,上面的是第一種寫法,要求以結構體成員定義的順序爲每個結構體成員指定一個面值。它要求寫代碼和讀代碼的人要記住結構體的每個成員的類型和順序,併且結構體成員有細微的調整就可能導致上述代碼不能編譯。因此,上述的語法一般隻在定義結構體的包內部使用,或者是在較小的結構體中使用,這些結構體的成員排列比較規則,比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。
|
||||
這里有兩種形式的結構體面值語法,上面的是第一種寫法,要求以結構體成員定義的順序爲每個結構體成員指定一個面值。它要求寫代碼和讀代碼的人要記住結構體的每個成員的類型和順序,不過結構體成員有細微的調整就可能導致上述代碼不能編譯。因此,上述的語法一般隻在定義結構體的包內部使用,或者是在較小的結構體中使用,這些結構體的成員排列比較規則,比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。
|
||||
|
||||
其實更常用的是第二種寫法,以成員名字和相應的值來初始化,可以包含部分或全部的成員,如1.4節的Lissajous程序的寫法:
|
||||
|
||||
@ -50,7 +50,7 @@ func Bonus(e *Employee, percent int) int {
|
||||
}
|
||||
```
|
||||
|
||||
如果要在函數內部脩改結構體成員的話,用指針傳入是必鬚的;因爲在Go語言中,所有的函數參數都是值拷貝出入的,函數參數將不再是函數調用時的原始變量。
|
||||
如果要在函數內部脩改結構體成員的話,用指針傳入是必鬚的;因爲在Go語言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。
|
||||
|
||||
```Go
|
||||
func AwardAnnualRaise(e *Employee) {
|
||||
@ -72,5 +72,3 @@ pp := new(Point)
|
||||
```
|
||||
|
||||
不過&Point{1, 2}寫法可以直接在表達式中使用,比如一個函數調用。
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 4.4.3. 結構體嵌入和匿名成員
|
||||
|
||||
在本節中,我們將看到如果使用Go語言提供的不同尋常的結構體嵌入機製讓一個命名的結構體包含另一個結構體類型的匿名成員,這樣就可以通過簡單的點運算符x.f來訪問匿名成員鏈中嵌套的x.d.e.f成員。
|
||||
在本節中,我們將看到如何使用Go語言提供的不同尋常的結構體嵌入機製讓一個命名的結構體包含另一個結構體類型的匿名成員,這樣就可以通過簡單的點運算符x.f來訪問匿名成員鏈中嵌套的x.d.e.f成員。
|
||||
|
||||
考慮一個二維的繪圖程序,提供了一個各種圖形的庫,例如矩形、橢圓形、星形和輪形等幾何形狀。這里是其中兩個的定義:
|
||||
|
||||
@ -52,7 +52,7 @@ w.Circle.Radius = 5
|
||||
w.Spokes = 20
|
||||
```
|
||||
|
||||
Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字;這類成員就叫匿名成員。匿名成員的數據類型必鬚是命名的類型或指向一個命名的類型的指針。下面的代碼中,Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入了Circle結構體,同時Circle類型被嵌入了Wheel結構體。
|
||||
Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字;這類成員就叫匿名成員。匿名成員的數據類型必鬚是命名的類型或指向一個命名的類型的指針。下面的代碼中,Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入到了Circle結構體,同時Circle類型被嵌入到了Wheel結構體。
|
||||
|
||||
```Go
|
||||
type Circle struct {
|
||||
@ -119,9 +119,8 @@ fmt.Printf("%#v\n", w)
|
||||
w.X = 8 // equivalent to w.circle.point.X = 8
|
||||
```
|
||||
|
||||
但是在包外部,因爲circle和point沒有導出不能訪問它們的成員,因此簡短語法也是禁止的。
|
||||
但是在包外部,因爲circle和point沒有導出不能訪問它們的成員,因此簡短的匿名成員訪問語法也是禁止的。
|
||||
|
||||
到目前未知,我們看到匿名成員特性隻是對訪問嵌套成員的點運算符提供了簡短的語法醣。稍後,我們將會看到匿名成員併不要求是結構體類型;其實任何命令的類型都可以作爲結構體的匿名成員。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢?
|
||||
|
||||
答案是匿名類型的方法集。簡短的點運算符語法可以用於選擇匿名成員嵌套的成員,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成員,而且也獲得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。組合是Go語言中面向對象編程的覈心,我們將在6.3節中專門討論。
|
||||
|
||||
答案是匿名類型的方法集。簡短的點運算符語法可以用於選擇匿名成員嵌套的成員,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成員,而且也獲得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。組合是Go語言中面向對象編程的核心,我們將在6.3節中專門討論。
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 4.4. 結構體
|
||||
|
||||
結構體是一種聚合的數據類型,由零個或多個任意類型的值聚合成的實體。每個值稱爲結構體的成員。是用結構體的經典案例處理公司的員工信息,每個員工信息包含一個唯一的員工編號、員工的名字、家庭住址、出生日期、工作崗位、薪資、上級領導等等。所有的這些成員都需要綁定到一個實體,可以作爲一個整體單元被複製,作爲函數的參數或返迴值,或者是被存儲到數組中,等等。
|
||||
結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每個值稱爲結構體的成員。用結構體的經典案例處理公司的員工信息,每個員工信息包含一個唯一的員工編號、員工的名字、家庭住址、出生日期、工作崗位、薪資、上級領導等等。所有的這些信息都需要綁定到一個實體中,可以作爲一個整體單元被複製,作爲函數的參數或返迴值,或者是被存儲到數組中,等等。
|
||||
|
||||
下面兩個語句分别聲明了一個叫Employee的結構體類型,併且聲明了一個Employee類型的變量dilbert:
|
||||
下面兩個語句聲明了一個叫Employee的命名的結構體類型,併且聲明了一個Employee類型的變量dilbert:
|
||||
|
||||
```Go
|
||||
type Employee struct {
|
||||
@ -18,7 +18,7 @@ type Employee struct {
|
||||
var dilbert Employee
|
||||
```
|
||||
|
||||
dilbert結構體變量的成員可以通過點操作符訪問,比如dilbert.Name和dilbert.DoB。因爲dilbert是一個變量,它所有的成員也同樣是變量,我們可以對每個成員賦值:
|
||||
dilbert結構體變量的成員可以通過點操作符訪問,比如dilbert.Name和dilbert.DoB。因爲dilbert是一個變量,它所有的成員也同樣是變量,我們可以直接對每個成員賦值:
|
||||
|
||||
```Go
|
||||
dilbert.Salary -= 5000 // demoted, for writing too few lines of code
|
||||
@ -44,7 +44,7 @@ employeeOfTheMonth.Position += " (proactive team player)"
|
||||
(*employeeOfTheMonth).Position += " (proactive team player)"
|
||||
```
|
||||
|
||||
EmployeeByID函數將根據給定的員工ID返迴對應的員工信息結構體的指針。我們可以使用點操作符來訪問它里面的成員:
|
||||
下面的EmployeeByID函數將根據給定的員工ID返迴對應的員工信息結構體的指針。我們可以使用點操作符來訪問它里面的成員:
|
||||
|
||||
```Go
|
||||
func EmployeeByID(id int) *Employee { /* ... */ }
|
||||
@ -55,7 +55,7 @@ id := dilbert.ID
|
||||
EmployeeByID(id).Salary = 0 // fired for... no real reason
|
||||
```
|
||||
|
||||
後面的語句通過EmployeeByID返迴的結構體指針更新了Employee結構體的成員。如果將EmployeeByID函數的返迴值從`*Employee`指針類型改爲Employee值類型,那麽更新語句將不能編譯通過,因爲在賦值語句的左邊併不確定是一個變量。
|
||||
後面的語句通過EmployeeByID返迴的結構體指針更新了Employee結構體的成員。如果將EmployeeByID函數的返迴值從`*Employee`指針類型改爲Employee值類型,那麽更新語句將不能編譯通過,因爲在賦值語句的左邊併不確定是一個變量(譯註:調用函數返迴的是值,併不是一個可取地址的變量)。
|
||||
|
||||
通常一行對應一個結構體成員,成員的名字在前類型在後,不過如果相鄰的成員類型如果相同的話可以被合併到一行,就像下面的Name和Address成員那樣:
|
||||
|
||||
@ -70,13 +70,13 @@ type Employee struct {
|
||||
}
|
||||
```
|
||||
|
||||
結構體成員的輸入順序也有重要的意義。我們也可以將Position成員合併(因爲也是字符串類型),或者是交換Name和Address出現的先後順序,那樣的話就是定義了不同的結構體類型。通常,我們隻是將相關的成員合併到一起。
|
||||
結構體成員的輸入順序也有重要的意義。我們也可以將Position成員合併(因爲也是字符串類型),或者是交換Name和Address出現的先後順序,那樣的話就是定義了不同的結構體類型。通常,我們隻是將相關的成員寫到一起。
|
||||
|
||||
如果結構體成員名字是以大寫字母開頭的,那麽該成員就是導出的;這是Go語言導出規則決定的。一個結構體可能同時包含導出和未導出的成員。
|
||||
|
||||
結構體類型往往是冗長的,因爲它的每個成員可能都會占一行。雖然我們每次都可以重寫整個結構體成員,但是重複會令人厭煩。因此,完整的結構體寫法通常隻在類型聲明語句的地方出現,就像Employee類型聲明語句那樣。
|
||||
|
||||
一個命名爲S的結構體類型將不能再包含S類型的成員:一個聚合的值不能包含它自身。(該限製同樣適應於數組。)但是S類型的結構體可以包含`*S`指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。在下面的代碼中,我們使用一個二叉樹來實現一個插入排序:
|
||||
一個命名爲S的結構體類型將不能再包含S類型的成員:因爲一個聚合的值不能包含它自身。(該限製同樣適應於數組。)但是S類型的結構體可以包含`*S`指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。在下面的代碼中,我們使用一個二叉樹來實現一個插入排序:
|
||||
|
||||
```Go
|
||||
gopl.io/ch4/treesort
|
||||
@ -114,15 +114,15 @@ func add(t *tree, value int) *tree {
|
||||
return t
|
||||
}
|
||||
if value < t.value {
|
||||
t.left = add(t.left, value)
|
||||
t.left = add(t.left, value)
|
||||
} else {
|
||||
t.right = add(t.right, value)
|
||||
t.right = add(t.right, value)
|
||||
}
|
||||
return t
|
||||
}
|
||||
```
|
||||
|
||||
結構體類型的零值是每個成員都對是零值。通常會將零值作爲最合理的默認值。例如,在bytes.Buffer類型,結構體初始值就是一個隨時可用的空緩存,還有在第9章將會講到的sync.Mutex的零值也是有效的未鎖狀態。有時候這種零值可用的特性是自然獲得的,但是也有些類型需要一些額外的工作。
|
||||
結構體類型的零值是每個成員都對是零值。通常會將零值作爲最合理的默認值。例如,對於bytes.Buffer類型,結構體初始值就是一個隨時可用的空緩存,還有在第9章將會講到的sync.Mutex的零值也是有效的未鎖定狀態。有時候這種零值可用的特性是自然獲得的,但是也有些類型需要一些額外的工作。
|
||||
|
||||
如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小爲0,也不包含任何信息,但是有時候依然是有價值的。有些Go語言程序員用map帶模擬set數據結構時,用它來代替map中布爾類型的value,隻是強調key的重要性,但是因爲節約的空間有限,而且語法比較複雜,所有我們通常避免避免這樣的用法。
|
||||
|
||||
@ -140,4 +140,3 @@ if _, ok := seen[s]; !ok {
|
||||
{% include "./ch4-04-2.md" %}
|
||||
|
||||
{% include "./ch4-04-3.md" %}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
## 4.5. JSON
|
||||
|
||||
JavaScript對象表示法(JSON)是一種用於發送和接收結構化信息的標準協議。JSON併不是唯一標準協議。 XML(§7.14)、ASN.1和Google的Protocol Buffers都是類似的協議,併且有各自的特色,但是由於簡潔性、可讀性和流行程度等原因,JSON是應用最廣泛的一個。
|
||||
JavaScript對象表示法(JSON)是一種用於發送和接收結構化信息的標準協議。在類似的協議中,JSON併不是唯一的一個標準協議。 XML(§7.14)、ASN.1和Google的Protocol Buffers都是類似的協議,併且有各自的特色,但是由於簡潔性、可讀性和流行程度等原因,JSON是應用最廣泛的一個。
|
||||
|
||||
Go語言對於這些標準格式的編碼和解碼都有良好的支持,由標準庫中的encoding/json、encoding/xml、encoding/asn1等包提供(譯註:Protocol Buffers的支持由 github.com/golang/protobuf 包提供),併且這類包都有着相似的API接口。本節,我們將對重要的encoding/json包的用法做個概述。
|
||||
Go語言對於這些標準格式的編碼和解碼都有良好的支持,由標準庫中的encoding/json、encoding/xml、encoding/asn1等包提供支持(譯註:Protocol Buffers的支持由 github.com/golang/protobuf 包提供),併且這類包都有着相似的API接口。本節,我們將對重要的encoding/json包的用法做個概述。
|
||||
|
||||
JSON是對JavaScript中各種值——字符串、數字、布爾值和對象——Unicode本文編碼。它可以用有效可讀的方式表示第三章的基礎數據類型和本章的數組、slice、結構體和map等聚合數據類型。
|
||||
JSON是對JavaScript中各種類型的值——字符串、數字、布爾值和對象——Unicode本文編碼。它可以用有效可讀的方式表示第三章的基礎數據類型和本章的數組、slice、結構體和map等聚合數據類型。
|
||||
|
||||
基本的JSON類型有數字(十進製或科學記數法)、布爾值(true或false)、字符串,其中字符串是以雙引號包含的Unicode字符序列,支持和Go語言類似的反斜槓轉義特性,不過JSON使用的是\Uhhhh轉義數字來表示一個UTF-16編碼,而不是Go語言的rune類型。
|
||||
基本的JSON類型有數字(十進製或科學記數法)、布爾值(true或false)、字符串,其中字符串是以雙引號包含的Unicode字符序列,支持和Go語言類似的反斜槓轉義特性,不過JSON使用的是\Uhhhh轉義數字來表示一個UTF-16編碼(譯註:UTF-16和UTF-8一樣是一種變長的編碼,有些Unicode碼點較大的字符需要用4個字節表示;而且UTF-16還有大端和小端的問題),而不是Go語言的rune類型。
|
||||
|
||||
這些基礎類型可以通過JSON的數組和對象類型進行遞歸組合。一個JSON數組是一個有序的值序列,寫在一個方括號中併以逗號分隔;一個JSON數組可以用於編碼Go語言的數組和slice。一個JSON對象是一個字符串到值的映射,寫成以繫列的name:value對形式,用花括號包含併以逗號分隔;JSON的對象類型可以用於編碼Go語言的map類型(key類型是字符串)和結構體。例如:
|
||||
|
||||
@ -20,7 +20,7 @@ object {"year": 1980,
|
||||
"medals": ["gold", "silver", "bronze"]}
|
||||
```
|
||||
|
||||
考慮一個應用程序,該程序負責收集各種電影評論併提供反饋功能。它的Movie數據類型和一個典型的表示電影的值列表如下所示。(其中結構體聲明中,Year和Color成員後面的字符串面值是結構體成員Tag;我們稍後會解釋它的作用。)
|
||||
考慮一個應用程序,該程序負責收集各種電影評論併提供反饋功能。它的Movie數據類型和一個典型的表示電影的值列表如下所示。(在結構體聲明中,Year和Color成員後面的字符串面值是結構體成員Tag;我們稍後會解釋它的作用。)
|
||||
|
||||
```Go
|
||||
gopl.io/ch4/movie
|
||||
@ -53,7 +53,7 @@ if err != nil {
|
||||
fmt.Printf("%s\n", data)
|
||||
```
|
||||
|
||||
Marshal函數生成一個字節slice,包含很長的字符串,併且沒有空白縮進;我們將它摺行以便於顯示:
|
||||
Marshal函數返還一個編碼後的字節slice,包含很長的字符串,併且沒有空白縮進;我們將它摺行以便於顯示:
|
||||
|
||||
```
|
||||
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
|
||||
@ -62,7 +62,7 @@ tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
|
||||
Actors":["Steve McQueen","Jacqueline Bisset"]}]
|
||||
```
|
||||
|
||||
這種緊湊的表示形式雖然包含了全部的信息,但是很難閲讀。爲了生成便於閲讀的格式,另一個json.MarshalIndent函數將産生整齊縮進的輸出。有兩個額外的字符串參數用於表示每一行輸出的前綴和每一個層級的縮進:
|
||||
這種緊湊的表示形式雖然包含了全部的信息,但是很難閲讀。爲了生成便於閲讀的格式,另一個json.MarshalIndent函數將産生整齊縮進的輸出。該函數有兩個額外的字符串參數用於表示每一行輸出的前綴和每一個層級的縮進:
|
||||
|
||||
```Go
|
||||
data, err := json.MarshalIndent(movies, "", " ")
|
||||
@ -72,7 +72,7 @@ if err != nil {
|
||||
fmt.Printf("%s\n", data)
|
||||
```
|
||||
|
||||
上面的代碼將産生這樣的輸出:
|
||||
上面的代碼將産生這樣的輸出(譯註:在最後一個成員或元素後面併沒有逗號分隔符):
|
||||
|
||||
```Json
|
||||
[
|
||||
@ -106,16 +106,16 @@ fmt.Printf("%s\n", data)
|
||||
|
||||
在編碼時,默認使用Go語言結構體的成員名字作爲JSON的對象(通過reflect反射技術,我們將在12.6節討論)。隻有導出的結構體成員才會被編碼,這也就是我們爲什麽選擇用大寫字母開頭的成員名稱。
|
||||
|
||||
細心的讀者可能已經註意到,其中Year名字的成員在編碼後變成了released,還有Color長遠編碼後變成了小寫字母開頭的color。這是因爲構體成員Tag所導致的。一個構體成員Tag是和在編譯階段關聯到該成員的元信息字符串:
|
||||
細心的讀者可能已經註意到,其中Year名字的成員在編碼後變成了released,還有Color成員編碼後變成了小寫字母開頭的color。這是因爲構體成員Tag所導致的。一個構體成員Tag是和在編譯階段關聯到該成員的元信息字符串:
|
||||
|
||||
```
|
||||
Year int `json:"released"`
|
||||
Color bool `json:"color,omitempty"`
|
||||
```
|
||||
|
||||
結構體的成員Tag可以是任意的字符串面值,但是通常是一繫列用空格分隔的key:"value"鍵值對序列;因爲值中含義雙引號字符,因此成員Tag一般用原生字符串面值的形式書寫。json開頭鍵對應的值用於控製encoding/json包的編碼和解碼的行爲,併且encoding/...下面其它的包也遵循這個約定。成員Tag中json對應值的第一部分用於指定JSON對象的名字,比如將Go語言中的TotalCount成員對應到JSON中的total_count對象。Color成員的Tag還帶了一個額外的omitempty選項,表示當Go語言結構體成員爲空或零值時不生成JSON對象(這里false爲零值)。果然,Casablanca是一個黑白電影,併沒有輸出Color成員。
|
||||
結構體的成員Tag可以是任意的字符串面值,但是通常是一繫列用空格分隔的key:"value"鍵值對序列;因爲值中含義雙引號字符,因此成員Tag一般用原生字符串面值的形式書寫。json開頭鍵名對應的值用於控製encoding/json包的編碼和解碼的行爲,併且encoding/...下面其它的包也遵循這個約定。成員Tag中json對應值的第一部分用於指定JSON對象的名字,比如將Go語言中的TotalCount成員對應到JSON中的total_count對象。Color成員的Tag還帶了一個額外的omitempty選項,表示當Go語言結構體成員爲空或零值時不生成JSON對象(這里false爲零值)。果然,Casablanca是一個黑白電影,併沒有輸出Color成員。
|
||||
|
||||
編碼的逆操作是解碼,對應將JSON數據解碼爲GO語言的數據結構,Go語言中一般叫unmarshaling,通過json.Unmarshal函數完成。下面的代碼將JSON格式的電影數據解碼爲一個結構體slice,結構體中隻有Title成員。通過定義合適的Go語言數據結構,我們可以選擇性地解碼JSON中感興趣的成員。當Unmarshal返迴,slice將被隻含有Title信息值填充,其它JSON成員將被忽略。
|
||||
編碼的逆操作是解碼,對應將JSON數據解碼爲Go語言的數據結構,Go語言中一般叫unmarshaling,通過json.Unmarshal函數完成。下面的代碼將JSON格式的電影數據解碼爲一個結構體slice,結構體中隻有Title成員。通過定義合適的Go語言數據結構,我們可以選擇性地解碼JSON中感興趣的成員。當Unmarshal函數調用返迴,slice將被隻含有Title信息值填充,其它JSON成員將被忽略。
|
||||
|
||||
```Go
|
||||
var titles []struct{ Title string }
|
||||
@ -125,7 +125,7 @@ if err := json.Unmarshal(data, &titles); err != nil {
|
||||
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"
|
||||
```
|
||||
|
||||
許多web服務都提供JSON接口,通過HTTP接口發送JSON格式請求併返迴JSON格式的信息。爲了説明這一點,我們通過Github的issue査詢服務。首先,我們要定義合適的類型和常量:
|
||||
許多web服務都提供JSON接口,通過HTTP接口發送JSON格式請求併返迴JSON格式的信息。爲了説明這一點,我們通過Github的issue査詢服務來演示類似的用法。首先,我們要定義合適的類型和常量:
|
||||
|
||||
```Go
|
||||
gopl.io/ch4/github
|
||||
@ -199,7 +199,7 @@ func SearchIssues(terms []string) (*IssuesSearchResult, error) {
|
||||
}
|
||||
```
|
||||
|
||||
在早些的例子中,我們使用了json.Unmarshal函數來將JSON格式的字符串解碼爲字節slice。但是這個例子中,我們使用了基於流式的解碼器json.Decoder,它可以從一個流解碼JSON數據,盡管這不是必鬚的。如您所料,還有一個針對輸出流的json.Encoder編碼對象。
|
||||
在早些的例子中,我們使用了json.Unmarshal函數來將JSON格式的字符串解碼爲字節slice。但是這個例子中,我們使用了基於流式的解碼器json.Decoder,它可以從一個輸入流解碼JSON數據,盡管這不是必鬚的。如您所料,還有一個針對輸出流的json.Encoder編碼對象。
|
||||
|
||||
我們調用Decode方法來填充變量。這里有多種方法可以格式化結構。下面是最簡單的一種,以一個固定寬度打印每個issue,但是在下一節我們將看到如果利用模闆來輸出複雜的格式。
|
||||
|
||||
@ -260,5 +260,3 @@ GitHub的Web服務接口 https://developer.github.com/v3/ 包含了更多的特
|
||||
**練習 4.12:** 流行的web漫畵服務xkcd也提供了JSON接口。例如,一個 https://xkcd.com/571/info.0.json 請求將返迴一個很多人喜愛的571編號的詳細描述。下載每個鏈接(隻下載一次)然後創建一個離線索引。編寫一個xkcd工具,使用這些離線索引,打印和命令行輸入的檢索詞相匹配的漫畵的URL。
|
||||
|
||||
**練習 4.13:** 使用開放電影數據庫的JSON服務接口,允許你檢索和下載 https://omdbapi.com/ 上電影的名字和對應的海報圖像。編寫一個poster工具,通過命令行輸入的電影名字,下載對應的海報。
|
||||
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 4.6. 文本和HTML模闆
|
||||
|
||||
前面的例子,隻是最簡單的格式,使用Printf是完全足夠的。但是有時候會需要複雜的打印格式,這時候一般需要將格式化代碼分離出來以便更安全地脩改。這寫功能是由text/template和html/template等模闆包提供的,它們提供了一個用變量值填充到一個文本或HTML格式的模闆的機製。
|
||||
前面的例子,隻是最簡單的格式化,使用Printf是完全足夠的。但是有時候會需要複雜的打印格式,這時候一般需要將格式化代碼分離出來以便更安全地脩改。這寫功能是由text/template和html/template等模闆包提供的,它們提供了一個將變量值填充到一個文本或HTML格式的模闆的機製。
|
||||
|
||||
一個模闆是一個字符串或一個文件,里面包含了一個或多個由雙花括號包含的action對象。大部分的字符串隻是按面值打印,但是對於actions部分將觸發其它的行爲。買個actions包好了一個用模闆語言書寫的表達式,一個雖然簡短但是可以輸出複雜的打印值,模闆語言包含通過選擇結構體的成員、調用函數或方法、表達式控製流if-else語句和range循環語句,還有其它實例化模闆等諸多特性。下面是一個簡單的模闆字符串:
|
||||
一個模闆是一個字符串或一個文件,里面包含了一個或多個由雙花括號包含的`{{action}}`對象。大部分的字符串隻是按面值打印,但是對於actions部分將觸發其它的行爲。每個actions都包含了一個用模闆語言書寫的表達式,一個action雖然簡短但是可以輸出複雜的打印值,模闆語言包含通過選擇結構體的成員、調用函數或方法、表達式控製流if-else語句和range循環語句,還有其它實例化模闆等諸多特性。下面是一個簡單的模闆字符串:
|
||||
|
||||
{% raw %}
|
||||
|
||||
@ -20,7 +20,7 @@ Age: {{.CreatedAt | daysAgo}} days
|
||||
|
||||
{% endraw %}
|
||||
|
||||
這個模闆先打印匹配到的issue總數,然後打印每個issue的編號、創建用戶、標題還有存在的時間。每一個action,都有一個當前值的概念,對應點操作符,寫作“.”。當前值“.”最初被初始化爲調用模闆是的參數,在當前例子中對應github.IssuesSearchResult類型的變量。模闆中`{{.TotalCount}}`對應action將展開爲結構體中TotalCount成員以默認的方式打印的值。模闆中`{{range .Items}}`和`{{end}}`對應一個循環action,因此它們直接的內容可能會被展開多次,循環每次迭代的當前值對應當前的Items元素的值。
|
||||
這個模闆先打印匹配到的issue總數,然後打印每個issue的編號、創建用戶、標題還有存在的時間。對於每一個action,都有一個當前值的概念,對應點操作符,寫作“.”。當前值“.”最初被初始化爲調用模闆是的參數,在當前例子中對應github.IssuesSearchResult類型的變量。模闆中`{{.TotalCount}}`對應action將展開爲結構體中TotalCount成員以默認的方式打印的值。模闆中`{{range .Items}}`和`{{end}}`對應一個循環action,因此它們直接的內容可能會被展開多次,循環每次迭代的當前值對應當前的Items元素的值。
|
||||
|
||||
在一個action中,`|`操作符表示將前一個表達式的結果作爲後一個函數的輸入,類似於UNIX中管道的概念。在Title這一行的action中,第二個操作是一個printf函數,是一個基於fmt.Sprintf實現的內置函數,所有模闆都可以直接使用。對於Age部分,第二個動作是一個叫daysAgo的函數,通過time.Since函數將CreatedAt成員轉換爲過去的時間長度:
|
||||
|
||||
@ -43,7 +43,7 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
因爲模闆通常在編譯時就測試好了,如果模闆解析失敗將是一個致命的錯誤。template.Must輔助函數可以簡化這個致命錯誤的處理:它接受一個模闆和一個error類型的參數,檢測error是否爲nil(如果不是則發出panic異常),然後返迴傳入的模闆。我們將在5.9節再討論這個話題。
|
||||
因爲模闆通常在編譯時就測試好了,如果模闆解析失敗將是一個致命的錯誤。template.Must輔助函數可以簡化這個致命錯誤的處理:它接受一個模闆和一個error類型的參數,檢測error是否爲nil(如果不是nil則發出panic異常),然後返迴傳入的模闆。我們將在5.9節再討論這個話題。
|
||||
|
||||
一旦模闆已經創建、註冊了daysAgo函數、併通過分析和檢測,我們就可以使用github.IssuesSearchResult作爲輸入源、os.Stdout作爲輸出源來執行模闆:
|
||||
|
||||
@ -83,7 +83,7 @@ Age: 695 days
|
||||
...
|
||||
```
|
||||
|
||||
現在讓我們轉到html/template模闆包。它使用和text/template包相同的API和模闆語言,但是增加了一個將字符串自動轉義,以避免輸入字符串和HTML、JavaScript、CSS或URL語法産生衝突的問題。這個特性可以避免一些長期存在的安全問題,比如通過生成HTML註入攻擊,通過構造一個含有惡意代碼的問題標題,這些都可能讓模闆輸出錯誤的輸出,從而讓他們控製頁面。
|
||||
現在讓我們轉到html/template模闆包。它使用和text/template包相同的API和模闆語言,但是增加了一個將字符串自動轉義特性,這可以避免輸入字符串和HTML、JavaScript、CSS或URL語法産生衝突的問題。這個特性還可以避免一些長期存在的安全問題,比如通過生成HTML註入攻擊,通過構造一個含有惡意代碼的問題標題,這些都可能讓模闆輸出錯誤的輸出,從而讓他們控製頁面。
|
||||
|
||||
下面的模闆以HTML格式輸出issue列表。註意import語句的不同:
|
||||
|
||||
@ -119,7 +119,7 @@ var issueList = template.Must(template.New("issuelist").Parse(`
|
||||
|
||||
下面的命令將在新的模闆上執行一個稍微不同的査詢:
|
||||
|
||||
```Go
|
||||
```
|
||||
$ go build gopl.io/ch4/issueshtml
|
||||
$ ./issueshtml repo:golang/go commenter:gopherbot json encoder >issues.html
|
||||
```
|
||||
@ -128,15 +128,15 @@ $ ./issueshtml repo:golang/go commenter:gopherbot json encoder >issues.html
|
||||
|
||||
![](../images/ch4-04.png)
|
||||
|
||||
圖4.4中的沒有問題會對HTML格式産生衝突,但是我們馬上將看到標題中含有`&`和`<`字符的issue。下面的命令選擇了兩個這樣的issue:
|
||||
圖4.4中issue沒有包含會對HTML格式産生衝突的特殊字符,但是我們馬上將看到標題中含有`&`和`<`字符的issue。下面的命令選擇了兩個這樣的issue:
|
||||
|
||||
```
|
||||
$ ./issueshtml repo:golang/go 3133 10535 >issues2.html
|
||||
```
|
||||
|
||||
圖4.5顯示了該査詢的結果。註意,html/template包已經自動將特殊字符轉義,我們依然可以看到正確的字面值。如果我們使用text/template包的話,這2個issue將會産生錯誤,其中“<”四個字符將會被當作小於字符“<”處理,同時“<link>”字符串將會被當作一個鏈接元素處理,它們都會導致HTML文檔結構的改變,從而導致有未知的風險。
|
||||
圖4.5顯示了該査詢的結果。註意,html/template包已經自動將特殊字符轉義,因此我們依然可以看到正確的字面值。如果我們使用text/template包的話,這2個issue將會産生錯誤,其中“&lt;”四個字符將會被當作小於字符“<”處理,同時“<link>”字符串將會被當作一個鏈接元素處理,它們都會導致HTML文檔結構的改變,從而導致有未知的風險。
|
||||
|
||||
我們也可以通過對信任的HTML字符串使用template.HTML類型來抑製這種自動轉義的行爲。還有很多采用類型命名的字符串類型對應信任的JavaScript、CSS和URL。下面的程序演示了兩個使用不同類型的相同字符串産生的不同結果:A是一個普通字符串,B是一個信任的template.HTML字符串類型。
|
||||
我們也可以通過對信任的HTML字符串使用template.HTML類型來抑製這種自動轉義的行爲。還有很多采用類型命名的字符串類型分别對應信任的JavaScript、CSS和URL。下面的程序演示了兩個使用不同類型的相同字符串産生的不同結果:A是一個普通字符串,B是一個信任的template.HTML字符串類型。
|
||||
|
||||
![](../images/ch4-05.png)
|
||||
|
||||
@ -174,5 +174,3 @@ $ go doc html/template
|
||||
```
|
||||
|
||||
**練習 4.14:** 創建一個web服務器,査詢一次GitHub,然後生成BUG報告、里程碑和對應的用戶信息。
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
fmt.Println(f(3)) // "-3"
|
||||
fmt.Printf("%T\n", f) // "func(int) int"
|
||||
|
||||
f = product // 編譯錯誤: can't assign f(int, int) int to f(int) int
|
||||
f = product // compile error: can't assign func(int, int) int to func(int) int
|
||||
```
|
||||
|
||||
函數類型的零值是nil。調用值爲nil的函數值會引起panic錯誤:
|
||||
|
@ -1,3 +1,58 @@
|
||||
### 5.6.1. 警告:捕獲迭代變量
|
||||
|
||||
TODO
|
||||
本節,將介紹Go詞法作用域的一個陷阱。請務必仔細的閲讀,弄清楚發生問題的原因。卽使是經驗豐富的程序員也會在這個問題上犯錯誤。
|
||||
|
||||
考慮這個樣一個問題:你被要求首先創建一些目録,再將目録刪除。在下面的例子中我們用函數值來完成刪除操作。下面的示例代碼需要引入os包。爲了使代碼簡單,我們忽略了所有的異常處理。
|
||||
|
||||
```Go
|
||||
var rmdirs []func()
|
||||
for _, d := range tempDirs() {
|
||||
dir := d // NOTE: necessary!
|
||||
os.MkdirAll(dir, 0755) // creates parent directories too
|
||||
rmdirs = append(rmdirs, func() {
|
||||
os.RemoveAll(dir)
|
||||
})
|
||||
}
|
||||
// ...do some work…
|
||||
for _, rmdir := range rmdirs {
|
||||
rmdir() // clean up
|
||||
}
|
||||
```
|
||||
|
||||
你可能會感到睏惑,爲什麽要在循環體中用循環變量d賦值一個新的局部變量,而不是像下面的代碼一樣直接使用循環變量dir。需要註意,下面的代碼是錯誤的。
|
||||
|
||||
```go
|
||||
var rmdirs []func()
|
||||
for _, dir := range tempDirs() {
|
||||
os.MkdirAll(dir, 0755)
|
||||
rmdirs = append(rmdirs, func() {
|
||||
os.RemoveAll(dir) // NOTE: incorrect!
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
問題的原因在於循環變量的作用域。在上面的程序中,for循環語句引入了新的詞法塊,循環變量dir在這個詞法塊中被聲明。在該循環中生成的所有函數值都共享相同的循環變量。需要註意,函數值中記録的是循環變量的內存地址,而不是循環變量某一時刻的值。以dir爲例,後續的迭代會不斷更新dir的值,當刪除操作執行時,for循環已完成,dir中存儲的值等於最後一次迭代的值。這意味着,每次對os.RemoveAll的調用刪除的都是相同的目録。
|
||||
|
||||
通常,爲了解決這個問題,我們會引入一個與循環變量同名的局部變量,作爲循環變量的副本。比如下面的變量dir,雖然這看起來很奇怪,但卻很有用。
|
||||
|
||||
```Go
|
||||
for _, dir := range tempDirs() {
|
||||
dir := dir // declares inner dir, initialized to outer dir
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
這個問題不僅存在基於range的循環,在下面的例子中,對循環變量i的使用也存在同樣的問題:
|
||||
|
||||
```Go
|
||||
var rmdirs []func()
|
||||
dirs := tempDirs()
|
||||
for i := 0; i < len(dirs); i++ {
|
||||
os.MkdirAll(dirs[i], 0755) // OK
|
||||
rmdirs = append(rmdirs, func() {
|
||||
os.RemoveAll(dirs[i]) // NOTE: incorrect!
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
如果你使用go語句(第八章)或者defer語句(5.8節)會經常遇到此類問題。這不是go或defer本身導致的,而是因爲它們都會等待循環結束後,再執行函數值。
|
||||
|
244
ch5/ch5-06.md
@ -1,5 +1,247 @@
|
||||
## 5.6. 匿名函數
|
||||
|
||||
TODO
|
||||
擁有函數名的函數隻能在包級語法塊中被聲明,通過函數字面量(function literal),我們可繞過這一限製,在任何表達式中表示一個函數值。函數字面量的語法和函數聲明相似,區别在於func關鍵字後沒有函數名。函數值字面量是一種表達式,它的值被成爲匿名函數(anonymous function)。
|
||||
|
||||
函數字面量允許我們在使用時函數時,再定義它。通過這種技巧,我們可以改寫之前對strings.Map的調用:
|
||||
|
||||
```Go
|
||||
strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
|
||||
```
|
||||
|
||||
更爲重要的是,通過這種方式定義的函數可以訪問完整的詞法環境(lexical environment),這意味着在函數中定義的內部函數可以引用該函數的變量,如下例所示:
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/squares
|
||||
// squares返迴一個匿名函數。
|
||||
// 該匿名函數每次被調用時都會返迴下一個數的平方。
|
||||
func squares() func() int {
|
||||
var x int
|
||||
return func() int {
|
||||
x++
|
||||
return x * x
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
f := squares()
|
||||
fmt.Println(f()) // "1"
|
||||
fmt.Println(f()) // "4"
|
||||
fmt.Println(f()) // "9"
|
||||
fmt.Println(f()) // "16"
|
||||
}
|
||||
```
|
||||
|
||||
函數squares返迴另一個類型爲 func() int 的函數。對squares的一次調用會生成一個局部變量x併返迴一個匿名函數。每次調用時匿名函數時,該函數都會先使x的值加1,再返迴x的平方。第二次調用squares時,會生成第二個x變量,併返迴一個新的匿名函數。新匿名函數操作的是第二個x變量。
|
||||
|
||||
squares的例子證明,函數值不僅僅是一串代碼,還記録了狀態。在squares中定義的匿名內部函數可以訪問和更新squares中的局部變量,這意味着匿名函數和squares中,存在變量引用。這就是函數值屬於引用類型和函數值不可比較的原因。Go使用閉包(closures)技術實現函數值,Go程序員也把函數值叫做閉包。
|
||||
|
||||
通過這個例子,我們看到變量的生命週期不由它的作用域決定:squares返迴後,變量x仍然隱式的存在於f中。
|
||||
|
||||
接下來,我們討論一個有點學術性的例子,考慮這樣一個問題:給定一些計算機課程,每個課程都有前置課程,隻有完成了前置課程才可以開始當前課程的學習;我們的目標是選擇出一組課程,這組課程必鬚確保按順序學習時,能全部被完成。每個課程的前置課程如下:
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/toposort
|
||||
// prereqs記録了每個課程的前置課程
|
||||
var prereqs = map[string][]string{
|
||||
"algorithms": {"data structures"},
|
||||
"calculus": {"linear algebra"},
|
||||
"compilers": {
|
||||
"data structures",
|
||||
"formal languages",
|
||||
"computer organization",
|
||||
},
|
||||
"data structures": {"discrete math"},
|
||||
"databases": {"data structures"},
|
||||
"discrete math": {"intro to programming"},
|
||||
"formal languages": {"discrete math"},
|
||||
"networks": {"operating systems"},
|
||||
"operating systems": {"data structures", "computer organization"},
|
||||
"programming languages": {"data structures", "computer organization"},
|
||||
}
|
||||
```
|
||||
|
||||
這類問題被稱作拓撲排序。從概念上説,前置條件可以構成有向圖。圖中的頂點表示課程,邊表示課程間的依賴關繫。顯然,圖中應該無環,這也就是説從某點出發的邊,最終不會迴到該點。下面的代碼用深度優先蒐索了整張圖,獲得了符合要求的課程序列。
|
||||
|
||||
```Go
|
||||
func main() {
|
||||
for i, course := range topoSort(prereqs) {
|
||||
fmt.Printf("%d:\t%s\n", i+1, course)
|
||||
}
|
||||
}
|
||||
|
||||
func topoSort(m map[string][]string) []string {
|
||||
var order []string
|
||||
seen := make(map[string]bool)
|
||||
var visitAll func(items []string)
|
||||
visitAll = func(items []string) {
|
||||
for _, item := range items {
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
visitAll(m[item])
|
||||
order = append(order, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
var keys []string
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
visitAll(keys)
|
||||
return order
|
||||
}
|
||||
```
|
||||
|
||||
當匿名函數需要被遞歸調用時,我們必鬚首先聲明一個變量(在上面的例子中,我們首先聲明了 visitAll),再將匿名函數賦值給這個變量。如果不分成兩部,函數字面量無法與visitAll綁定,我們也無法遞歸調用該匿名函數。
|
||||
|
||||
```Go
|
||||
visitAll := func(items []string) {
|
||||
// ...
|
||||
visitAll(m[item]) // compile error: undefined: visitAll
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
在topsort中,首先對prereqs中的key排序,再調用visitAll。因爲prereqs映射的是切片而不是更複雜的map,所以數據的遍歷次序是固定的,這意味着你每次運行topsort得到的輸出都是一樣的。 topsort的輸出結果如下:
|
||||
|
||||
```
|
||||
1: intro to programming
|
||||
2: discrete math
|
||||
3: data structures
|
||||
4: algorithms
|
||||
5: linear algebra
|
||||
6: calculus
|
||||
7: formal languages
|
||||
8: computer organization
|
||||
9: compilers
|
||||
10: databases
|
||||
11: operating systems
|
||||
12: networks
|
||||
13: programming languages
|
||||
```
|
||||
|
||||
讓我們迴到findLinks這個例子。我們將代碼移動到了links包下,將函數重命名爲Extract,在第八章我們會再次用到這個函數。新的匿名函數被引入,用於替換原來的visit函數。該匿名函數負責將新連接添加到切片中。在Extract中,使用forEachNode遍歷HTML頁面,由於Extract隻需要在遍歷結點前操作結點,所以forEachNode的post參數被傳入nil。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/links
|
||||
// Package links provides a link-extraction function.
|
||||
package links
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
// Extract makes an HTTP GET request to the specified URL, parses
|
||||
// the response as HTML, and returns the links in the HTML document.
|
||||
func Extract(url string) ([]string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
|
||||
}
|
||||
doc, err := html.Parse(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
var links []string
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "a" {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key != "href" {
|
||||
continue
|
||||
}
|
||||
link, err := resp.Request.URL.Parse(a.Val)
|
||||
if err != nil {
|
||||
continue // ignore bad URLs
|
||||
}
|
||||
links = append(links, link.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachNode(doc, visitNode, nil)
|
||||
return links, nil
|
||||
}
|
||||
```
|
||||
|
||||
上面的代碼對之前的版本做了改進,現在links中存儲的不是href屬性的原始值,而是通過resp.Request.URL解析後的值。解析後,這些連接以絶對路徑的形式存在,可以直接被http.Get訪問。
|
||||
|
||||
網頁抓取的核心問題就是如何遍歷圖。在topoSort的例子中,已經展示了深度優先遍歷,在網頁抓取中,我們會展示如何用廣度優先遍歷圖。在第8章,我們會介紹如何將深度優先和廣度優先結合使用。
|
||||
|
||||
下面的函數實現了廣度優先算法。調用者需要輸入一個初始的待訪問列表和一個函數f。待訪問列表中的每個元素被定義爲string類型。廣度優先算法會爲每個元素調用一次f。每次f執行完畢後,會返迴一組待訪問元素。這些元素會被加入到待訪問列表中。當待訪問列表中的所有元素都被訪問後,breadthFirst函數運行結束。爲了避免同一個元素被訪問兩次,代碼中維護了一個map。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/findlinks3
|
||||
// breadthFirst calls f for each item in the worklist.
|
||||
// Any items returned by f are added to the worklist.
|
||||
// f is called at most once for each item.
|
||||
func breadthFirst(f func(item string) []string, worklist []string) {
|
||||
seen := make(map[string]bool)
|
||||
for len(worklist) > 0 {
|
||||
items := worklist
|
||||
worklist = nil
|
||||
for _, item := range items {
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
worklist = append(worklist, f(item)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
就像我們在章節3解釋的那樣,append的參數“f(item)...”,會將f返迴的一組元素一個個添加到worklist中。
|
||||
|
||||
在我們網頁抓取器中,元素的類型是url。crawl函數會將URL輸出,提取其中的新鏈接,併將這些新鏈接返迴。我們會將crawl作爲參數傳遞給breadthFirst。
|
||||
|
||||
```go
|
||||
func crawl(url string) []string {
|
||||
fmt.Println(url)
|
||||
list, err := links.Extract(url)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
return list
|
||||
}
|
||||
```
|
||||
|
||||
爲了使抓取器開始運行,我們用命令行輸入的參數作爲初始的待訪問url。
|
||||
|
||||
```Go
|
||||
func main() {
|
||||
// Crawl the web breadth-first,
|
||||
// starting from the command-line arguments.
|
||||
breadthFirst(crawl, os.Args[1:])
|
||||
}
|
||||
```
|
||||
|
||||
讓我們從 https://golang.org 開始,下面是程序的輸出結果:
|
||||
|
||||
```bash
|
||||
$ go build gopl.io/ch5/findlinks3
|
||||
$ ./findlinks3 https://golang.org
|
||||
https://golang.org/
|
||||
https://golang.org/doc/
|
||||
https://golang.org/pkg/
|
||||
https://golang.org/project/
|
||||
https://code.google.com/p/go-tour/
|
||||
https://golang.org/doc/code.html
|
||||
https://www.youtube.com/watch?v=XCsL89YtqCs
|
||||
http://research.swtch.com/gotour
|
||||
```
|
||||
|
||||
當所有發現的鏈接都已經被訪問或電腦的內存耗盡時,程序運行結束。
|
||||
|
||||
**練習5.10:** 重寫topoSort函數,用map代替切片併移除對key的排序代碼。驗證結果的正確性(結果不唯一)。
|
||||
|
||||
**練習5.11:** 現在線性代數的老師把微積分設爲了前置課程。完善topSort,使其能檢測有向圖中的環。
|
||||
|
||||
**練習5.12:** gopl.io/ch5/outline2(5.5節)的startElement和endElement共用了全局變量depth,將它們脩改爲匿名函數,使其共享outline中的局部變量。
|
||||
|
||||
**練習5.13:** 脩改crawl,使其能保存發現的頁面,必要時,可以創建目録來保存這些頁面。隻保存來自原始域名下的頁面。假設初始頁面在golang.org下,就不要保存vimeo.com下的頁面。
|
||||
|
||||
**練習5.14:** 使用breadthFirst遍歷其他數據結構。比如,topoSort例子中的課程依賴關繫(有向圖),個人計算機的文件層次結構(樹),你所在城市的公交或地鐵線路(無向圖)。
|
||||
|
||||
{% include "./ch5-06-1.md" %}
|
||||
|
@ -1,3 +1,65 @@
|
||||
## 5.7. 可變參數
|
||||
|
||||
TODO
|
||||
參數數量可變的函數稱爲爲可變參數函數。典型的例子就是fmt.Printf和類似函數。Printf首先接收一個的必備參數,之後接收任意個數的後續參數。
|
||||
|
||||
在聲明可變參數函數時,需要在參數列表的最後一個參數類型之前加上省略符號“...”,這表示該函數會接收任意數量的該類型參數。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/sum
|
||||
func sum(vals...int) int {
|
||||
total := 0
|
||||
for _, val := range vals {
|
||||
total += val
|
||||
}
|
||||
return total
|
||||
}
|
||||
```
|
||||
sum函數返迴任意個int型參數的和。在函數體中,vals被看作是類型爲[] int的切片。sum可以接收任意數量的int型參數:
|
||||
|
||||
```Go
|
||||
fmt.Println(sum()) // "0"
|
||||
fmt.Println(sum(3)) // "3"
|
||||
fmt.Println(sum(1, 2, 3, 4)) // "10"
|
||||
```
|
||||
|
||||
在上面的代碼中,調用者隱式的創建一個數組,併將原始參數複製到數組中,再把數組的一個切片作爲參數傳給被調函數。如果原始參數已經是切片類型,我們該如何傳遞給sum?隻需在最後一個參數後加上省略符。下面的代碼功能與上個例子中最後一條語句相同。
|
||||
|
||||
```Go
|
||||
values := []int{1, 2, 3, 4}
|
||||
fmt.Println(sum(values...)) // "10"
|
||||
```
|
||||
|
||||
雖然在可變參數函數內部,...int 型參數的行爲看起來很像切片類型,但實際上,可變參數函數和以切片作爲參數的函數是不同的。
|
||||
|
||||
```Go
|
||||
func f(...int) {}
|
||||
func g([]int) {}
|
||||
fmt.Printf("%T\n", f) // "func(...int)"
|
||||
fmt.Printf("%T\n", g) // "func([]int)"
|
||||
```
|
||||
|
||||
可變參數函數經常被用於格式化字符串。下面的errorf函數構造了一個以行號開頭的,經過格式化的錯誤信息。函數名的後綴f是一種通用的命名規范,代表該可變參數函數可以接收Printf風格的格式化字符串。
|
||||
|
||||
```Go
|
||||
func errorf(linenum int, format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "Line %d: ", linenum)
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
}
|
||||
linenum, name := 12, "count"
|
||||
errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count"
|
||||
```
|
||||
|
||||
interfac{}表示函數的最後一個參數可以接收任意類型,我們會在第7章詳細介紹。
|
||||
|
||||
**練習5.15:** 編寫類似sum的可變參數函數max和min。考慮不傳參時,max和min該如何處理,再編寫至少接收1個參數的版本。
|
||||
|
||||
**練習5.16:**編寫多參數版本的strings.Join。
|
||||
|
||||
**練習5.17:**編寫多參數版本的ElementsByTagName,函數接收一個HTML結點樹以及任意數量的標籤名,返迴與這些標籤名匹配的所有元素。下面給出了2個例子:
|
||||
|
||||
```Go
|
||||
func ElementsByTagName(doc *html.Node, name...string) []*html.Node
|
||||
images := ElementsByTagName(doc, "img")
|
||||
headings := ElementsByTagName(doc, "h1", "h2", "h3", "h4")
|
||||
```
|
||||
|
226
ch5/ch5-08.md
@ -1,3 +1,227 @@
|
||||
## 5.8. Deferred函數
|
||||
|
||||
TODO
|
||||
在findLinks的例子中,我們用http.Get的輸出作爲html.Parse的輸入。隻有url的內容的確是HTML格式的,html.Parse才可以正常工作,但實際上,url指向的內容很豐富,可能是圖片,純文本或是其他。將這些格式的內容傳遞給html.parse,會産生不良後果。
|
||||
|
||||
下面的例子獲取HTML頁面併輸出頁面的標題。title函數會檢査服務器返迴的Content-Type字段,如果發現頁面不是HTML,將終止函數運行,返迴錯誤。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/title1
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check Content-Type is HTML (e.g., "text/html;charset=utf-8").
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if ct != "text/html" && !strings.HasPrefix(ct,"text/html;") {
|
||||
resp.Body.Close()
|
||||
return fmt.Errorf("%s has type %s, not text/html",url, ct)
|
||||
}
|
||||
doc, err := html.Parse(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing %s as HTML: %v", url,err)
|
||||
}
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "title"&&n.FirstChild != nil {
|
||||
fmt.Println(n.FirstChild.Data)
|
||||
}
|
||||
}
|
||||
forEachNode(doc, visitNode, nil)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
下面展示了運行效果:
|
||||
|
||||
```powershell
|
||||
$ go build gopl.io/ch5/title1
|
||||
$ ./title1 http://gopl.io
|
||||
The Go Programming Language
|
||||
$ ./title1 https://golang.org/doc/effective_go.html
|
||||
Effective Go - The Go Programming Language
|
||||
$ ./title1 https://golang.org/doc/gopher/frontpage.png
|
||||
title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/html
|
||||
```
|
||||
|
||||
resp.Body.close調用了多次,這是爲了確保title在所有執行路徑下(卽使函數運行失敗)都關閉了網絡連接。隨着函數變得複雜,需要處理的錯誤也變多,維護清理邏輯變得越來越睏難。而Go語言獨有的defer機製可以讓事情變得簡單。
|
||||
|
||||
你隻需要在調用普通函數或方法前加上關鍵字defer,就完成了defer所需要的語法。當defer語句被執行時,跟在defer後面的函數會被延遲執行。直到包含該defer語句的函數執行完畢時,defer後的函數才會被執行,不論包含defer語句的函數是通過return正常結束,還是由於panic導致的異常結束。你可以在一個函數中執行多條defer語句,它們的執行順序與聲明順序相反。
|
||||
|
||||
defer語句經常被用於處理成對的操作,如打開、關閉、連接、斷開連接、加鎖、釋放鎖。通過defer機製,不論函數邏輯多複雜,都能保證在任何執行路徑下,資源被釋放。釋放資源的defer應該直接跟在請求資源的語句後。在下面的代碼中,一條defer語句替代了之前的所有resp.Body.Close
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/title2
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if ct != "text/html" && !strings.HasPrefix(ct,"text/html;") {
|
||||
return fmt.Errorf("%s has type %s, not text/html",url, ct)
|
||||
}
|
||||
doc, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing %s as HTML: %v", url,err)
|
||||
}
|
||||
// ...print doc's title element…
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
在處理其他資源時,也可以采用defer機製,比如對文件的操作:
|
||||
|
||||
```Go
|
||||
io/ioutil
|
||||
package ioutil
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return ReadAll(f)
|
||||
}
|
||||
```
|
||||
|
||||
或是處理互斥鎖(9.2章)
|
||||
|
||||
```Go
|
||||
var mu sync.Mutex
|
||||
var m = make(map[string]int)
|
||||
func lookup(key string) int {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return m[key]
|
||||
}
|
||||
```
|
||||
|
||||
調試複雜程序時,defer機製也常被用於記録何時進入和退出函數。下例中的bigSlowOperation函數,直接調用trace記録函數的被調情況。bigSlowOperation被調時,trace會返迴一個函數值,該函數值會在bigSlowOperation退出時被調用。通過這種方式, 我們可以隻通過一條語句控製函數的入口和所有的出口,甚至可以記録函數的運行時間,如例子中的start。需要註意一點:不要忘記defer語句後的圓括號,否則本該在進入時執行的操作會在退出時執行,而本該在退出時執行的,永遠不會被執行。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/trace
|
||||
func bigSlowOperation() {
|
||||
defer trace("bigSlowOperation")() // don't forget the
|
||||
extra parentheses
|
||||
// ...lots of work…
|
||||
time.Sleep(10 * time.Second) // simulate slow
|
||||
operation by sleeping
|
||||
}
|
||||
func trace(msg string) func() {
|
||||
start := time.Now()
|
||||
log.Printf("enter %s", msg)
|
||||
return func() {
|
||||
log.Printf("exit %s (%s)", msg,time.Since(start))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每一次bigSlowOperation被調用,程序都會記録函數的進入,退出,持續時間。(我們用time.Sleep模擬一個耗時的操作)
|
||||
|
||||
```powershell
|
||||
$ go build gopl.io/ch5/trace
|
||||
$ ./trace
|
||||
2015/11/18 09:53:26 enter bigSlowOperation
|
||||
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
|
||||
```
|
||||
|
||||
我們知道,defer語句中的函數會在return語句更新返迴值變量後再執行,又因爲在函數中定義的匿名函數可以訪問該函數包括返迴值變量在內的所有變量,所以,對匿名函數采用defer機製,可以使其觀察函數的返迴值。
|
||||
|
||||
以double函數爲例:
|
||||
|
||||
```Go
|
||||
func double(x int) int {
|
||||
return x + x
|
||||
}
|
||||
```
|
||||
|
||||
我們隻需要首先命名double的返迴值,再增加defer語句,我們就可以在double每次被調用時,輸出參數以及返迴值。
|
||||
|
||||
```Go
|
||||
func double(x int) (result int) {
|
||||
defer func() { fmt.Printf("double(%d) = %d\n", x,result) }()
|
||||
return x + x
|
||||
}
|
||||
_ = double(4)
|
||||
// Output:
|
||||
// "double(4) = 8"
|
||||
```
|
||||
|
||||
可能doulbe函數過於簡單,看不出這個小技巧的作用,但對於有許多return語句的函數而言,這個技巧很有用。
|
||||
|
||||
被延遲執行的匿名函數甚至可以脩改函數返迴給調用者的返迴值:
|
||||
|
||||
```Go
|
||||
func triple(x int) (result int) {
|
||||
defer func() { result += x }()
|
||||
return double(x)
|
||||
}
|
||||
fmt.Println(triple(4)) // "12"
|
||||
```
|
||||
|
||||
在循環體中的defer語句需要特别註意,因爲隻有在函數執行完畢後,這些被延遲的函數才會執行。下面的代碼會導致繫統的文件描述符耗盡,因爲在所有文件都被處理之前,沒有文件會被關閉。
|
||||
|
||||
```Go
|
||||
for _, filename := range filenames {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close() // NOTE: risky; could run out of file
|
||||
descriptors
|
||||
// ...process f…
|
||||
}
|
||||
```
|
||||
|
||||
一種解決方法是將循環體中的defer語句移至另外一個函數。在每次循環時,調用這個函數。
|
||||
|
||||
```Go
|
||||
for _, filename := range filenames {
|
||||
if err := doFile(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
func doFile(filename string) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
// ...process f…
|
||||
}
|
||||
```
|
||||
|
||||
下面的代碼是fetch(1.5節)的改進版,我們將http響應信息寫入本地文件而不是從標準輸出流輸出。我們通過path.Base提出url路徑的最後一段作爲文件名。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/fetch
|
||||
// Fetch downloads the URL and returns the
|
||||
// name and length of the local file.
|
||||
func fetch(url string) (filename string, n int64, err error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
local := path.Base(resp.Request.URL.Path)
|
||||
if local == "/" {
|
||||
local = "index.html"
|
||||
}
|
||||
f, err := os.Create(local)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
n, err = io.Copy(f, resp.Body)
|
||||
// Close file, but prefer error from Copy, if any.
|
||||
if closeErr := f.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
return local, n, err
|
||||
}
|
||||
```
|
||||
|
||||
對resp.Body.Close延遲調用我們已經見過了,在此不做解釋。上例中,通過os.Create打開文件進行寫入,在關閉文件時,我們沒有對f.close采用defer機製,因爲這會産生一些微妙的錯誤。許多文件繫統,尤其是NFS,寫入文件時發生的錯誤會被延遲到文件關閉時反饋。如果沒有檢査文件關閉時的反饋信息,可能會導致數據丟失,而我們還誤以爲寫入操作成功。如果io.Copy和f.close都失敗了,我們傾向於將io.Copy的錯誤信息反饋給調用者,因爲它先於f,close發生,更有可能接近問題的本質。
|
||||
|
||||
**練習5.18:**不脩改fetch的行爲,重寫fetch函數,要求使用defer機製關閉文件。
|
129
ch5/ch5-09.md
@ -1,3 +1,130 @@
|
||||
## 5.9. Panic異常
|
||||
|
||||
TODO
|
||||
Go的類型繫統會在編譯時捕獲很多錯誤,但有些錯誤隻能在運行時檢査,如數組訪問越界、空指針引用等。這些運行時錯誤會引起painc異常。
|
||||
|
||||
一般而言,當panic異常發生時,程序會中斷運行,併立卽執行在該goroutine(可以先理解成線程,在第8章會詳細介紹)中被延遲的函數(defer 機製)。隨後,程序崩潰併輸出日誌信息。日誌信息包括panic value和函數調用的堆棧跟蹤信息。panic value通常是某種錯誤信息。對於每個goroutine,日誌信息中都會有與之相對的,發生panic時的函數調用堆棧跟蹤信息。通常,我們不需要再次運行程序去定位問題,日誌信息已經提供了足夠的診斷依據。因此,在我們填寫問題報告時,一般會將panic異常和日誌信息一併記録。
|
||||
|
||||
不是所有的panic異常都來自運行時,直接調用內置的panic函數也會引發panic異常;panic函數接受任何值作爲參數。 當某些不應該發生的場景發生時,我們就應該調用panic。比如,當程序到達了某條邏輯上不可能到達的路徑:
|
||||
|
||||
```Go
|
||||
switch s := suit(drawCard()); s {
|
||||
case "Spades": // ...
|
||||
case "Hearts": // ...
|
||||
case "Diamonds": // ...
|
||||
case "Clubs": // ...
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid suit %q", s)) // Joker?
|
||||
}
|
||||
```
|
||||
|
||||
斷言函數必鬚滿足的前置條件是明智的做法,但這很容易被濫用。除非你能提供更多的錯誤信息,或者能更快速的發現錯誤,否則不需要使用斷言,編譯器在運行時會幫你檢査代碼。
|
||||
|
||||
```Go
|
||||
func Reset(x *Buffer) {
|
||||
if x == nil {
|
||||
panic("x is nil") // unnecessary!
|
||||
}
|
||||
x.elements = nil
|
||||
}
|
||||
```
|
||||
|
||||
雖然Go的panic機製類似於其他語言的異常,但panic的適用場景有一些不同。由於panic會引起程序的崩潰,因此panic一般用於嚴重錯誤,如程序內部的邏輯不一致。勤奮的程序員認爲任何崩潰都表明代碼中存在漏洞,所以對於大部分漏洞,我們應該使用Go提供的錯誤機製,而不是panic,盡量避免程序的崩潰。在健壯的程序中,任何可以預料到的錯誤,如不正確的輸入、錯誤的配置或是失敗的I/O操作都應該被優雅的處理,最好的處理方式,就是使用Go的錯誤機製。
|
||||
|
||||
考慮regexp.Compile函數,該函數將正則表達式編譯成有效的可匹配格式。當輸入的正則表達式不合法時,該函數會返迴一個錯誤。當調用者明確的知道正確的輸入不會引起函數錯誤時,要求調用者檢査這個錯誤是不必要和纍贅的。我們應該假設函數的輸入一直合法,就如前面的斷言一樣:當調用者輸入了不應該出現的輸入時,觸發panic異常。
|
||||
|
||||
在程序源碼中,大多數正則表達式是字符串字面值(string literals),因此regexp包提供了包裝函數regexp.MustCompile檢査輸入的合法性。
|
||||
|
||||
```Go
|
||||
package regexp
|
||||
func Compile(expr string) (*Regexp, error) { /* ... */ }
|
||||
func MustCompile(expr string) *Regexp {
|
||||
re, err := Compile(expr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return re
|
||||
}
|
||||
```
|
||||
|
||||
包裝函數使得調用者可以便捷的用一個編譯後的正則表達式爲包級别的變量賦值:
|
||||
|
||||
```Go
|
||||
var httpSchemeRE = regexp.MustCompile(`^https?:`) //"http:" or "https:"
|
||||
```
|
||||
|
||||
顯然,MustCompile不能接收不合法的輸入。函數名中的Must前綴是一種針對此類函數的命名約定,比如template.Must(4.6節)
|
||||
|
||||
```Go
|
||||
func main() {
|
||||
f(3)
|
||||
}
|
||||
func f(x int) {
|
||||
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
|
||||
defer fmt.Printf("defer %d\n", x)
|
||||
f(x - 1)
|
||||
}
|
||||
```
|
||||
|
||||
上例中的運行輸出如下:
|
||||
|
||||
```bash
|
||||
f(3)
|
||||
f(2)
|
||||
f(1)
|
||||
defer 1
|
||||
defer 2
|
||||
defer 3
|
||||
```
|
||||
|
||||
當f(0)被調用時,發生panic異常,之前被延遲執行的的3個fmt.Printf被調用。程序中斷執行後,panic信息和堆棧信息會被輸出(下面是簡化的輸出):
|
||||
|
||||
```powershell
|
||||
panic: runtime error: integer divide by zero
|
||||
main.f(0)
|
||||
src/gopl.io/ch5/defer1/defer.go:14
|
||||
main.f(1)
|
||||
src/gopl.io/ch5/defer1/defer.go:16
|
||||
main.f(2)
|
||||
src/gopl.io/ch5/defer1/defer.go:16
|
||||
main.f(3)
|
||||
src/gopl.io/ch5/defer1/defer.go:16
|
||||
main.main()
|
||||
src/gopl.io/ch5/defer1/defer.go:10
|
||||
```
|
||||
|
||||
我們在下一節將看到,如何使程序從panic異常中恢複,阻止程序的崩潰。
|
||||
|
||||
爲了方便診斷問題,runtime包允許程序員輸出堆棧信息。在下面的例子中,我們通過在main函數中延遲調用printStack輸出堆棧信息。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/defer2
|
||||
func main() {
|
||||
defer printStack()
|
||||
f(3)
|
||||
}
|
||||
func printStack() {
|
||||
var buf [4096]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
os.Stdout.Write(buf[:n])
|
||||
}
|
||||
```
|
||||
|
||||
printStack的簡化輸出如下(下面隻是printStack的輸出,不包括panic的日誌信息):
|
||||
|
||||
```bash
|
||||
goroutine 1 [running]:
|
||||
main.printStack()
|
||||
src/gopl.io/ch5/defer2/defer.go:20
|
||||
main.f(0)
|
||||
src/gopl.io/ch5/defer2/defer.go:27
|
||||
main.f(1)
|
||||
src/gopl.io/ch5/defer2/defer.go:29
|
||||
main.f(2)
|
||||
src/gopl.io/ch5/defer2/defer.go:29
|
||||
main.f(3)
|
||||
src/gopl.io/ch5/defer2/defer.go:29
|
||||
main.main()
|
||||
src/gopl.io/ch5/defer2/defer.go:15
|
||||
```
|
||||
|
||||
將panic機製類比其他語言異常機製的讀者可能會驚訝,rumtime.Stack爲何能輸出已經被釋放函數的信息?在Go的panic機製中,延遲函數的調用在釋放堆棧信息之前。
|
||||
|
@ -1,7 +1,9 @@
|
||||
### 7.5.1. 警告:一個包含nil指針的接口不是nil接口
|
||||
|
||||
一個不包含任何值的nil接口值和一個剛好包含nil指針的接口值是不同的。這個細微區别産生了一個容易絆倒每個Go程序員的陷阱。
|
||||
|
||||
思考下面的程序。當debug變量設置爲true時,main函數會將f函數的輸出收集到一個bytes.Buffer類型中。
|
||||
|
||||
```go
|
||||
const debug = true
|
||||
|
||||
@ -24,12 +26,15 @@ func f(out io.Writer) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我們可能會預計當把變量debug設置爲false時可以禁止對輸出的收集,但是實際上在out.Write方法調用時程序發生了panic:
|
||||
|
||||
```go
|
||||
if out != nil {
|
||||
out.Write([]byte("done!\n")) // panic: nil pointer dereference
|
||||
}
|
||||
```
|
||||
|
||||
當main函數調用函數f時,它給f函數的out參數賦了一個\*bytes.Buffer的空指針,所以out的動態值是nil。然而,它的動態類型是\*bytes.Buffer,意思就是out變量是一個包含空指針值的非空接口(如圖7.5),所以防禦性檢査out!=nil的結果依然是true。
|
||||
|
||||
![](../images/ch7-05.png)
|
||||
@ -37,6 +42,7 @@ if out != nil {
|
||||
動態分配機製依然決定(\*bytes.Buffer).Write的方法會被調用,但是這次的接收者的值是nil。對於一些如\*os.File的類型,nil是一個有效的接收者(§6.2.1),但是\*bytes.Buffer類型不在這些類型中。這個方法會被調用,但是當它嚐試去獲取緩衝區時會發生panic。
|
||||
|
||||
問題在於盡管一個nil的\*bytes.Buffer指針有實現這個接口的方法,它也不滿足這個接口具體的行爲上的要求。特别是這個調用違反了(\*bytes.Buffer).Write方法的接收者非空的隱含先覺條件,所以將nil指針賦給這個接口是錯誤的。解決方案就是將main函數中的變量buf的類型改爲io.Writer,因此可以避免一開始就將一個不完全的值賦值給這個接口:
|
||||
|
||||
```go
|
||||
var buf io.Writer
|
||||
if debug {
|
||||
@ -44,4 +50,5 @@ if debug {
|
||||
}
|
||||
f(buf) // OK
|
||||
```
|
||||
|
||||
現在我們已經把接口值的技巧都講完了,讓我們來看更多的一些在Go標準庫中的重要接口類型。在下面的三章中,我們會看到接口類型是怎樣用在排序,web服務,錯誤處理中的。
|
||||
|
@ -107,3 +107,5 @@ fmt.Printf("%T\n", w) // "*bytes.Buffer"
|
||||
```
|
||||
|
||||
在fmt包內部,使用反射來獲取接口動態類型的名稱。我們會在第12章中學到反射相關的知識。
|
||||
|
||||
{% include "./ch7-05-1.md" %}
|
||||
|
@ -153,7 +153,7 @@ $ killall clock2
|
||||
$ TZ=US/Eastern ./clock2 -port 8010 &
|
||||
$ TZ=Asia/Tokyo ./clock2 -port 8020 &
|
||||
$ TZ=Europe/London ./clock2 -port 8030 &
|
||||
$ clockwall NewYork=localhost:8010 London=localhost:8020 Tokyo=localhost:8030
|
||||
$ clockwall NewYork=localhost:8010 Tokyo=localhost:8020 London=localhost:8030
|
||||
```
|
||||
|
||||
練習8.2: 實現一個併發FTP服務器。服務器應該解析客戶端來的一些命令,比如cd命令來切換目録,ls來列出目録內文件,get和send來傳輸文件,close來關閉連接。你可以用標準的ftp命令來作爲客戶端,或者也可以自己實現一個。
|
||||
|
@ -27,7 +27,7 @@ func makeThumbnails(filenames []string) {
|
||||
|
||||
顯然我們處理文件的順序無關緊要,因爲每一個圖片的拉伸操作和其它圖片的處理操作都是彼此獨立的。像這種子問題都是完全彼此獨立的問題被叫做易併行問題(譯註:embarrassingly parallel,直譯的話更像是尷尬併行)。易併行問題是最容易被實現成併行的一類問題(廢話),併且是最能夠享受併發帶來的好處,能夠隨着併行的規模線性地擴展。
|
||||
|
||||
下面讓我們併行地執行這些操作,從而將文件IO的延遲隱藏掉,併用上多覈cpu的計算能力來拉伸圖像。我們的第一個併發程序隻是使用了一個go關鍵字。這里我們先忽略掉錯誤,之後再進行處理。
|
||||
下面讓我們併行地執行這些操作,從而將文件IO的延遲隱藏掉,併用上多核cpu的計算能力來拉伸圖像。我們的第一個併發程序隻是使用了一個go關鍵字。這里我們先忽略掉錯誤,之後再進行處理。
|
||||
|
||||
```go
|
||||
// NOTE: incorrect!
|
||||
@ -186,4 +186,4 @@ sizes channel攜帶了每一個文件的大小到main goroutine,在main gorout
|
||||
|
||||
練習8.4: 脩改reverb2服務器,在每一個連接中使用sync.WaitGroup來計數活躍的echo goroutine。當計數減爲零時,關閉TCP連接的寫入,像練習8.3中一樣。驗證一下你的脩改版netcat3客戶端會一直等待所有的併發“喊叫”完成,卽使是在標準輸入流已經關閉的情況下。
|
||||
|
||||
練習8.5: 使用一個已有的CPU綁定的順序程序,比如在3.3節中我們寫的Mandelbrot程序或者3.2節中的3-D surface計算程序,併將他們的主循環改爲併發形式,使用channel來進行通信。在多覈計算機上這個程序得到了多少速度上的改進?使用多少個goroutine是最合適的呢?
|
||||
練習8.5: 使用一個已有的CPU綁定的順序程序,比如在3.3節中我們寫的Mandelbrot程序或者3.2節中的3-D surface計算程序,併將他們的主循環改爲併發形式,使用channel來進行通信。在多核計算機上這個程序得到了多少速度上的改進?使用多少個goroutine是最合適的呢?
|
||||
|
@ -59,7 +59,7 @@ https://golang.org/blog/
|
||||
|
||||
最初的錯誤信息是一個讓人莫名的DNS査找失敗,卽使這個域名是完全可靠的。而隨後的錯誤信息揭示了原因:這個程序一次性創建了太多網絡連接,超過了每一個進程的打開文件數限製,旣而導致了在調用net.Dial像DNS査找失敗這樣的問題。
|
||||
|
||||
這個程序實在是太他媽併行了。無窮無盡地併行化併不是什麽好事情,因爲不管怎麽説,你的繫統總是會有一個些限製因素,比如CPU覈心數會限製你的計算負載,比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率,比如你的網絡帶寬限製了你的下載速度上限,或者是你的一個web服務的服務容量上限等等。爲了解決這個問題,我們可以限製併發程序所使用的資源來使之適應自己的運行環境。對於我們的例子來説,最簡單的方法就是限製對links.Extract在同一時間最多不會有超過n次調用,這里的n是fd的limit-20,一般情況下。這個一個夜店里限製客人數目是一個道理,隻有當有客人離開時,才會允許新的客人進入店內(譯註:作者你個老流氓)。
|
||||
這個程序實在是太他媽併行了。無窮無盡地併行化併不是什麽好事情,因爲不管怎麽説,你的繫統總是會有一個些限製因素,比如CPU核心數會限製你的計算負載,比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率,比如你的網絡帶寬限製了你的下載速度上限,或者是你的一個web服務的服務容量上限等等。爲了解決這個問題,我們可以限製併發程序所使用的資源來使之適應自己的運行環境。對於我們的例子來説,最簡單的方法就是限製對links.Extract在同一時間最多不會有超過n次調用,這里的n是fd的limit-20,一般情況下。這個一個夜店里限製客人數目是一個道理,隻有當有客人離開時,才會允許新的客人進入店內(譯註:作者你個老流氓)。
|
||||
|
||||
我們可以用一個有容量限製的buffered channel來控製併發,這類似於操作繫統里的計數信號量概念。從概念上講,channel里的n個空槽代表n個可以處理內容的token(通行證),從channel里接收一個值會釋放其中的一個token,併且生成一個新的空槽位。這樣保證了在沒有接收介入時最多有n個發送操作。(這里可能我們拿channel里填充的槽來做token更直觀一些,不過還是這樣吧~)。由於channel里的元素類型併不重要,我們用一個零值的struct{}來作爲其元素。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 第八章 Goroutines和Channels
|
||||
|
||||
併發程序指的是同時做好幾件事情的程序,隨着硬件的發展,併發程序顯得越來越重要。Web服務器會一次處理成韆上萬的請求。平闆電腦和手機app在渲染用戶動畵的同時,還會後台執行各種計算任務和網絡請求。卽使是傳統的批處理問題--讀取數據,計算,寫輸出--現在也會用併發來隱藏掉I/O的操作延遲充分利用現代計算機設備的多覈,盡管計算機的性能每年都在增長,但併不是線性。
|
||||
併發程序指的是同時做好幾件事情的程序,隨着硬件的發展,併發程序顯得越來越重要。Web服務器會一次處理成韆上萬的請求。平闆電腦和手機app在渲染用戶動畵的同時,還會後台執行各種計算任務和網絡請求。卽使是傳統的批處理問題--讀取數據,計算,寫輸出--現在也會用併發來隱藏掉I/O的操作延遲充分利用現代計算機設備的多核,盡管計算機的性能每年都在增長,但併不是線性。
|
||||
|
||||
Go語言中的併發程序可以用兩種手段來實現。這一章會講解goroutine和channel,其支持“順序進程通信”(communicating sequential processes)或被簡稱爲CSP。CSP是一個現代的併發編程模型,在這種編程模型中值會在不同的運行實例(goroutine)中傳遞,盡管大多數情況下被限製在單一實例中。第9章會覆蓋到更爲傳統的併發模型:多線程共享內存,如果你在其它的主流語言中寫過併發程序的話可能會更熟悉一些。第9章同時會講一些本章不會深入的併發程序帶來的重要風險和陷阱。
|
||||
|
||||
|
@ -1,3 +1,45 @@
|
||||
## 9.4. 內存同步
|
||||
|
||||
TODO
|
||||
你可能比較糾結爲什麽Balance方法需要用到互斥條件,無論是基於channel還是基於互斥量。畢竟和存款不一樣,它隻由一個簡單的操作組成,所以不會碰到其它goroutine在其執行"中"執行其它的邏輯的風險。這里使用mutex有兩方面考慮。第一Balance不會在其它操作比如Withdraw“中間”執行。第二(更重要)的是"同步"不僅僅是一堆goroutine執行順序的問題;同樣也會涉及到內存的問題。
|
||||
|
||||
在現代計算機中可能會有一堆處理器,每一個都會有其本地緩存(local cache)。爲了效率,對內存的寫入一般會在每一個處理器中緩衝,併在必要時一起flush到主存。這種情況下這些數據可能會以與當初goroutine寫入順序不同的順序被提交到主存。像channel通信或者互斥量操作這樣的原語會使處理器將其聚集的寫入flush併commit,這樣goroutine在某個時間點上的執行結果才能被其它處理器上運行的goroutine得到。
|
||||
|
||||
考慮一下下面代碼片段的可能輸出:
|
||||
|
||||
```go
|
||||
var x, y int
|
||||
go func() {
|
||||
x = 1 // A1
|
||||
fmt.Print("y:", y, " ") // A2
|
||||
}()
|
||||
go func() {
|
||||
y = 1 // B1
|
||||
fmt.Print("x:", x, " ") // B2
|
||||
}()
|
||||
```
|
||||
|
||||
因爲兩個goroutine是併發執行,併且訪問共享變量時也沒有互斥,會有數據競爭,所以程序的運行結果沒法預測的話也請不要驚訝。我們可能希望它能夠打印出下面這四種結果中的一種,相當於幾種不同的交錯執行時的情況:
|
||||
|
||||
```
|
||||
y:0 x:1
|
||||
x:0 y:1
|
||||
x:1 y:1
|
||||
y:1 x:1
|
||||
```
|
||||
|
||||
第四行可以被解釋爲執行順序A1,B1,A2,B2或者B1,A1,A2,B2的執行結果。
|
||||
然而實際的運行時還是有些情況讓我們有點驚訝:
|
||||
|
||||
```
|
||||
x:0 y:0
|
||||
y:0 x:0
|
||||
```
|
||||
|
||||
但是根據所使用的編譯器,CPU,或者其它很多影響因子,這兩種情況也是有可能發生的。那麽這兩種情況要怎麽解釋呢?
|
||||
|
||||
在一個獨立的goroutine中,每一個語句的執行順序是可以被保證的;也就是説goroutine是順序連貫的。但是在不使用channel且不使用mutex這樣的顯式同步操作時,我們就沒法保證事件在不同的goroutine中看到的執行順序是一致的了。盡管goroutine A中一定需要觀察到x=1執行成功之後才會去讀取y,但它沒法確保自己觀察得到goroutine B中對y的寫入,所以A還可能會打印出y的一個舊版的值。
|
||||
|
||||
盡管去理解併發的一種嚐試是去將其運行理解爲不同goroutine語句的交錯執行,但看看上面的例子,這已經不是現代的編譯器和cpu的工作方式了。因爲賦值和打印指向不同的變量,編譯器可能會斷定兩條語句的順序不會影響執行結果,併且會交換兩個語句的執行順序。如果兩個goroutine在不同的CPU上執行,每一個核心有自己的緩存,這樣一個goroutine的寫入對於其它goroutine的Print,在主存同步之前就是不可見的了。
|
||||
|
||||
所有併發的問題都可以用一致的、簡單的旣定的模式來規避。所以可能的話,將變量限定在goroutine內部;如果是多個goroutine都需要訪問的變量,使用互斥條件來訪問。
|
||||
|
||||
|
105
ch9/ch9-05.md
@ -1,3 +1,106 @@
|
||||
## 9.5. sync.Once初始化
|
||||
|
||||
TODO
|
||||
如果初始化成本比較大的話,那麽將初始化延遲到需要的時候再去做就是一個比較好的選擇。如果在程序啟動的時候就去做這類的初始化的話會增加程序的啟動時間併且因爲執行的時候可能也併不需要這些變量所以實際上有一些浪費。讓我們在本章早一些時候看到的icons變量:
|
||||
|
||||
```go
|
||||
var icons map[string]image.Image
|
||||
```
|
||||
|
||||
這個版本的Icon用到了懶初始化(lazy initialization)。
|
||||
|
||||
```go
|
||||
func loadIcons() {
|
||||
icons = map[string]image.Image{
|
||||
"spades.png": loadIcon("spades.png"),
|
||||
"hearts.png": loadIcon("hearts.png"),
|
||||
"diamonds.png": loadIcon("diamonds.png"),
|
||||
"clubs.png": loadIcon("clubs.png"),
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: not concurrency-safe!
|
||||
func Icon(name string) image.Image {
|
||||
if icons == nil {
|
||||
loadIcons() // one-time initialization
|
||||
}
|
||||
return icons[name]
|
||||
}
|
||||
```
|
||||
|
||||
如果一個變量隻被一個單獨的goroutine所訪問的話,我們可以使用上面的這種模闆,但這種模闆在Icon被併發調用時併不安全。就像前面銀行的那個Deposit(存款)函數一樣,Icon函數也是由多個步驟組成的:首先測試icons是否爲空,然後load這些icons,之後將icons更新爲一個非空的值。直覺會告訴我們最差的情況是loadIcons函數被多次訪問會帶來數據競爭。當第一個goroutine在忙着loading這些icons的時候,另一個goroutine進入了Icon函數,發現變量是nil,然後也會調用loadIcons函數。
|
||||
|
||||
不過這種直覺是錯誤的。(我們希望現在你從現在開始能夠構建自己對併發的直覺,也就是説對併發的直覺總是不能被信任的!)迴憶一下9.4節。因爲缺少顯式的同步,編譯器和CPU是可以隨意地去更改訪問內存的指令順序,以任意方式,隻要保證每一個goroutine自己的執行順序一致。其中一種可能loadIcons的語句重排是下面這樣。它會在填寫icons變量的值之前先用一個空map來初始化icons變量。
|
||||
|
||||
```go
|
||||
func loadIcons() {
|
||||
icons = make(map[string]image.Image)
|
||||
icons["spades.png"] = loadIcon("spades.png")
|
||||
icons["hearts.png"] = loadIcon("hearts.png")
|
||||
icons["diamonds.png"] = loadIcon("diamonds.png")
|
||||
icons["clubs.png"] = loadIcon("clubs.png")
|
||||
}
|
||||
```
|
||||
|
||||
因此,一個goroutine在檢査icons是非空時,也併不能就假設這個變量的初始化流程已經走完了(譯註:可能隻是塞了個空map,里面的值還沒填完,也就是説填值的語句都沒執行完呢)。
|
||||
|
||||
最簡單且正確的保證所有goroutine能夠觀察到loadIcons效果的方式,是用一個mutex來同步檢査。
|
||||
|
||||
```go
|
||||
var mu sync.Mutex // guards icons
|
||||
var icons map[string]image.Image
|
||||
|
||||
// Concurrency-safe.
|
||||
func Icon(name string) image.Image {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if icons == nil {
|
||||
loadIcons()
|
||||
}
|
||||
return icons[name]
|
||||
}
|
||||
```
|
||||
|
||||
然而使用互斥訪問icons的代價就是沒有辦法對該變量進行併發訪問,卽使變量已經被初始化完畢且再也不會進行變動。這里我們可以引入一個允許多讀的鎖:
|
||||
|
||||
```go
|
||||
var mu sync.RWMutex // guards icons
|
||||
var icons map[string]image.Image
|
||||
// Concurrency-safe.
|
||||
func Icon(name string) image.Image {
|
||||
mu.RLock()
|
||||
if icons != nil {
|
||||
icon := icons[name]
|
||||
mu.RUnlock()
|
||||
return icon
|
||||
}
|
||||
mu.RUnlock()
|
||||
|
||||
// acquire an exclusive lock
|
||||
mu.Lock()
|
||||
if icons == nil { // NOTE: must recheck for nil
|
||||
loadIcons()
|
||||
}
|
||||
icon := icons[name]
|
||||
mu.Unlock()
|
||||
return icon
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
上面的代碼有兩個臨界區。goroutine首先會獲取一個寫鎖,査詢map,然後釋放鎖。如果條目被找到了(一般情況下),那麽會直接返迴。如果沒有找到,那goroutine會獲取一個寫鎖。不釋放共享鎖的話,也沒有任何辦法來將一個共享鎖陞級爲一個互斥鎖,所以我們必鬚重新檢査icons變量是否爲nil,以防止在執行這一段代碼的時候,icons變量已經被其它gorouine初始化過了。
|
||||
|
||||
上面的模闆使我們的程序能夠更好的併發,但是有一點太複雜且容易出錯。幸運的是,sync包爲我們提供了一個專門的方案來解決這種一次性初始化的問題:sync.Once。概念上來講,一次性的初始化需要一個互斥量mutex和一個boolean變量來記録初始化是不是已經完成了;互斥量用來保護boolean變量和客戶端數據結構。Do這個唯一的方法需要接收初始化函數作爲其參數。讓我們用sync.Once來簡化前面的Icon函數吧:
|
||||
|
||||
```go
|
||||
var loadIconsOnce sync.Once
|
||||
var icons map[string]image.Image
|
||||
// Concurrency-safe.
|
||||
func Icon(name string) image.Image {
|
||||
loadIconsOnce.Do(loadIcons)
|
||||
return icons[name]
|
||||
}
|
||||
```
|
||||
|
||||
每一次對Do(loadIcons)的調用都會鎖定mutex,併會檢査boolean變量。在第一次調用時,變量的值是false,Do會調用loadIcons併會將boolean設置爲true。隨後的調用什麽都不會做,但是mutex同步會保證loadIcons對內存(這里其實就是指icons變量啦)産生的效果能夠對所有goroutine可見。用這種方式來使用sync.Once的話,我們能夠避免在變量被構建完成之前和其它goroutine共享該變量。
|
||||
|
||||
**練習 9.2:** 重寫2.6.2節中的PopCount的例子,使用sync.Once,隻在第一次需要用到的時候進行初始化。(雖然實際上,對PopCount這樣很小且高度優化的函數進行同步可能代價沒法接受)
|
||||
|
@ -1,3 +1,7 @@
|
||||
### 9.8.1. 動態棧
|
||||
|
||||
TODO
|
||||
每一個OS線程都有一個固定大小的內存塊(一般會是2MB)來做棧,這個棧會用來存儲當前正在被調用或掛起(指在調用其它函數時)的函數的內部變量。這個固定大小的棧同時很大又很小。因爲2MB的棧對於一個小小的goroutine來説是很大的內存浪費,比如對於我們用到的,一個隻是用來WaitGroup之後關閉channel的goroutine來説。而對於go程序來説,同時創建成百上韆個gorutine是非常普遍的,如果每一個goroutine都需要這麽大的棧的話,那這麽多的goroutine就不太可能了。除去大小的問題之外,固定大小的棧對於更複雜或者更深層次的遞歸函數調用來説顯然是不夠的。脩改固定的大小可以提陞空間的利用率允許創建更多的線程,併且可以允許更深的遞歸調用,不過這兩者是沒法同時兼備的。
|
||||
|
||||
相反,一個goroutine會以一個很小的棧開始其生命週期,一般隻需要2KB。一個goroutine的棧,和操作繫統線程一樣,會保存其活躍或掛起的函數調用的本地變量,但是和OS線程不太一樣的是一個goroutine的棧大小併不是固定的;棧的大小會根據需要動態地伸縮。而goroutine的棧的最大值有1GB,比傳統的固定大小的線程棧要大得多,盡管一般情況下,大多goroutine都不需要這麽大的棧。
|
||||
|
||||
練習 9.4: 創建一個流水線程序,支持用channel連接任意數量的goroutine,在跑爆內存之前,可以創建多少流水線階段?一個變量通過整個流水線需要用多久?(這個練習題翻譯不是很確定。。)
|
||||
|
@ -1,3 +1,9 @@
|
||||
### 9.8.2. Goroutine調度
|
||||
|
||||
TODO
|
||||
OS線程會被操作繫統內核調度。每幾毫秒,一個硬件計時器會中斷處理器,這會調用一個叫作scheduler的內核函數。這個函數會掛起當前執行的線程併保存內存中它的寄存器內容,檢査線程列表併決定下一次哪個線程可以被運行,併從內存中恢複該線程的寄存器信息,然後恢複執行該線程的現場併開始執行線程。因爲操作繫統線程是被內核所調度,所以從一個線程向另一個“移動”需要完整的上下文切換,也就是説,保存一個用戶線程的狀態到內存,恢複另一個線程的到寄存器,然後更新調度器的數據結構。這幾步操作很慢,因爲其局部性很差需要幾次內存訪問,併且會增加運行的cpu週期。
|
||||
|
||||
Go的運行時包含了其自己的調度器,這個調度器使用了一些技術手段,比如m:n調度,因爲其會在n個操作繫統線程上多工(調度)m個goroutine。Go調度器的工作和內核的調度是相似的,但是這個調度器隻關註單獨的Go程序中的goroutine(譯註:按程序獨立)。
|
||||
|
||||
和操作繫統的線程調度不同的是,Go調度器併不是用一個硬件定時器而是被Go語言"建築"本身進行調度的。例如當一個goroutine調用了time.Sleep或者被channel調用或者mutex操作阻塞時,調度器會使其進入休眠併開始執行另一個goroutine直到時機到了再去喚醒第一個goroutine。因爲因爲這種調度方式不需要進入內核的上下文,所以重新調度一個goroutine比調度一個線程代價要低得多。
|
||||
|
||||
練習 9.5: 寫一個有兩個goroutine的程序,兩個goroutine會向兩個無buffer channel反複地發送ping-pong消息。這樣的程序每秒可以支持多少次通信?
|
||||
|
@ -1,3 +1,23 @@
|
||||
### 9.8.3. GOMAXPROCS
|
||||
|
||||
TODO
|
||||
Go的調度器使用了一個叫做GOMAXPROCS的變量來決定會有多少個操作繫統的線程同時執行Go的代碼。其默認的值是運行機器上的CPU的核心數,所以在一個有8個核心的機器上時,調度器一次會在8個OS線程上去調度GO代碼。(GOMAXPROCS是前面説的m:n調度中的n)。在休眠中的或者在通信中被阻塞的goroutine是不需要一個對應的線程來做調度的。在I/O中或繫統調用中或調用非Go語言函數時,是需要一個對應的操作繫統線程的,但是GOMAXPROCS併不需要將這幾種情況計數在內。
|
||||
|
||||
你可以用GOMAXPROCS的環境變量呂顯式地控製這個參數,或者也可以在運行時用runtime.GOMAXPROCS函數來脩改它。我們在下面的小程序中會看到GOMAXPROCS的效果,這個程序會無限打印0和1。
|
||||
|
||||
|
||||
```go
|
||||
for {
|
||||
go fmt.Print(0)
|
||||
fmt.Print(1)
|
||||
}
|
||||
|
||||
$ GOMAXPROCS=1 go run hacker-cliché.go
|
||||
111111111111111111110000000000000000000011111...
|
||||
|
||||
$ GOMAXPROCS=2 go run hacker-cliché.go
|
||||
010101010101010101011001100101011010010100110...
|
||||
```
|
||||
|
||||
在第一次執行時,最多同時隻能有一個goroutine被執行。初始情況下隻有main goroutine被執行,所以會打印很多1。過了一段時間後,GO調度器會將其置爲休眠,併喚醒另一個goroutine,這時候就開始打印很多0了,在打印的時候,goroutine是被調度到操作繫統線程上的。在第二次執行時,我們使用了兩個操作繫統線程,所以兩個goroutine可以一起被執行,以同樣的頻率交替打印0和1。我們必鬚強調的是goroutine的調度是受很多因子影響的,而runtime也是在不斷地發展演進的,所以這里的你實際得到的結果可能會因爲版本的不同而與我們運行的結果有所不同。
|
||||
|
||||
練習9.6: 測試一下計算密集型的併發程序(練習8.5那樣的)會被GOMAXPROCS怎樣影響到。在你的電腦上最佳的值是多少?你的電腦CPU有多少個核心?
|
||||
|
@ -1,3 +1,10 @@
|
||||
### 9.8.4. Goroutine沒有ID號
|
||||
|
||||
TODO
|
||||
在大多數支持多線程的操作繫統和程序語言中,當前的線程都有一個獨特的身份(id),併且這個身份信息可以以一個普通值的形式被被很容易地獲取到,典型的可以是一個integer或者指針值。這種情況下我們做一個抽象化的thread-local storage(線程本地存儲,多線程編程中不希望其它線程訪問的內容)就很容易,隻需要以線程的id作爲key的一個map就可以解決問題,每一個線程以其id就能從中獲取到值,且和其它線程互不衝突。
|
||||
|
||||
goroutine沒有可以被程序員獲取到的身份(id)的概念。這一點是設計上故意而爲之,由於thread-local storage總是會被濫用。比如説,一個web server是用一種支持tls的語言實現的,而非常普遍的是很多函數會去尋找HTTP請求的信息,這代表它們就是去其存儲層(這個存儲層有可能是tls)査找的。這就像是那些過分依賴全局變量的程序一樣,會導致一種非健康的“距離外行爲”,在這種行爲下,一個函數的行爲可能不是由其自己內部的變量所決定,而是由其所運行在的線程所決定。因此,如果線程本身的身份會改變--比如一些worker線程之類的--那麽函數的行爲就會變得神祕莫測。
|
||||
|
||||
Go鼓勵更爲簡單的模式,這種模式下參數對函數的影響都是顯式的。這樣不僅使程序變得更易讀,而且會讓我們自由地向一些給定的函數分配子任務時不用擔心其身份信息影響行爲。
|
||||
|
||||
你現在應該已經明白了寫一個Go程序所需要的所有語言特性信息。在後面兩章節中,我們會迴顧一些之前的實例和工具,支持我們寫出更大規模的程序:如何將一個工程組織成一繫列的包,如果獲取,構建,測試,性能測試,剖析,寫文檔,併且將這些包分享出去。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 9.8. Goroutines和線程
|
||||
|
||||
TODO
|
||||
在上一章中我們説goroutine和操作繫統的線程區别可以先忽略。盡管兩者的區别實際上隻是一個量的區别,但量變會引起質變的道理同樣適用於goroutine和線程。現在正是我們來區分開兩者的最佳時機。
|
||||
|
||||
{% include "./ch9-08-1.md" %}
|
||||
|
||||
|
BIN
cover.jpg
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 256 KiB |
BIN
cover_middle.jpg
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
cover_patch.png
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 34 KiB |
BIN
cover_small.jpg
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 10 KiB |
@ -133,10 +133,11 @@ var _RegexpLinksTable = func() map[*regexp.Regexp]string {
|
||||
var _LinkTable = map[string]string{
|
||||
|
||||
// 人名
|
||||
"Alan Donovan": "https://github.com/adonovan",
|
||||
"Alan Donovan": "https://github.com/adonovan",
|
||||
"Brian Kernighan": "http://www.cs.princeton.edu/~bwk/",
|
||||
"Alan A. A. Donovan": "https://github.com/adonovan",
|
||||
"Brian W. Kernighan": "http://www.cs.princeton.edu/~bwk/",
|
||||
"Dennis M. Ritchie": "http://genius.cat-v.org/dennis-ritchie/",
|
||||
"Robert Griesemer": "http://research.google.com/pubs/author96.html",
|
||||
"Rob Pike": "http://genius.cat-v.org/rob-pike/",
|
||||
"Ken Thompson": "http://genius.cat-v.org/ken-thompson/",
|
||||
@ -183,4 +184,5 @@ var _LinkTable = map[string]string{
|
||||
"Bell Labs": "http://www.cs.bell-labs.com/",
|
||||
"communicating sequential processes": "https://en.wikipedia.org/wiki/Communicating_sequential_processes",
|
||||
"CSP": "https://en.wikipedia.org/wiki/Communicating_sequential_processes",
|
||||
"K&R": "https://en.wikipedia.org/wiki/K%26R",
|
||||
}
|
||||
|
BIN
gopl-zh-qrcode.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
images/go-log04.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
images/logo/github.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
images/logo/gopher-china.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
28
preface.md
@ -9,7 +9,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
- 在線預覽:http://golang-china.github.io/gopl-zh
|
||||
- 原版官網:http://gopl.io
|
||||
|
||||
[![](cover_small.jpg)](https://github.com/golang-china/gopl-zh)
|
||||
[![](cover_middle.jpg)](https://github.com/golang-china/gopl-zh)
|
||||
|
||||
**版權聲明:** <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>。
|
||||
|
||||
@ -19,6 +19,30 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
|
||||
歡迎大家提供建議!
|
||||
|
||||
-------
|
||||
|
||||
# 譯者序
|
||||
|
||||
在上個世紀70年代,貝爾實驗室的[Ken Thompson](http://genius.cat-v.org/ken-thompson/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作發明了[UNIX](http://doc.cat-v.org/unix/)操作繫統,同時[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)爲了解決[UNIX](http://doc.cat-v.org/unix/)繫統的移植性問題而發明了C語言,貝爾實驗室的[UNIX](http://doc.cat-v.org/unix/)和C語言兩大發明奠定了整個現代IT行業最重要的軟件基礎(目前的三大桌面操作繫統的中[Linux](http://www.linux.org/)和[Mac OS X](http://www.apple.com/cn/osx/)都是源於[UINX]()繫統,兩大移動平台的操作繫統iOS和Android也都是源於[UNIX](http://doc.cat-v.org/unix/)繫統。C繫家族的編程語言占據統治地位達幾十年之久)。在[UINX]()和C語言發明40年之後,目前已經在Google工作的[Ken Thompson](http://genius.cat-v.org/ken-thompson/)和[Rob Pike](http://genius.cat-v.org/rob-pike/)(他們在貝爾實驗室時就是同事)、還有[Robert Griesemer](http://research.google.com/pubs/author96.html)(設計了V8引擎和HotSpot虛擬機)一起合作,爲了解決在21世紀多核和網絡化環境下越來越複雜的編程問題而發明了Go語言。從Go語言庫早期代碼庫日誌可以看出它的演化歷程(Git用`git log --before={2008-03-03} --reverse`命令査看):
|
||||
|
||||
![](./images/go-log04.png)
|
||||
|
||||
從早期提交日誌中也可以看出,Go語言是從[Ken Thompson](http://genius.cat-v.org/ken-thompson/)發明的B語言、[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)發明的C語言逐步演化過來的,是C語言家族的成員,因此很多人將Go語言稱爲21世紀的C語言。縱觀這幾年來的發展趨勢,Go語言已經成爲雲計算、雲存儲時代最重要的基礎編程語言。
|
||||
|
||||
在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](The C Programming Language)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。同時,大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。
|
||||
|
||||
同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必鬚進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化,充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題,希望大家能先用自己的方式嚐試完成習題,然後再參考官方給出的解決方案。
|
||||
|
||||
該書英文版約從2015年10月開始公開發售,同時同步發售的還有日文版本。不過比較可惜的是,中文版併沒有在同步發售之列,甚至連中文版是否會引進、是由哪個出版社引進、卽使引進將由何人來翻譯、何時能出版都成了一個祕密。中国的Go語言社區是全球最大的Go語言社區,我們從一開始就始終緊跟着Go語言的發展腳步。我們應該也完全有能力以中国Go語言社區的力量同步完成Go語言聖經中文版的翻譯工作。與此同時,国內有很多Go語言愛好者也在積極關註該書(本人也在第一時間購買了紙質版本,[亞馬遜價格314人民幣](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/))。爲了Go語言的學習和交流,大家決定合作免費翻譯該書。
|
||||
|
||||
翻譯工作從2015年11月20日前後開始,到2016年1月底初步完成,前後歷時約2個月時間。其中,[chai2010](https://github.com/chai2010)翻譯了前言、第2~4章、第10~13章,[Xargin](https://github.com/cch123)翻譯了第1章、第6章、第8~9章,[CrazySssst](https://github.com/CrazySssst)翻譯了第5章,[foreversmart](https://github.com/foreversmart)翻譯了第7章,大家共同參與了基本的校驗工作,還有其他一些朋友提供了積極的反饋建議。如果大家還有任何問題或建議,可以直接到中文版項目頁面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果發現英文版原文在[勘誤](http://www.gopl.io/errata.html)中未提到的任何錯誤,可以直接去[英文版項目](https://github.com/adonovan/gopl.io/)提交。
|
||||
|
||||
最後,希望這本書能夠幫助大家用Go語言快樂地編程。
|
||||
|
||||
2016年 1月 於 武漢
|
||||
|
||||
-------
|
||||
|
||||
# 前言
|
||||
|
||||
*“Go是一個開源的編程語言,它很容易用於構建簡單、可靠和高效的軟件。”(摘自Go語言官方網站:http://golang.org )*
|
||||
@ -41,5 +65,3 @@ Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在
|
||||
Go語言編寫的程序無需脩改就可以運行在上面這些環境。
|
||||
|
||||
本書是爲了幫助你開始以有效的方式使用Go語言,充分利用語言本身的特性和自帶的標準庫去編寫清晰地道的Go程序。
|
||||
|
||||
|
||||
|
14
progress.md
@ -38,10 +38,10 @@
|
||||
- [x] 5.3 Multiple Return Values
|
||||
- [x] 5.4 Errors
|
||||
- [x] 5.5 Function Values
|
||||
- [ ] 5.6 Anonymous Functions
|
||||
- [ ] 5.7 Variadic Functions
|
||||
- [ ] 5.8 Deferred Function Calls
|
||||
- [ ] 5.9 Panic
|
||||
- [x] 5.6 Anonymous Functions
|
||||
- [x] 5.7 Variadic Functions
|
||||
- [x] 5.8 Deferred Function Calls
|
||||
- [x] 5.9 Panic
|
||||
- [ ] 5.10 Recover
|
||||
- [x] Chapter 6: Methods
|
||||
- [x] 6.1 Method Declarations
|
||||
@ -81,11 +81,11 @@
|
||||
- [ ] 9.1 Race Conditions
|
||||
- [ ] 9.2 Mutual Exclusion: sync.Mutex
|
||||
- [x] 9.3 Read/Write Mutexes: sync.RWMutex
|
||||
- [ ] 9.4 Memory Synchronization
|
||||
- [ ] 9.5 Lazy Initialization: sync.Once
|
||||
- [x] 9.4 Memory Synchronization
|
||||
- [x] 9.5 Lazy Initialization: sync.Once
|
||||
- [x] 9.6 The Race Detector
|
||||
- [ ] 9.7 Example: Concurrent Non-Blocking Cache
|
||||
- [ ] 9.8 Goroutines and Threads
|
||||
- [x] 9.8 Goroutines and Threads
|
||||
- [x] Chapter 10: Packages and the Go Tool
|
||||
- [x] 10.1 Introduction
|
||||
- [x] 10.2 Import Paths
|
||||
|
@ -1,71 +0,0 @@
|
||||
# 術語翻譯
|
||||
|
||||
單詞 | 譯法 | 詞性 | 類别 | 備註
|
||||
----------------- | ----------------- | ------ | ---- | ----
|
||||
approximation | 近似[值]/逼近式 | n | |
|
||||
argument | 實參 | n | |
|
||||
assignment | 賦值 | n | |
|
||||
block | 塊/阻塞 | n | | “阻塞”僅用於信道
|
||||
body | 執行體 | n | |
|
||||
buffer | 緩衝區 | n | |
|
||||
cache | 緩存 | n | |
|
||||
case | 情況/寫法 | n | | 在用作大小寫時譯作“寫法”,因爲有些字母還有其它多種寫法
|
||||
channel | 信道 | n | |
|
||||
char/character | 字符 | n | |
|
||||
code point | 碼點 | n | |
|
||||
coefficient | 繫數 | n | |
|
||||
commit | 提交 | v | | 卽直接向repo提交代碼
|
||||
compatibility | 兼容性 | n/adj | |
|
||||
complex | 複數 | n | |
|
||||
constant | 常量 | n | |
|
||||
constructor | 構造函數 | n | |
|
||||
convention | 約定 | n | |
|
||||
defer | 推遲 | v | |
|
||||
degree | 階 | n | | 僅用於多項式
|
||||
distribute | 分發 | v | |
|
||||
distribution | 分發 | n | |
|
||||
embedding | 內嵌 | n/v | | 用作類型,與“嵌入式”分開
|
||||
error | 錯誤/誤差 | n | | “誤差”用於數學
|
||||
evaluation | 求值 | n | |
|
||||
even | 偶(數) | n/adj | |
|
||||
expression | 表達式 | n | |
|
||||
flag | 標誌 | n | |
|
||||
floating-point | 浮點數 | n | |
|
||||
form | 形式 | n | |
|
||||
function | 函數 | n | |
|
||||
implementation | 實現 | n | |
|
||||
integer | 整數 | n | |
|
||||
introduction | 引言 | n | |
|
||||
label | 標籤 | n/v | |
|
||||
lock | 鎖 | n/v | |
|
||||
method | 方法 | n | |
|
||||
mutex | 互斥鎖 | n | |
|
||||
name space | 命名空間 | n | |
|
||||
normalize | 規范化 | v | |
|
||||
odd | 奇(數) | n/adj | |
|
||||
panic |(保留) | n/v | |
|
||||
parameter | 形參 | n | |
|
||||
pending | 待定/掛起 | adj | |
|
||||
polynomial | 多項式 | n | |
|
||||
profile/profiling | 評估 | n/v | |
|
||||
race | 競爭/競態 | n/v | |
|
||||
recover | 恢複 | v | |
|
||||
reduction | 換算 | n | |
|
||||
reference | 引用 | n/v | |
|
||||
repository | [源碼]倉庫/源碼庫 | n | |
|
||||
round | 舍入 | v/adj | | 在數學中爲舍入
|
||||
rune | 符文 | n | |
|
||||
scope | 作用域 | n | |
|
||||
script | 腳本/書寫[繫統] | n | | 在Unicode中譯作“書寫[繫統]”
|
||||
source code | 源碼/源代碼 | n | |
|
||||
statement | 語句 | n | |
|
||||
stride | 間距 | n | | 用作兩個碼點的間距。如從A(0x41)到a(0x61)的間距爲32(0x20)
|
||||
struct | 結構體 | n | |
|
||||
submit | 遞交 | v | | 指遞交至主代碼樹
|
||||
tag | 標記 | n | | 多指struct tag,上下文隻與xml/html相關時仍作“標籤”
|
||||
token | [詞法]標記 | n | |
|
||||
ulp | 末尾單元 | n | | Unit in the Last Place 的縮寫
|
||||
universe block | 全域塊 | n | |
|
||||
variable | 變量 | n | |
|
||||
|
||||
説明: 如果對翻譯有更好的建議請 [創建ISSUE](https://github.com/golang-china/gopl-zh/issues/new) 參與討論。添加新詞請按字符串排序。
|