make loop

This commit is contained in:
chai2010
2015-12-26 20:05:30 +08:00
parent 82ec0c025d
commit e15e88dad7
74 changed files with 207 additions and 207 deletions

View File

@@ -1,6 +1,6 @@
## 1.1. Hello, World
我們以1978年版的C語言聖經《The C Programming Language》中經典的“hello world”案例來開始吧譯註本書作者之一Brian W. Kernighan也是C語言聖經一書的作者。C語言對Go語言的設計産生了很多影響。用這個例子我們來講解一些Go語言的覈心特性
我們以1978年版的C語言聖經《The C Programming Language》中經典的“hello world”案例來開始吧譯註本書作者之一Brian W. Kernighan也是C語言聖經一書的作者。C語言對Go語言的設計産生了很多影響。用這個例子我們來講解一些Go語言的覈心特性
```go
gopl.io/ch1/helloworld
@@ -19,7 +19,7 @@ Go是一門編譯型語言Go語言的工具鏈將源代碼和其依賴一起
$ go run helloworld.go
```
毫無意外,這個命令會輸
毫無意外,這個命令會輸
```
Hello, 世界
@@ -52,7 +52,7 @@ gopl.io/ch1/helloworld
我們來討論一下程序本身。Go語言的代碼是通過package來組織的package的概念和你知道的其它語言里的libraries或者modules概念比較類似。一個package會包含一個或多個.go結束的源代碼文件。每一個源文件都是以一個package xxx的聲明語句開頭的比如我們的例子里就是package main。這行聲明語句表示該文件是屬於哪一個package緊跟着是一繫列import的package名表示這個文件中引入的package。再之後是本文件本身的代碼。
Go的標準庫已經提供了100多個package用來完成一門程序語言的一些常見的基本任務比如輸入、輸、排序或者字符串/文本處理。比如fmt這個package就包括接收輸入、格式化輸的各種函數。Println是其中的一個常用的函數可以用這個函數來打印一個或多個值該函數會將這些參數用空格隔開進行輸,併在輸完畢之後在行末加上一個換行符。
Go的標準庫已經提供了100多個package用來完成一門程序語言的一些常見的基本任務比如輸入、輸、排序或者字符串/文本處理。比如fmt這個package就包括接收輸入、格式化輸的各種函數。Println是其中的一個常用的函數可以用這個函數來打印一個或多個值該函數會將這些參數用空格隔開進行輸,併在輸完畢之後在行末加上一個換行符。
package main是一個比較特殊的package。這個package里會定義一個獨立的程序這個程序是可以運行的而不是像其它package一樣對應一個library。在main這個package里main函數也是一個特殊的函數這是我們整個程序的入口譯註其實C繫語言差不多都是這樣。main函數所做的事情就是我們程序做的事情。當然了main函數一般是通過是調用其它packge里的函數來完成自己的工作比如fmt.Println。

View File

@@ -1,6 +1,6 @@
## 1.2. 命令行參數
大多數的程序都是處理輸入,産生輸;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。
大多數的程序都是處理輸入,産生輸;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。
os這個package提供了操作繫統無關跨平颱與繫統交互的一些函數和相關的變量運行時程序的命令行參數可以通過os包中一個叫Args的這個變量來穫取當在os包外部使用該變量時需要用os.Args來訪問。
@@ -8,7 +8,7 @@ os.Args這個變量是一個字符串string的slice譯註slice和Pyt
os.Args的第一個元素卽os.Args[0]是命令行執行時的命令本身其它的元素則是執行該命令時傳給這個程序的參數。前面提到的切片表達式s[m:n]會返迴第m到第n-1個元素所以下一個例子里需要用到的os.Args[1:len(os.Args)]卽是除了命令本身外的所有傳入參數。如果我們省略s[m:n]里的m和n那麽默認這個表達式會填入0:len(s)所以這里我們還可以省略掉n寫成os.Args[1:]。
下面是一個Unix里echo命令的實現這個命令會在單行內打印命令行參數。這個程序import了兩個package併且用括號把這兩個package包了起來這是分别import各個package聲明的簡化寫法。當然了你分開來寫import也沒有什麽問題隻是一般爲了方便我們都會像下面這樣來導入多個package。我們自己寫的導入順序併不重要因爲gofmt工具會幫助我們按照字母順序來排列好這些導入包名。本書中如果一個例子有多種版本時我們會用編號標記來)
下面是一個Unix里echo命令的實現這個命令會在單行內打印命令行參數。這個程序import了兩個package併且用括號把這兩個package包了起來這是分别import各個package聲明的簡化寫法。當然了你分開來寫import也沒有什麽問題隻是一般爲了方便我們都會像下面這樣來導入多個package。我們自己寫的導入順序併不重要因爲gofmt工具會幫助我們按照字母順序來排列好這些導入包名。本書中如果一個例子有多種版本時我們會用編號標記來)
```go
gopl.io/ch1/echo1
@@ -56,7 +56,7 @@ s = s + sep + os.Args[i]
運算符+=是一個賦值運算符(assignment operator),每一種數值和邏輯運算符,例如*或者+都有其對應的賦值運算符。
echo程序可以每循環一次輸一個參數不過我們這里的版本是不斷地將其結果連接到一個字符串的末尾。s這個字符串在聲明的時候是一個空字符串而之後循環每次都會被在末尾添加一段字符串第一次迭代之後一個空格會被插入到字符串末尾所以每插入一個新值都會和前一個中間有一個空格隔開。這是一種非線性的操作當我們的參數數量變得龐大的時候當然不是説這里的echo一般echo也不會有太多參數其運行開銷也會變得龐大。下面我們會介紹一繫列的echo改進版來應對這里説到的運行效率低下。
echo程序可以每循環一次輸一個參數不過我們這里的版本是不斷地將其結果連接到一個字符串的末尾。s這個字符串在聲明的時候是一個空字符串而之後循環每次都會被在末尾添加一段字符串第一次迭代之後一個空格會被插入到字符串末尾所以每插入一個新值都會和前一個中間有一個空格隔開。這是一種非線性的操作當我們的參數數量變得龐大的時候當然不是説這里的echo一般echo也不會有太多參數其運行開銷也會變得龐大。下面我們會介紹一繫列的echo改進版來應對這里説到的運行效率低下。
在for循環中我們用到了i來做下標索引可以看到我們用了:=符號來給i進行初始化和賦值這是var xxx=yyy的一種簡寫形式Go語言會根據等號右邊的值的類型自動判斷左邊的值類型下一章會對這一點進行詳細説明。
@@ -141,17 +141,17 @@ func main() {
}
```
最後,如果我們對輸的格式也不是很關心,隻是想簡單地輸值得的話還可以像下面這麽寫Println函數會爲我們自動格式化輸
最後,如果我們對輸的格式也不是很關心,隻是想簡單地輸值得的話還可以像下面這麽寫Println函數會爲我們自動格式化輸
```go
fmt.Println(os.Args[1:])
```
這個輸結果和前面的string.Join得到的結果很相似隻是被自動地放到了一個方括號里對slice調用Println函數都會被打印成這樣形式的結果。
這個輸結果和前面的string.Join得到的結果很相似隻是被自動地放到了一個方括號里對slice調用Println函數都會被打印成這樣形式的結果。
**練習 1.1** 脩改echo程序使其能夠打印os.Args[0]。
**練習 1.2** 脩改echo程序使其打印value和index每個value和index顯示一行。
**練習 1.3** 上手實踐前面提到的strings.Join和直接Println併觀察輸結果的區别。
**練習 1.3** 上手實踐前面提到的strings.Join和直接Println併觀察輸結果的區别。

View File

@@ -1,8 +1,8 @@
## 1.3. 査找重複的行
文件拷貝、文件打印、文件蒐索、文件排序、文件統計類的程序一般都會有比較相似的程序結構:一個處理輸入的循環,在每一個輸入元素上執行計算處理,在處理的同時或者處理完成之後進行結果輸。我們會展示一個叫dup程序的三個版本這個程序的靈感來自於linux的uniq命令我們的程序將會找到相鄰的重複的行。這個程序提供的模式可以很方便地被脩改來完成不同的需求。
文件拷貝、文件打印、文件蒐索、文件排序、文件統計類的程序一般都會有比較相似的程序結構:一個處理輸入的循環,在每一個輸入元素上執行計算處理,在處理的同時或者處理完成之後進行結果輸。我們會展示一個叫dup程序的三個版本這個程序的靈感來自於linux的uniq命令我們的程序將會找到相鄰的重複的行。這個程序提供的模式可以很方便地被脩改來完成不同的需求。
第一個版本的dup會輸標準輸入流中的現多次的行,在行內容前會有其現次數的計數。這個程序將引入if表達式map內置數據結構和bufio的package。
第一個版本的dup會輸標準輸入流中的現多次的行,在行內容前會有其現次數的計數。這個程序將引入if表達式map內置數據結構和bufio的package。
```go
gopl.io/ch1/dup1
@@ -47,7 +47,7 @@ counts[line] = counts[line] + 1
在這里我們又用了一個range的循環來打印結果這次range是被用在map這個數據結構之上。這一次的情況和上次比較類似range會返迴兩個值一個key和在map對應這個key的value。對map進行range循環時其迭代順序是不確定的從實踐來看很可能每次運行都會有不一樣的結果譯註這是Go語言的設計者有意爲之的因爲其底層實現不保證插入順序和遍歷順序一致也希望程序員不要依賴遍歷時的順序所以榦脆直接在遍歷的時候做了隨機化處理醉了。補充好像説隨機序可以防止某種類型的攻擊雖然不太明白但是感覺還蠻厲害的來避免程序員在業務中依賴遍歷時的順序。
然後輪到我們例子中的bufio這個package了這個package主要的目的是幫助我們更方便有效地處理程序的輸入和輸。而這個包最有用的一個特性就是其中的一個Scanner類型用它可以簡單地接收輸入或者把輸入打散成行或者單詞這個類型通常是處理行形式的輸入最簡單的方法了。
然後輪到我們例子中的bufio這個package了這個package主要的目的是幫助我們更方便有效地處理程序的輸入和輸。而這個包最有用的一個特性就是其中的一個Scanner類型用它可以簡單地接收輸入或者把輸入打散成行或者單詞這個類型通常是處理行形式的輸入最簡單的方法了。
本程序中用了一個短變量聲明來創建一個buffio.Scanner對象
@@ -57,9 +57,9 @@ input := bufio.NewScanner(os.Stdin)
scanner對象可以從程序的標準輸入中讀取內容。對input.Scanner的每一次調用都會調入一個新行併且會自動將其行末的換行符去掉其結果可以用input.Text()得到。Scan方法在讀到了新行的時候會返迴true而在沒有新行被讀入時會返迴false。
例子中還有一個fmt.Printf這個函數和C繫的其它語言里的那個printf函數差不多都是格式化輸的方法。fmt.Printf的第一個參數卽是輸內容的格式規約,每一個參數如何格式化是取決於在格式化字符串里現的“轉換字符”,這個字符串是跟着%號後的一個字母。比如%d表示以一個整數的形式來打印一個變量而%s則表示以string形式來打印一個變量。
例子中還有一個fmt.Printf這個函數和C繫的其它語言里的那個printf函數差不多都是格式化輸的方法。fmt.Printf的第一個參數卽是輸內容的格式規約,每一個參數如何格式化是取決於在格式化字符串里現的“轉換字符”,這個字符串是跟着%號後的一個字母。比如%d表示以一個整數的形式來打印一個變量而%s則表示以string形式來打印一個變量。
Printf有一大堆這種轉換Go語言程序員把這些叫做verb動詞。下面的表格列了常用的動詞,當然了不是全部,但基本也夠用了。
Printf有一大堆這種轉換Go語言程序員把這些叫做verb動詞。下面的表格列了常用的動詞,當然了不是全部,但基本也夠用了。
```
%d int變量
@@ -69,12 +69,12 @@ Printf有一大堆這種轉換Go語言程序員把這些叫做verb動詞
%c rune (Unicode碼點)Go語言里特有的Unicode字符類型
%s string
%q 帶雙引號的字符串 "abc" 或 帶單引號的 rune 'c'
%v 會將任意變量以易讀的形式打印
%v 會將任意變量以易讀的形式打印
%T 打印變量的類型
%% 字符型百分比標誌(%符號本身,沒有其他操作)
```
dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來表示不可見字符。Printf默認不會在輸內容後加上換行符。按照慣例用來格式化的函數都會在末尾以f字母結尾譯註f後綴對應format或fmt縮寫比如log.Printffmt.Errorf同時還有一繫列對應以ln結尾的函數譯註ln後綴對應line縮寫這些函數默認以%v來格式化他們的參數併且會在輸結束後在最後自動加上一個換行符。
dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來表示不可見字符。Printf默認不會在輸內容後加上換行符。按照慣例用來格式化的函數都會在末尾以f字母結尾譯註f後綴對應format或fmt縮寫比如log.Printffmt.Errorf同時還有一繫列對應以ln結尾的函數譯註ln後綴對應line縮寫這些函數默認以%v來格式化他們的參數併且會在輸結束後在最後自動加上一個換行符。
許多程序從標準輸入中讀取數據像上面的例子那樣。除此之外還可能從一繫列的文件中讀取。下一個dup程序就是從標準輸入中讀到一些文件名用os.Open函數來打開每一個文件穫取內容的。
@@ -124,7 +124,7 @@ func countLines(f *os.File, counts map[string]int) {
os.Open函數會返迴兩個值。第一個值是一個打開的文件類型(*os.File)這個對象在下面的程序中被Scanner讀取。
os.Open返迴的第二個值是一個Go語言內置的error類型。如果這個error和內置值的nil譯註相當於其它語言里的NULL相等的話説明文件被成功的打開了。之後文件被讀取一直到文件的最後文件的Close方法關閉該文件併釋放相應的占用一切資源。另一方面如果err的值不是nil的話那説明在打開文件的時候了某種錯誤。這種情況下error類型的值會描述具體的問題。我們例子里的簡單錯誤處理會在標準錯誤流中用Fprintf和%v來格式化該錯誤字符串。然後繼續處理下一個文件continue語句會直接跳過之後的語句直接開始執行下一個循環迭代。
os.Open返迴的第二個值是一個Go語言內置的error類型。如果這個error和內置值的nil譯註相當於其它語言里的NULL相等的話説明文件被成功的打開了。之後文件被讀取一直到文件的最後文件的Close方法關閉該文件併釋放相應的占用一切資源。另一方面如果err的值不是nil的話那説明在打開文件的時候了某種錯誤。這種情況下error類型的值會描述具體的問題。我們例子里的簡單錯誤處理會在標準錯誤流中用Fprintf和%v來格式化該錯誤字符串。然後繼續處理下一個文件continue語句會直接跳過之後的語句直接開始執行下一個循環迭代。
我們在本書中早期的例子中做了比較詳盡的錯誤處理當然了在實際編碼過程中像os.Open這類的函數是一定要檢査其返迴的error值的爲了減少例子程序的代碼量我們姑且簡化掉這些不太可能返迴錯誤的處理邏輯。後面的例子里我們會跳過錯誤檢査。在5.4節中我們會對錯誤處理做更詳細的闡述。
@@ -167,9 +167,9 @@ func main() {
}
```
ReadFile函數返迴一個byte的slice這個slice必鬚被轉換爲string之後能夠用string.Split方法來進行處理。我們在3.5.4節中會更詳細地講解string和byte slice字節數組
ReadFile函數返迴一個byte的slice這個slice必鬚被轉換爲string之後能夠用string.Split方法來進行處理。我們在3.5.4節中會更詳細地講解string和byte slice字節數組
在更底層一些的地方bufio.Scannerioutil.ReadFile和ioutil.WriteFile使用的是*os.File的Read和Write方法不過一般程序員併不需要去直接了解到其底層實現細節在bufio和io/ioutil包中提供的方法已經足夠好用。
**練習 1.4** 脩改dup2使其可以打印重複的行分别現在哪些文件。
**練習 1.4** 脩改dup2使其可以打印重複的行分别現在哪些文件。

View File

@@ -1,10 +1,10 @@
## 1.4. GIF動畵
下面的程序會演示Go語言標準庫里的image這個package的用法我們會用這個包來生成一繫列的bit-mapped圖然後將這些圖片編碼爲一個GIF動畵。我們生成的圖形名字叫利薩如圖形(Lissajous figures)這種效果是在1960年代的老電影里現的一種視覺特效。它們是協振子在兩個緯度上振動所産生的麴線比如兩個sin正絃波分别在x軸和y軸輸入會産生的麴線。圖1.1是這樣的一個例子:
下面的程序會演示Go語言標準庫里的image這個package的用法我們會用這個包來生成一繫列的bit-mapped圖然後將這些圖片編碼爲一個GIF動畵。我們生成的圖形名字叫利薩如圖形(Lissajous figures)這種效果是在1960年代的老電影里現的一種視覺特效。它們是協振子在兩個緯度上振動所産生的麴線比如兩個sin正絃波分别在x軸和y軸輸入會産生的麴線。圖1.1是這樣的一個例子:
![](../images/ch1-01.png)
譯註:要看這個程序的結果,需要將標準輸重定向到一個GIF圖像文件使用 `./lissajous > output.gif` 命令。下面是GIF圖像動畵效果
譯註:要看這個程序的結果,需要將標準輸重定向到一個GIF圖像文件使用 `./lissajous > output.gif` 命令。下面是GIF圖像動畵效果
![](../images/ch1-01.gif)
@@ -69,17 +69,17 @@ bla kIndex)
當我們import了一個包路徑包含有多個單詞的package時比如image/colorimage和color兩個單詞通常我們隻需要用最後那個單詞表示這個包就可以。所以當我們寫color.White時這個變量指向的是image/color包里的變量同理gif.GIF是屬於image/gif包里的變量。
這個程序里的常量聲明給了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會現在包級别所以這些常量在整個包中都是可以共享的或者你也可以把常量聲明定義在函數體內部那麽這種常量就隻能在函數體內用。目前常量聲明的值必鬚是一個數字值、字符串或者一個固定的boolean值。
這個程序里的常量聲明給了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會現在包級别所以這些常量在整個包中都是可以共享的或者你也可以把常量聲明定義在函數體內部那麽這種常量就隻能在函數體內用。目前常量聲明的值必鬚是一個數字值、字符串或者一個固定的boolean值。
[]color.Color{...}和gif.GIF{...}這兩個表達式就是我們説的複合聲明4.2和4.4.1節有説明。這是實例化Go語言里的複合類型的一種寫法。這里的前者生成的是一個slice切片後者生成的是一個struct結構體。
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)點來染黑色。
main函數調用了lissajous函數併且用它來向標準輸中打印信息所以下面這個命令會像圖1.1中産生一個GIF動畵。
main函數調用了lissajous函數併且用它來向標準輸中打印信息所以下面這個命令會像圖1.1中産生一個GIF動畵。
```
$ go build gopl.io/ch1/lissajous

View File

@@ -2,7 +2,7 @@
對於很多現代應用來説訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go語言在net這個強大package的幫助下提供了一繫列的package來做這件事情使用這些包可以更簡單地用網絡收發信息還可以建立更底層的網絡連接編寫服務器程序。在這些情景下Go語言原生的併發特性在第八章中會介紹就顯得尤其好用了。
爲了最簡單地展示基於HTTP穫取信息的方式下面給一個示例程序fetch這個程序將穫取對應的url併將其源文本打印這個例子的靈感來源於curl工具譯註unix下的一個網絡相關的工具。當然了curl提供的功能更爲複雜豐富這里我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。
爲了最簡單地展示基於HTTP穫取信息的方式下面給一個示例程序fetch這個程序將穫取對應的url併將其源文本打印這個例子的靈感來源於curl工具譯註unix下的一個網絡相關的工具。當然了curl提供的功能更爲複雜豐富這里我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。
```go
gopl.io/ch1/fetch
@@ -34,7 +34,7 @@ func main() {
}
```
這個程序從兩個package中導入了函數net/http和io/ioutil包http.Get函數是創建HTTP請求的函數如果穫取過程沒有那麽會在resp這個結構體中得到訪問的請求結果。resp的Body字段包括一個可讀的服務器響應流。這之後ioutil.ReadAll函數從response中讀取到全部內容其結果保存在變量b中。resp.Body.Close這一句會關閉resp的Body流防止資源洩露Printf函數會將結果b寫到標準輸流中。
這個程序從兩個package中導入了函數net/http和io/ioutil包http.Get函數是創建HTTP請求的函數如果穫取過程沒有那麽會在resp這個結構體中得到訪問的請求結果。resp的Body字段包括一個可讀的服務器響應流。這之後ioutil.ReadAll函數從response中讀取到全部內容其結果保存在變量b中。resp.Body.Close這一句會關閉resp的Body流防止資源洩露Printf函數會將結果b寫到標準輸流中。
```
$ go build gopl.io/ch1/fetch
@@ -65,5 +65,5 @@ fetch: Get http://gopl.io: dial tcp: lookup gopl.io: getaddrinfow: No such host
**練習 1.8** 脩改fetch這個范例如果輸入的url參數沒有 `http://` 前綴的話爲這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。
**練習 1.9** 脩改fetch打印HTTP協議的狀態碼可以從resp.Status變量得到該狀態碼。
**練習 1.9** 脩改fetch打印HTTP協議的狀態碼可以從resp.Status變量得到該狀態碼。

View File

@@ -2,7 +2,7 @@
Go語言最有意思併且最新奇的特性就是其對併發編程的支持了。併發編程是一個大話題在第八章和第九章中會專門講到。這里我們隻淺嚐輒止地來體驗一下Go語言里的goroutine和channel。
下面的例子fetchall和上面的fetch程序所要做的工作是一致的但是這個fetchall的特别之處在於它會同時去穫取所有的URL所以這個程序的穫取時間不會超過執行時間最長的那一個任務而不會像前面的fetch程序一樣執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印穫取的內容大小和經過的時間不會像上面那樣打印穫取的內容。
下面的例子fetchall和上面的fetch程序所要做的工作是一致的但是這個fetchall的特别之處在於它會同時去穫取所有的URL所以這個程序的穫取時間不會超過執行時間最長的那一個任務而不會像前面的fetch程序一樣執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印穫取的內容大小和經過的時間不會像上面那樣打印穫取的內容。
```go
gopl.io/ch1/fetchall
@@ -61,8 +61,8 @@ $ ./fetchall https://golang.org http://gopl.io https://godoc.org
goroutine是一種函數的併發執行方式而channel是用來在goroutine之間進行參數傳遞。main函數也是運行在一個goroutine中而go function則表示創建一個新的goroutine併在這個這個新的goroutine里執行這個函數。
main函數中用make函數創建了一個傳遞string類型參數的channel對每一個命令行參數我們都用go這個關鍵字來創建一個goroutine併且讓函數在這個goroutine異步執行http.Get方法。這個程序里的io.Copy會把響應的Body內容拷貝到ioutil.Discard輸流中譯註這是一個垃圾桶可以向里面寫一些不需要的數據因爲我們需要這個方法返迴的字節數但是又不想要其內容。每當請求返迴內容時fetch函數都會往ch這個channel里寫入一個字符串由main函數里的第二個for循環來處理併打印channel里的這個字符串。
main函數中用make函數創建了一個傳遞string類型參數的channel對每一個命令行參數我們都用go這個關鍵字來創建一個goroutine併且讓函數在這個goroutine異步執行http.Get方法。這個程序里的io.Copy會把響應的Body內容拷貝到ioutil.Discard輸流中譯註這是一個垃圾桶可以向里面寫一些不需要的數據因爲我們需要這個方法返迴的字節數但是又不想要其內容。每當請求返迴內容時fetch函數都會往ch這個channel里寫入一個字符串由main函數里的第二個for循環來處理併打印channel里的這個字符串。
當一個goroutine嚐試在一個channel上做send或者receive操作時這個goroutine會阻塞在調用處直到另一個goroutine往這個channel里寫入、或者接收了值這樣兩個goroutine會繼續執行操作channel完成之後的邏輯。在這個例子中每一個fetch函數在執行時都會往channel里發送一個值(ch <- expression)主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串可以避免在goroutine異步執行時同時結束
當一個goroutine嚐試在一個channel上做send或者receive操作時這個goroutine會阻塞在調用處直到另一個goroutine往這個channel里寫入、或者接收了值這樣兩個goroutine會繼續執行操作channel完成之後的邏輯。在這個例子中每一個fetch函數在執行時都會往channel里發送一個值(ch <- expression)主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串可以避免在goroutine異步執行時同時結束
**練習 1.10** 找一個數據量比較大的網站用本小節中的程序調研網站的緩存策略對每個URL執行兩遍請求査看兩次時間是否有較大的差别併且每次穫取到的響應內容是否一致脩改本節中的程序將響應結果輸以便於進行對比
**練習 1.10** 找一個數據量比較大的網站用本小節中的程序調研網站的緩存策略對每個URL執行兩遍請求査看兩次時間是否有較大的差别併且每次穫取到的響應內容是否一致脩改本節中的程序將響應結果輸以便於進行對比

View File

@@ -24,7 +24,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
}
```
我們隻用了八九行代碼就實現了一個個Web服務程序這都是多虧了標準庫里的方法已經幫我們處理了大量的工作。main函數會將所有發送到/路徑下的請求和handler函數關聯起來/開頭的請求其實就是所有發送到當前站點上的請求我們的服務跑在了8000端口上。發送到這個服務的“請求”是一個http.Request類型的對象這個對象中包含了請求中的一繫列相關字段其中就包括我們需要的URL。當請求到達服務器時這個請求會被傳給handler函數來處理這個函數會將/hello這個路徑從請求的URL中解析來,然後把其發送到響應中,這里我們用的是標準輸流的fmt.Fprintf。Web服務會在第7.7節中詳細闡述。
我們隻用了八九行代碼就實現了一個個Web服務程序這都是多虧了標準庫里的方法已經幫我們處理了大量的工作。main函數會將所有發送到/路徑下的請求和handler函數關聯起來/開頭的請求其實就是所有發送到當前站點上的請求我們的服務跑在了8000端口上。發送到這個服務的“請求”是一個http.Request類型的對象這個對象中包含了請求中的一繫列相關字段其中就包括我們需要的URL。當請求到達服務器時這個請求會被傳給handler函數來處理這個函數會將/hello這個路徑從請求的URL中解析來,然後把其發送到響應中,這里我們用的是標準輸流的fmt.Fprintf。Web服務會在第7.7節中詳細闡述。
讓我們在後颱運行這個服務程序。如果你的操作繫統是Mac OS X或者Linux那麽在運行命令的末尾加上一個&符號卽可讓程序簡單地跑在後颱而在windows下你需要在另外一個命令行窗口去運行這個程序了。
@@ -46,7 +46,7 @@ URL.Path = "/help"
![](../images/ch1-02.png)
在這個服務的基礎上疊加特性是很容易的。一種比較實用的脩改是爲訪問的url添加某種狀態。比如下面這個版本輸了同樣的內容但是會對請求的次數進行計算對URL的請求結果會包含各種URL被訪問的總次數直接對/count這個URL的訪問要除外。
在這個服務的基礎上疊加特性是很容易的。一種比較實用的脩改是爲訪問的url添加某種狀態。比如下面這個版本輸了同樣的內容但是會對請求的次數進行計算對URL的請求結果會包含各種URL被訪問的總次數直接對/count這個URL的訪問要除外。
```go
gopl.io/ch1/server2
@@ -87,7 +87,7 @@ func counter(w http.ResponseWriter, r *http.Request) {
這個服務器有兩個請求處理函數請求的url會決定具體調用哪一個對/count這個url的請求會調用到count這個函數其它所有的url都會調用默認的處理函數。如果你的請求pattern是以/結尾那麽所有以該url爲前綴的url都會被這條規則匹配。在這些代碼的背後服務器每一次接收請求處理時都會另起一個goroutine這樣服務器就可以同一時間處理多數請求。然而在併發情況下假如眞的有兩個請求同一時刻去更新count那麽這個值可能併不會被正確地增加這個程序可能會被引發一個嚴重的bug競態條件參見9.1。爲了避免這個問題我們必鬚保證每次脩改變量的最多隻能有一個goroutine這也就是代碼里的mu.Lock()和mu.Unlock()調用將脩改count的所有行爲包在中間的目的。第九章中我們會進一步講解共享變量。
下面是一個更爲豐富的例子handler函數會把請求的http頭和請求的form數據都打印來,這樣可以讓檢査和調試這個服務更爲方便:
下面是一個更爲豐富的例子handler函數會把請求的http頭和請求的form數據都打印來,這樣可以讓檢査和調試這個服務更爲方便:
```go
gopl.io/ch1/server3
@@ -108,7 +108,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
}
```
我們用http.Request這個struct里的字段來輸下面這樣的內容:
我們用http.Request這個struct里的字段來輸下面這樣的內容:
```
GET /?q=query HTTP/1.1
@@ -119,7 +119,7 @@ RemoteAddr = "127.0.0.1:59911"
Form["q"] = ["query"]
```
可以看到這里的ParseForm被嵌套在了if語句中。Go語言允許這樣的一個簡單的語句結果作爲循環的變量聲明現在if語句的最前面這一點對錯誤處理很有用處。我們還可以像下面這樣寫當然看起來就長了一些
可以看到這里的ParseForm被嵌套在了if語句中。Go語言允許這樣的一個簡單的語句結果作爲循環的變量聲明現在if語句的最前面這一點對錯誤處理很有用處。我們還可以像下面這樣寫當然看起來就長了一些
```go
err := r.ParseForm()
@@ -130,11 +130,11 @@ if err != nil {
用if和ParseForm結合可以讓代碼更加簡單併且可以限製err這個變量的作用域這麽做是很不錯的。我們會在2.7節中講解作用域。
在這些程序中,我們看到了很多不同的類型被輸到標準輸流中。比如前面的fetch程序就把HTTP的響應數據拷貝到了os.Stdout或者在lissajous程序里我們輸的是一個文件。fetchall程序則完全忽略到了HTTP的響應體隻是計算了一下響應體的大小這個程序中把響應體拷貝到了ioutil.Discard。在本節的web服務器程序中則是用fmt.Fprintf直接寫到了http.ResponseWriter中。
在這些程序中,我們看到了很多不同的類型被輸到標準輸流中。比如前面的fetch程序就把HTTP的響應數據拷貝到了os.Stdout或者在lissajous程序里我們輸的是一個文件。fetchall程序則完全忽略到了HTTP的響應體隻是計算了一下響應體的大小這個程序中把響應體拷貝到了ioutil.Discard。在本節的web服務器程序中則是用fmt.Fprintf直接寫到了http.ResponseWriter中。
盡管這三種具體的實現流程併不太一樣,他們都實現一個共同的接口,卽當它們被調用需要一個標準流輸時都可以滿足。這個接口叫作io.Writer在7.1節中會詳細討論。
盡管這三種具體的實現流程併不太一樣,他們都實現一個共同的接口,卽當它們被調用需要一個標準流輸時都可以滿足。這個接口叫作io.Writer在7.1節中會詳細討論。
Go語言的接口機製會在第7章中講解爲了在這里簡單説明接口能做什麽讓我們簡單地將這里的web服務器和之前寫的lissajous函數結合起來這樣GIF動畵可以被寫到HTTP的客戶端而不是之前的標準輸流。隻要在web服務器的代碼里加入下面這幾行。
Go語言的接口機製會在第7章中講解爲了在這里簡單説明接口能做什麽讓我們簡單地將這里的web服務器和之前寫的lissajous函數結合起來這樣GIF動畵可以被寫到HTTP的客戶端而不是之前的標準輸流。隻要在web服務器的代碼里加入下面這幾行。
```Go
handler := func(w http.ResponseWriter, r *http.Request) {

View File

@@ -15,7 +15,7 @@ default:
}
```
在翻轉硬幣的時候例子里的coinflip函數返迴幾種不同的結果每一個case都會對應個返迴結果這里需要註意Go語言併不需要顯式地去在每一個case後寫break語言默認執行完case後的邏輯語句會自動退。當然了如果你想要相鄰的幾個case都執行同一邏輯的話需要自己顯式地寫上一個fallthrough語句來覆蓋這種默認行爲。不過fallthrough語句在一般的編程中用到得很少。
在翻轉硬幣的時候例子里的coinflip函數返迴幾種不同的結果每一個case都會對應個返迴結果這里需要註意Go語言併不需要顯式地去在每一個case後寫break語言默認執行完case後的邏輯語句會自動退。當然了如果你想要相鄰的幾個case都執行同一邏輯的話需要自己顯式地寫上一個fallthrough語句來覆蓋這種默認行爲。不過fallthrough語句在一般的編程中用到得很少。
Go語言里的switch還可以不帶操作對象譯註switch不帶操作對象時默認用true值代替然後將每個case的表達式和true值進行比較可以直接羅列多種條件像其它語言里面的多個if else一樣下面是一個例子

View File

@@ -1,5 +1,5 @@
# 第1章 入門
本章會介紹Go語言里的一些基本組件。我們希望用信息和例子盡快帶你入門。本章和之後章節的例子都是針對眞實的開發案例給。本章我們隻是簡單地爲你介紹一些Go語言的入門例子從簡單的文件處理、圖像處理到互聯網併發客戶端和服務端程序。當然在第一章我們不會詳盡地一一去説明細枝末節不過用這些程序來學習一門新語言肯定是很有效的。
本章會介紹Go語言里的一些基本組件。我們希望用信息和例子盡快帶你入門。本章和之後章節的例子都是針對眞實的開發案例給。本章我們隻是簡單地爲你介紹一些Go語言的入門例子從簡單的文件處理、圖像處理到互聯網併發客戶端和服務端程序。當然在第一章我們不會詳盡地一一去説明細枝末節不過用這些程序來學習一門新語言肯定是很有效的。
當你學習一門新語言時你會用這門新語言去重寫自己以前熟悉語言例子的傾向。在學習Go語言的過程中盡量避免這麽做。我們會向你演示如何能寫好的Go語言程序所以請使用這里的代碼作爲你寫自己的Go程序時的指南。
當你學習一門新語言時你會用這門新語言去重寫自己以前熟悉語言例子的傾向。在學習Go語言的過程中盡量避免這麽做。我們會向你演示如何能寫好的Go語言程序所以請使用這里的代碼作爲你寫自己的Go程序時的指南。