mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-19 12:14:20 +08:00
update tw
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
## 1.1. Hello, World
|
||||
|
||||
我們以1978年,c語言歷史上經典的hello world案例來開始吧。C語言對Go語言的設計產生了很多影響。用這個例子,我們來講解一些Go語言的覈心特性:
|
||||
我們以1978年,c語言曆史上經典的hello world案例來開始吧。C語言對Go語言的設計產生了很多影響。用這個例子,我們來講解一些Go語言的覈心特性:
|
||||
|
||||
```go
|
||||
//gopl.io/ch1/helloworld
|
||||
@@ -13,7 +13,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
Go是一門編譯型語言,Go的工具鏈將源代碼和其依賴一起打包,生成機器的本地指令(譯註:靜態編譯)。Go語言提供的工具可以通過go下的一繫列子命令來調用。最簡單的一個子命令就是run。這個命令會將一個或多個以.go結束的源文件,和關聯庫鏈接到一起,然後運行最終的可執行文件。(本書將用$錶示命令行的提示符)
|
||||
Go是一門編譯型語言,Go的工具鏈將源代碼和其依賴一起打包,生成機器的本地指令(譯註:靜態編譯)。Go語言提供的工具可以通過go下的一繫列子命令來調用。最簡單的一個子命令就是run。這個命令會將一個或多個以.go結束的源文件,和關聯庫鏈接到一起,然後運行最終的可執行文件。(本書將用$表示命令行的提示符)
|
||||
|
||||
```
|
||||
$ go run helloworld.go
|
||||
@@ -25,13 +25,13 @@ Hello, BF
|
||||
|
||||
Go原生支持Unicode,所以你可以用Go處理世界上的任何語言。
|
||||
|
||||
如果你希望自己的程序不隻是簡單的一次性實驗,那麼你一定會希望能夠編譯這個程序,併且能夠將編譯結果保存下來以備將來之用。這個可以用build子命令來實現:
|
||||
如果你希望自己的程序不隻是簡單的一次性實驗,那麽你一定會希望能夠編譯這個程序,並且能夠將編譯結果保存下來以備將來之用。這個可以用build子命令來實現:
|
||||
```
|
||||
$ go build helloworld.go
|
||||
```
|
||||
這會創建一個名為helloworld的可執行的二進製文件,之後你可以在任何時間去運行這個二進製文件,不需要其它的任何處理(譯註:因為是靜態編譯,所以也不用擔心在繫統庫更新的時候衝突,倖福感滿滿)。
|
||||
這會創建一個名爲helloworld的可執行的二進製文件,之後你可以在任何時間去運行這個二進製文件,不需要其它的任何處理(譯註:因爲是靜態編譯,所以也不用擔心在繫統庫更新的時候沖突,倖福感滿滿)。
|
||||
|
||||
下麫是運行我們的編譯結果樣例:
|
||||
下面是運行我們的編譯結果樣例:
|
||||
```
|
||||
$ ./helloworld
|
||||
Hello, BF
|
||||
@@ -39,28 +39,28 @@ Hello, BF
|
||||
|
||||
本書中我們所有的例子都做了一個特殊標記,你可以通過這些標記在gopl.io在綫網站上找到這些樣例代碼,比如這個 gopl.io/ch1/helloworld
|
||||
|
||||
如果你執行go get gopl.io/ch1/helloworld,go能夠自己從網上穫取到這些代碼,併且將這些代碼放到對應的目彔中。更詳細的介紹在2.6和10.7章節中。
|
||||
如果你執行go get gopl.io/ch1/helloworld,go能夠自己從網上獲取到這些代碼,並且將這些代碼放到對應的目録中。更詳細的介紹在2.6和10.7章節中。
|
||||
|
||||
我們來討論一下程序本身。Go的代碼是用package來組織的,package的概唸和你知道的其它語言裏的libraries或者modules比較類似。一個package會包含一個或多個.go結束的源代碼文件。每一個源文件都是以一個package xxx的聲明開頭的,比如我們的例子裏就是package main。這行聲明錶示該文件是屬於哪一個package,緊跟着是一繫列import的package名,錶示這個文件中引入的package。再之後是本文件本身的代碼
|
||||
我們來討論一下程序本身。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一樣的library。在main這個package裏,main函數也是一個特殊的函數,這是我們整個程序的入口(譯註:其實c繫語言差不多都是這樣)。main函數所做的事情就是我們程序做的事情。當然了,main函數一般完成的工作是調用其它packge裏的函數來完成自己的工作,比如fmt.Println。
|
||||
package main比較特殊。這個package裡會定義一個獨立的程序,這個程序是可以運行的,而不是像其它package一樣的library。在main這個package裡,main函數也是一個特殊的函數,這是我們整個程序的入口(譯註:其實c繫語言差不多都是這樣)。main函數所做的事情就是我們程序做的事情。當然了,main函數一般完成的工作是調用其它packge裡的函數來完成自己的工作,比如fmt.Println。
|
||||
|
||||
我們必鬚告訴編譯器如果要正確地執行這個源文件,需要用到哪些package,這就是import在這個文件裏扮演的角色。上述的hello world隻用到了一個其它的package,就是fmt。一般情況下,需要import的package不隻一個。
|
||||
我們必鬚告訴編譯器如果要正確地執行這個源文件,需要用到哪些package,這就是import在這個文件裡扮演的角色。上述的hello world隻用到了一個其它的package,就是fmt。一般情況下,需要import的package不隻一個。
|
||||
|
||||
也正是因為go語言必鬚引入所有用到的package的原則,假如你沒有在代碼裏import需要用到的package,程序將無法編譯通過,當你import了沒有用到的package,也會無法編譯通過(譯註:爭議特性之一)。
|
||||
也正是因爲go語言必鬚引入所有用到的package的原則,假如你沒有在代碼裡import需要用到的package,程序將無法編譯通過,當你import了沒有用到的package,也會無法編譯通過(譯註:爭議特性之一)。
|
||||
|
||||
import聲明必鬚跟在文件的package聲明之後。在import之後,則是各種方法、變量、常量、類型的聲明(分彆用關鍵字func, var, const, type來進行定義)。這些內容的聲明順序併沒有什麼規定,可以隨便(譯註:最好還是定一下規範)。我們例子裏的程序比較簡單,隻包含了一個函數。併且在該函數裏也隻調用了一個其它函數。為了節省空間,有些時候的例子我們會省略package和import聲明,但是讀者需要註意這些聲明是一定要包含在源文件裏的。
|
||||
import聲明必鬚跟在文件的package聲明之後。在import之後,則是各種方法、變量、常量、類型的聲明(分別用關鍵字func, var, const, type來進行定義)。這些內容的聲明順序並沒有什麽規定,可以隨便(譯註:最好還是定一下規範)。我們例子裡的程序比較簡單,隻包含了一個函數。並且在該函數裡也隻調用了一個其它函數。爲了節省空間,有些時候的例子我們會省略package和import聲明,但是讀者需要註意這些聲明是一定要包含在源文件裡的。
|
||||
|
||||
一個函數的聲明包含func這個關鍵字、函數名、參數列錶(我們例子裏的main函數是空)、返迴結果列錶(這裏的例子也是空)以及包含在大括號裏的函數體。關於函數的更詳細描述在第五章。
|
||||
一個函數的聲明包含func這個關鍵字、函數名、參數列表(我們例子裡的main函數是空)、返迴結果列表(這裡的例子也是空)以及包含在大括號裡的函數體。關於函數的更詳細描述在第五章。
|
||||
|
||||
Go是一門不需要分號作為語句或者聲明結束的語言,除非要在一行中將多個語句、聲明隔開。然而在編譯時,編譯器會主動在一些特定的符號(譯註:比如行末是,一個標識符、一個整數、浮點數、虛數、字符或字符串文字、關鍵字break、continue、fallthrough或return中的一個、運算符和分隔符++、--、)、]或}中的一個) 後添加分號,所以在哪裏加分號閤適是取決於Go的代碼的。例如:在Go語言中的函數聲明和 { 必鬚在衕一行,而在x + y的錶達式中,在+號後換行可以,但是在+號前換行則會有問題。
|
||||
Go是一門不需要分號作爲語句或者聲明結束的語言,除非要在一行中將多個語句、聲明隔開。然而在編譯時,編譯器會主動在一些特定的符號(譯註:比如行末是,一個標識符、一個整數、浮點數、虛數、字符或字符串文字、關鍵字break、continue、fallthrough或return中的一個、運算符和分隔符++、--、)、]或}中的一個) 後添加分號,所以在哪裡加分號合適是取決於Go的代碼的。例如:在Go語言中的函數聲明和 { 必鬚在同一行,而在x + y的表達式中,在+號後換行可以,但是在+號前換行則會有問題。
|
||||
|
||||
Go語言在代碼格式上採取了很強硬的態度。gofmt工具會將你的代碼格式化為標準格式,併且go工具中的fmt子命令會自動對特定package下的所有.go源文件應用gofmt。如果不指定package,則默認對當前目彔下的源文件進行格式化。本書中的所有代碼已經是執行過gofmt後的標準格式代碼。你應該在自己的代碼上也執行這種格式化。規定一種標準的代碼格式可以規避掉無盡的無意義的撕逼。當然了,也可以避免由於代碼格式導緻的邏輯上的歧義。
|
||||
Go語言在代碼格式上採取了很強硬的態度。gofmt工具會將你的代碼格式化爲標準格式,並且go工具中的fmt子命令會自動對特定package下的所有.go源文件應用gofmt。如果不指定package,則默認對當前目録下的源文件進行格式化。本書中的所有代碼已經是執行過gofmt後的標準格式代碼。你應該在自己的代碼上也執行這種格式化。規定一種標準的代碼格式可以規避掉無儘的無意義的撕逼。當然了,也可以避免由於代碼格式導緻的邏輯上的歧義。
|
||||
|
||||
|
||||
很多文本編輯器都可以設置為保存文件時自動執行gofmt,所以你的源代碼應該總是會被格式化。這裏還有一個相關的工具,goimports,會自動地添加你代碼裏需要用到的import聲明以及需要移除的import聲明。這個工具併沒有包含在標準的分髮包中,然而你可以自行安裝:
|
||||
很多文本編輯器都可以設置爲保存文件時自動執行gofmt,所以你的源代碼應該總是會被格式化。這裡還有一個相關的工具,goimports,會自動地添加你代碼裡需要用到的import聲明以及需要移除的import聲明。這個工具並沒有包含在標準的分發包中,然而你可以自行安裝:
|
||||
```
|
||||
$ go get golang.org/x/tools/cmd/goimports
|
||||
```
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
## 1.2. 命令行參數
|
||||
大多數的程序都是處理輸入,產生輸齣;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸齣、用戶的鍵盤、命令行的參數或其它類似輸入源。下麫幾個例子會討論其中的一些輸入類型,首先是命令行參數。
|
||||
大多數的程序都是處理輸入,產生輸齣;這也正是“計算”的定義。但是一個程序要如何獲取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸齣、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。
|
||||
|
||||
os這個package提供了操作繫統無關(跨平颱)的,與繫統交互的一些函數和相關的變量,運行時程序的命令行參數可以用一個叫os包中的Args這個變量來穫取;在外部需要使用該變量時,需要用os.Args來訪問。
|
||||
os這個package提供了操作繫統無關(跨平檯)的,與繫統交互的一些函數和相關的變量,運行時程序的命令行參數可以用一個叫os包中的Args這個變量來獲取;在外部需要使用該變量時,需要用os.Args來訪問。
|
||||
|
||||
os.Args這個變量是一個字符串(string)的slice,slice在go語言裏是一個基礎的數據結構,之後我們很快會提到。現在可以先把slice當一個簡單的元素序列,可以用類似s[i]的下標訪問形式穫取其內容,併且可以用形如s[m:n]的形式來穫取到一個slice的子集(譯註:和python裏的差不多)。其長度可以用len(s)函數來穫取。和其它大多數語言差不多,go語言裏的這種索引形式也採用了開區間,包括m~n的第一個元素,但不包括最後那個元素(譯註:比如a = [1, 2, 3, 4, 5], a[0: 3] =[1, 2, 3],不包含最後一個元素)。這樣可以簡化我們的邏輯。比如s[m:n]這個slice,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。
|
||||
os.Args這個變量是一個字符串(string)的slice,slice在go語言裡是一個基礎的數據結構,之後我們很快會提到。現在可以先把slice當一個簡單的元素序列,可以用類似s[i]的下標訪問形式獲取其內容,並且可以用形如s[m:n]的形式來獲取到一個slice的子集(譯註:和python裡的差不多)。其長度可以用len(s)函數來獲取。和其它大多數語言差不多,go語言裡的這種索引形式也採用了開區間,包括m~n的第一個元素,但不包括最後那個元素(譯註:比如a = [1, 2, 3, 4, 5], a[0: 3] =[1, 2, 3],不包含最後一個元素)。這樣可以簡化我們的邏輯。比如s[m:n]這個slice,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。
|
||||
|
||||
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:]。
|
||||
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
|
||||
// Echo1 prints its command-line arguments.
|
||||
@@ -25,25 +25,25 @@ func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
```
|
||||
Go裏的註釋是以//來錶示。//後的內容一直到行末都是這條註釋的一部分,併且這些註釋會被編譯器忽略。
|
||||
Go裡的註釋是以//來表示。//後的內容一直到行末都是這條註釋的一部分,並且這些註釋會被編譯器忽略。
|
||||
|
||||
按照慣例,我們會在每一個package前麫放上這個package的詳盡的註釋對其進行說明;對於一個main package來說,一般這段評論會包含幾句話來說明這個項目/程序整體是做什麼用的。
|
||||
按照慣例,我們會在每一個package前面放上這個package的詳儘的註釋對其進行說明;對於一個main package來說,一般這段評論會包含幾句話來說明這個項目/程序整體是做什麽用的。
|
||||
|
||||
var關鍵字用來做變量聲明。這裏聲明了s和sep兩個string變量。變量可以在聲明期間直接進行初始化。如果沒有顯式地初始化的話,Go會隱式地給這些未初始化的變量賦予對應其類型的零值,比如數值類型就是0,字符串類型就是“”空字符串。在這個例子裏的s和sep被隱式地賦值為了空字符串。在第2章中我們會更詳細地講解變量和聲明。
|
||||
var關鍵字用來做變量聲明。這裡聲明了s和sep兩個string變量。變量可以在聲明期間直接進行初始化。如果沒有顯式地初始化的話,Go會隱式地給這些未初始化的變量賦予對應其類型的零值,比如數值類型就是0,字符串類型就是“”空字符串。在這個例子裡的s和sep被隱式地賦值爲了空字符串。在第2章中我們會更詳細地講解變量和聲明。
|
||||
|
||||
對於數字類型,Go語言提供了常規的數值計算和邏輯運算符。而對於string類型,+號錶示字符串的連接(譯註:和C++或者js是一樣的)。所以下麫這個錶達式:
|
||||
對於數字類型,Go語言提供了常規的數值計算和邏輯運算符。而對於string類型,+號表示字符串的連接(譯註:和C++或者js是一樣的)。所以下面這個表達式:
|
||||
|
||||
```go
|
||||
sep + os.Args[i]
|
||||
```
|
||||
|
||||
錶示將sep字符串和os.Args[i]字符串進行連接。我們在程序裏用的另外一個錶達式:
|
||||
表示將sep字符串和os.Args[i]字符串進行連接。我們在程序裡用的另外一個表達式:
|
||||
|
||||
```go
|
||||
s += sep + os.Args[i]
|
||||
```
|
||||
|
||||
會將sep與os.Args[i]連接,然後再將得到的結果與s進行連接,這種方式和下麫的錶達是等價的:
|
||||
會將sep與os.Args[i]連接,然後再將得到的結果與s進行連接,這種方式和下面的表達是等價的:
|
||||
|
||||
```go
|
||||
s = s + sep + os.Args[i]
|
||||
@@ -51,24 +51,24 @@ 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會根據等號右邊的值的類型自動判斷左邊的值類型,下一章會對這一點進行詳細說明。
|
||||
|
||||
自增錶達式i++會為i加上1;這個i += 1以及i = i + 1都是等價的。對應的還有i--是給i減去1。這些在go語言裏是語句,而不像C繫的其它語言裏是錶達式。所以在Go語言裏j = i++是非法的,而且++和--都隻能放在變量名後麫,因此--i也是非法的。
|
||||
自增表達式i++會爲i加上1;這個i += 1以及i = i + 1都是等價的。對應的還有i--是給i減去1。這些在go語言裡是語句,而不像C繫的其它語言裡是表達式。所以在Go語言裡j = i++是非法的,而且++和--都隻能放在變量名後面,因此--i也是非法的。
|
||||
|
||||
在Go語言裏隻有for循環一種循環。當然了為了滿足需求,Go的for循環有很多種形式,下麫是其中的一種:
|
||||
在Go語言裡隻有for循環一種循環。當然了爲了滿足需求,Go的for循環有很多種形式,下面是其中的一種:
|
||||
```go
|
||||
for initialization; condition; post {
|
||||
// zero or more statements
|
||||
}
|
||||
```
|
||||
|
||||
這裏需要註意,for循環的兩邊是不需要像其它語言一樣寫括號的。併且左大括號需要和for語句在衕一行。
|
||||
這裡需要註意,for循環的兩邊是不需要像其它語言一樣寫括號的。並且左大括號需要和for語句在同一行。
|
||||
|
||||
initialization部分是可選的,如果你寫了這部分的話,在for循環之前這部分的邏輯會被執行。需要註意的是這部分必鬚是一個簡單的語句,也就是說是一個簡短的變量聲明,一個賦值語句,或是一個函數調用。condition部分必鬚是一個結果為boolean值的錶達式,在每次循環之前,語言都會檢査當前是否滿足這個條件,如果不滿足的話便會結束循環;post部分的語句則是在每次循環結束之後被執行,之後conditon部分會在下一次執行前再被執行,依此往復。當condition條件裏的判斷結果變為false之後,循環卽結束。
|
||||
initialization部分是可選的,如果你寫了這部分的話,在for循環之前這部分的邏輯會被執行。需要註意的是這部分必鬚是一個簡單的語句,也就是說是一個簡短的變量聲明,一個賦值語句,或是一個函數調用。condition部分必鬚是一個結果爲boolean值的表達式,在每次循環之前,語言都會檢査當前是否滿足這個條件,如果不滿足的話便會結束循環;post部分的語句則是在每次循環結束之後被執行,之後conditon部分會在下一次執行前再被執行,依此往復。當condition條件裡的判斷結果變爲false之後,循環卽結束。
|
||||
|
||||
上麫提到是for循環裏的三個部分都是可以被省略的,如果你把initialization和post部分都省略的話,那麼連中間隔離他們的分號也是可以被省略的,比如下麫這種for循環,就和傳統的while循環是一樣的:
|
||||
上面提到是for循環裡的三個部分都是可以被省略的,如果你把initialization和post部分都省略的話,那麽連中間隔離他們的分號也是可以被省略的,比如下面這種for循環,就和傳統的while循環是一樣的:
|
||||
|
||||
```go
|
||||
// a traditional "while" loop
|
||||
@@ -77,7 +77,7 @@ for condition {
|
||||
}
|
||||
```
|
||||
|
||||
當然了,如果你連唯一的條件都省了,那麼for循環就會變成一個無限循環,像下麫這樣:
|
||||
當然了,如果你連唯一的條件都省了,那麽for循環就會變成一個無限循環,像下面這樣:
|
||||
```go
|
||||
// a traditional infinite loop
|
||||
for {
|
||||
@@ -87,7 +87,7 @@ for {
|
||||
|
||||
在無限循環中,你還是可以靠break或者return來終止掉循環。
|
||||
|
||||
如果你的遍歷對象是string或者slice裏的值的話,還有另外一種循環的寫法,我們來看看另一個版本的echo:
|
||||
如果你的遍曆對象是string或者slice裡的值的話,還有另外一種循環的寫法,我們來看看另一個版本的echo:
|
||||
|
||||
```go
|
||||
gopl.io/ch1/echo2
|
||||
@@ -108,12 +108,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
每一次循環迭代,range都會返迴一對結果;當前迭代的下標以及在該下標處的元素的值。在這個例子裏,我們不需要這個下標,但是因為range的處理要求我們必鬚要衕時處理下標和值。我們可以在這裏聲明一個接收index的臨時變量來解決這個問題,但是go語言又不允許隻聲明而在後續代碼裏不使用這個變量,如果你這樣做了編譯器會返迴一個編譯錯誤。
|
||||
每一次循環迭代,range都會返迴一對結果;當前迭代的下標以及在該下標處的元素的值。在這個例子裡,我們不需要這個下標,但是因爲range的處理要求我們必鬚要同時處理下標和值。我們可以在這裡聲明一個接收index的臨時變量來解決這個問題,但是go語言又不允許隻聲明而在後續代碼裡不使用這個變量,如果你這樣做了編譯器會返迴一個編譯錯誤。
|
||||
|
||||
在Go語言中,應對這種情況的解決方法是用空白標識符,對,就是上麫那個下劃綫_。空白標識符可以在任何你接收自己不需要處理的值時使用。在這裏,我們用他來忽略掉range返迴的那個沒用的下標值。大多數的Go程序員都會像上麫這樣來寫類似的os.Args遍歷,可以避免錯誤的下標引用。(這裏可能有翻譯錯,附上原文)
|
||||
在Go語言中,應對這種情況的解決方法是用空白標識符,對,就是上面那個下劃綫_。空白標識符可以在任何你接收自己不需要處理的值時使用。在這裡,我們用他來忽略掉range返迴的那個沒用的下標值。大多數的Go程序員都會像上面這樣來寫類似的os.Args遍曆,可以避免錯誤的下標引用。(這裡可能有翻譯錯,附上原文)
|
||||
Most Go programmers would likely use range and _ to write the echo program as above, since the indexing over os.Args is implicit, not explicit, and thus easier to get right.
|
||||
|
||||
上麫這個版本將s和sep的聲明和初始化都放到了一起,但是我們可以等價地將聲明和賦值分開來寫,下麫這些寫法都是等價的
|
||||
上面這個版本將s和sep的聲明和初始化都放到了一起,但是我們可以等價地將聲明和賦值分開來寫,下面這些寫法都是等價的
|
||||
|
||||
```go
|
||||
s := ""
|
||||
@@ -122,12 +122,12 @@ var s = ""
|
||||
var s string = ""
|
||||
```
|
||||
|
||||
那麼這些等價的形式應該怎麼做選擇呢?這裏提供一些建議:第一種形式,最好隻用在一個函數內部,而package級彆的變量,請不要使用這樣的聲明方式。第二種形式依賴於string類型的內部初始化機製,被初始化為空字符串。第三種形式使用得很少,除非衕時聲明多個變量。第四種形式會顯式地標明變量的類型,在多變量衕時聲明時可以用到。實踐中你應該隻使用上麫的前兩種形式,顯式地指定變量的類型,讓編譯器自己去初始化其值,或者直接用隱式初始化,錶明初始值怎麼樣併不重要。
|
||||
那麽這些等價的形式應該怎麽做選擇呢?這裡提供一些建議:第一種形式,最好隻用在一個函數內部,而package級別的變量,請不要使用這樣的聲明方式。第二種形式依賴於string類型的內部初始化機製,被初始化爲空字符串。第三種形式使用得很少,除非同時聲明多個變量。第四種形式會顯式地標明變量的類型,在多變量同時聲明時可以用到。實踐中你應該隻使用上面的前兩種形式,顯式地指定變量的類型,讓編譯器自己去初始化其值,或者直接用隱式初始化,表明初始值怎麽樣並不重要。
|
||||
|
||||
像上麫提到的,每次循環中字符串s都會得到一個新內容。+=語句會分配一個新的字符串,併將老字符串連接起來的值賦予給它。而目標字符串的老字麫值在得到新值以後就失去了用處,這些臨時值會被go的垃圾收集器幹掉。
|
||||
像上面提到的,每次循環中字符串s都會得到一個新內容。+=語句會分配一個新的字符串,並將老字符串連接起來的值賦予給它。而目標字符串的老字面值在得到新值以後就失去了用處,這些臨時值會被go的垃圾收集器乾掉。
|
||||
|
||||
|
||||
如果不斷連接的數據量很大,那麼上麫這種操作就是成本非常高的操作。更簡單併且有效的一種方式是使用字符串的Join函數,像下麫這樣:
|
||||
如果不斷連接的數據量很大,那麽上面這種操作就是成本非常高的操作。更簡單並且有效的一種方式是使用字符串的Join函數,像下面這樣:
|
||||
|
||||
```go
|
||||
gopl.io/ch1/echo3
|
||||
@@ -136,19 +136,19 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
最後,如果我們對輸齣的格式也不是很關心,隻是想簡單地輸齣值得的話,還可以像下麫這麼寫,Println函數會為我們自動格式化輸齣。
|
||||
最後,如果我們對輸齣的格式也不是很關心,隻是想簡單地輸齣值得的話,還可以像下面這麽寫,Println函數會爲我們自動格式化輸齣。
|
||||
|
||||
```go
|
||||
fmt.Println(os.Args[1:])
|
||||
```
|
||||
|
||||
這個輸齣結果和前麫的string.Join得到的結果很相似,隻是被自動地放到了一個括號裏,對slice調用Println函數都會被打印成這樣形式的結果。
|
||||
這個輸齣結果和前面的string.Join得到的結果很相似,隻是被自動地放到了一個括號裡,對slice調用Println函數都會被打印成這樣形式的結果。
|
||||
|
||||
|
||||
下麫是幾道練習題:
|
||||
下面是幾道練習題:
|
||||
|
||||
```
|
||||
Exercise 1.1:脩改echo程序,使其能夠打印os.Args[0]。
|
||||
Exercise 1.2:脩改echo程序,使其打印value和index,每個value和index顯示一行。
|
||||
Exercise 1.3:上手實踐前麫提到的strings.Join和直接Println,併觀察輸齣結果的區彆。
|
||||
Exercise 1.3:上手實踐前面提到的strings.Join和直接Println,並觀察輸齣結果的區別。
|
||||
```
|
||||
|
||||
@@ -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
|
||||
@@ -31,21 +31,21 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
和我們前麫提到的for循環一樣,在if條件的兩邊,我們也不需要加括號,但是if錶達式後的邏輯體的花括號是不能省略的。如果需要的話,像其它語言一樣,這個錶達式也可以有else部分,這部分邏輯會在if中的條件結果為false時被執行。
|
||||
和我們前面提到的for循環一樣,在if條件的兩邊,我們也不需要加括號,但是if表達式後的邏輯體的花括號是不能省略的。如果需要的話,像其它語言一樣,這個表達式也可以有else部分,這部分邏輯會在if中的條件結果爲false時被執行。
|
||||
|
||||
map是go語言內置的key/value數據結構,這個數據結構能夠提供常數時間的存儲、穫取、測試操作。key可以是任意數據類型,隻要該類型能夠用==來進行比較,string是最常用的key類型。而value類型的範圍就更大了,基本上什麼類型都是可以的。這個例子中的key都是string類型,value用的是int類型。我們用內置make函數來創建一個空的map,當然了,make方法還可以有彆的用處。在4.3章中我們還會對map進行更深度的討論。
|
||||
map是go語言內置的key/value數據結構,這個數據結構能夠提供常數時間的存儲、獲取、測試操作。key可以是任意數據類型,隻要該類型能夠用==來進行比較,string是最常用的key類型。而value類型的範圍就更大了,基本上什麽類型都是可以的。這個例子中的key都是string類型,value用的是int類型。我們用內置make函數來創建一個空的map,當然了,make方法還可以有別的用處。在4.3章中我們還會對map進行更深度的討論。
|
||||
|
||||
|
||||
dup程序每次讀取輸入的一行,這一行的內容會被當做一個map的key,而其value值會被+1。counts[input.Text()]++這個語句和下麫的兩句是等價的:
|
||||
dup程序每次讀取輸入的一行,這一行的內容會被當做一個map的key,而其value值會被+1。counts[input.Text()]++這個語句和下面的兩句是等價的:
|
||||
|
||||
```go
|
||||
line := input.Text()
|
||||
counts[line] = counts[line] + 1
|
||||
```
|
||||
|
||||
當然了,在這個例子裏我們併不用擔心map在沒有當前的key時就對其進行++操作會有什麼問題,因為go語言在碰到這種情況時,會自動將其初始化為0,然後再進行操作。
|
||||
當然了,在這個例子裡我們並不用擔心map在沒有當前的key時就對其進行++操作會有什麽問題,因爲go語言在碰到這種情況時,會自動將其初始化爲0,然後再進行操作。
|
||||
|
||||
在這裏我們又用了一個range的循環來打印結果,這次range是被用在map這個數據結果上。這一次的情況和上次比較類型,range會返迴兩個值,一個key和在map對應這個key的value。對map進行range循環時,其順序是不確定的,從實踐來看,很可能每次運行都會有不一樣的結果(譯註:這是go的設計者有意為之的,因為其底層實現不保證插入順序和遍歷順序一緻,而希望程序員不要依賴遍歷時的順序,所以幹脆直接在遍歷的時候做了隨機化處理,醉了),來避免程序員在業務中依賴遍歷時的順序。
|
||||
在這裡我們又用了一個range的循環來打印結果,這次range是被用在map這個數據結果上。這一次的情況和上次比較類型,range會返迴兩個值,一個key和在map對應這個key的value。對map進行range循環時,其順序是不確定的,從實踐來看,很可能每次運行都會有不一樣的結果(譯註:這是go的設計者有意爲之的,因爲其底層實現不保証插入順序和遍曆順序一緻,而希望程序員不要依賴遍曆時的順序,所以乾脆直接在遍曆的時候做了隨機化處理,醉了),來避免程序員在業務中依賴遍曆時的順序。
|
||||
|
||||
然後輪到我們例子中的bufio這個package了,這個package主要的目的是幫助我們更方便有效地處理程序的輸入和輸齣。而這個包最有用的一個特性就是其中的一個Scanner類型,用它可以簡單地接收輸入,或者把輸入打散成行或者單詞;這個類型通常是處理行形式的輸入最簡單的方法了。
|
||||
|
||||
@@ -55,18 +55,18 @@ counts[line] = counts[line] + 1
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
```
|
||||
|
||||
scanner對象可以從程序的標準輸入中讀取內容。對input.Scanner的每一次調用都會調入一個新行,併且會自動將其行末的換行符去掉;其結果可以用input.Text()得到。Scan方法在讀到了新行的時候會返迴true,而在沒有新行被讀入時,會返迴false。
|
||||
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變量
|
||||
%x, %o, %b 分彆為16進製,8進製,2進製形式的int
|
||||
%x, %o, %b 分別爲16進製,8進製,2進製形式的int
|
||||
%f, %g, %e 浮點數: 3.141593 3.141592653589793 3.141593e+00
|
||||
%t 佈爾變量:true 或 false
|
||||
%c rune (Unicode code point),go語言裏特有的Unicode字符類型
|
||||
%c rune (Unicode code point),go語言裡特有的Unicode字符類型
|
||||
%s string
|
||||
%q quoted string "abc" or rune 'c'
|
||||
%v 會將任意變量以易讀的形式打印齣來
|
||||
@@ -74,9 +74,9 @@ Printf有一大堆這種轉換,Go程序員把這些叫做verb(動詞)。下麫
|
||||
%% 字符型百分比標誌(不確定) literal percent sign (no operand)
|
||||
```
|
||||
|
||||
dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來錶示不可見字符。Printf默認不會在輸齣內容後加上換行符。按照慣例,用來格式化的函數都會在末尾以f字母結尾,比如log.Printf,fmt.Errorf,衕時還有一繫列對應以ln結尾的函數,這些函數默認以%v來格式化他們的參數,併且會在輸齣結束後在最後自動加上一個換行符。
|
||||
dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來表示不可見字符。Printf默認不會在輸齣內容後加上換行符。按照慣例,用來格式化的函數都會在末尾以f字母結尾,比如log.Printf,fmt.Errorf,同時還有一繫列對應以ln結尾的函數,這些函數默認以%v來格式化他們的參數,並且會在輸齣結束後在最後自動加上一個換行符。
|
||||
|
||||
許多程序從標準輸入中讀取數據,像上麫的例子那樣。除此之外,還可能從一繫列的文件中讀取。下一個dup程序就是從標準輸入中讀到一些文件名,用os.Open函數來打開每一個文件穫取內容的。
|
||||
許多程序從標準輸入中讀取數據,像上面的例子那樣。除此之外,還可能從一繫列的文件中讀取。下一個dup程序就是從標準輸入中讀到一些文件名,用os.Open函數來打開每一個文件獲取內容的。
|
||||
|
||||
```go
|
||||
gopl.io/ch1/dup2
|
||||
@@ -122,19 +122,19 @@ func countLines(f *os.File, counts map[string]int) {
|
||||
}
|
||||
```
|
||||
|
||||
os.Open函數會返迴兩個值。第一個值是一個打開的文件類型(*os.File),這個對象在下麫的程序中被Scanner讀取。
|
||||
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節中我們會對錯誤處理做更詳細的闡述。
|
||||
我們在本書中早期的例子中做了比較詳儘的錯誤處理,當然了,在實際編碼過程中,像os.Open這類的函數是一定要檢査其返迴的error值的;爲了減少例子程序的代碼量,我們姑且簡化掉這些不太可能返迴錯誤的邏輯。後面的例子裡我們會跳過錯誤檢査。在5.4節中我們會對錯誤處理做更詳細的闡述。
|
||||
|
||||
讀者可以再觀察一下上麫的例子,我們的countLines函數是在其聲明之前就被調用了。在Go語言裏,函數和包級彆的變量可以以任意的順序被聲明,併不影響其被調用。(譯註:最好還是遵循一定的規範)
|
||||
讀者可以再觀察一下上面的例子,我們的countLines函數是在其聲明之前就被調用了。在Go語言裡,函數和包級別的變量可以以任意的順序被聲明,並不影響其被調用。(譯註:最好還是遵循一定的規範)
|
||||
|
||||
再來講講map這個數據結構,map是用make函數創建的數據結構的一個引用。當一個map被作為參數傳遞給一個函數時,函數接收到的是一份引用的拷貝,雖然本身併不是一個東西,但因為他們指曏的是衕一塊數據對象(譯註:類似於C艹裏的引用傳遞),所以你在函數裏對map裏的值進行脩改時,原始的map內的值也會改變。在我們的例子中,我們在countLines函數中插入到counts這個map裏的值,在主函數中也是看得到的。
|
||||
再來講講map這個數據結構,map是用make函數創建的數據結構的一個引用。當一個map被作爲參數傳遞給一個函數時,函數接收到的是一份引用的拷貝,雖然本身並不是一個東西,但因爲他們指向的是同一塊數據對象(譯註:類似於C艹裡的引用傳遞),所以你在函數裡對map裡的值進行脩改時,原始的map內的值也會改變。在我們的例子中,我們在countLines函數中插入到counts這個map裡的值,在主函數中也是看得到的。
|
||||
|
||||
上麫這個版本的dup是以流的形式來處理輸入,併將其打散為行。理論上這些程序也是可以以二進製形式來處理輸入的。我們也可以一次性的把整個輸入內容全部讀到內存中,然後再把其分割為多行,然後再去處理這些行內的數據。下麫的dup3這個例子就是以這種形式來進行操作的。這個例子引入了一個新函數ReadFile(從io/ioutil這個包),這個函數會把一個指定名字的文件內容一次性調入,之後我們用strings.Split函數把文件分割為多個子字符串,併存儲到slice結構中。(Split函數是strings.Join的逆函數,Join函數之前提到過)
|
||||
上面這個版本的dup是以流的形式來處理輸入,並將其打散爲行。理論上這些程序也是可以以二進製形式來處理輸入的。我們也可以一次性的把整個輸入內容全部讀到內存中,然後再把其分割爲多行,然後再去處理這些行內的數據。下面的dup3這個例子就是以這種形式來進行操作的。這個例子引入了一個新函數ReadFile(從io/ioutil這個包),這個函數會把一個指定名字的文件內容一次性調入,之後我們用strings.Split函數把文件分割爲多個子字符串,並存儲到slice結構中。(Split函數是strings.Join的逆函數,Join函數之前提到過)
|
||||
|
||||
我們簡化了dup3這個程序。首先,他隻讀取命名的文件,而不去讀標準輸入,因為ReadFile函數需要一個文件名參數。其次,我們將行計數邏輯移迴到了main函數,因為現在這個邏輯隻有一個地方需要用到。
|
||||
我們簡化了dup3這個程序。首先,他隻讀取命名的文件,而不去讀標準輸入,因爲ReadFile函數需要一個文件名參數。其次,我們將行計數邏輯移迴到了main函數,因爲現在這個邏輯隻有一個地方需要用到。
|
||||
|
||||
```go
|
||||
gopl.io/ch1/dup3
|
||||
@@ -167,10 +167,10 @@ 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.Scanner,ioutil.ReadFile和ioutil.WriteFile使用的是*os.File的Read和Write方法,不過一般程序員併不需要去直接了解到其底層實現細節,在bufio和io/ioutil包中提供的方法已經足夠好用。
|
||||
在更底層一些的地方,bufio.Scanner,ioutil.ReadFile和ioutil.WriteFile使用的是*os.File的Read和Write方法,不過一般程序員並不需要去直接了解到其底層實現細節,在bufio和io/ioutil包中提供的方法已經足夠好用。
|
||||
|
||||
```
|
||||
Exercise 1.4: 脩改dup2,使其可以打印重復的行分彆齣現在哪些文件。
|
||||
Exercise 1.4: 脩改dup2,使其可以打印重復的行分別齣現在哪些文件。
|
||||
```
|
||||
|
||||
@@ -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是這樣的一個例子:
|
||||
|
||||

|
||||
|
||||
這段代碼裏我們用了一些新的結構,包括const聲明,數據struct類型,復閤聲明。和我們舉的其它的例子不太一樣,這一個例子包含了浮點數運算。這些概唸我們隻在這裏簡單地說明一下,之後的章節會更詳細地講解。
|
||||
這段代碼裡我們用了一些新的結構,包括const聲明,數據struct類型,復合聲明。和我們舉的其它的例子不太一樣,這一個例子包含了浮點數運算。這些概唸我們隻在這裡簡單地說明一下,之後的章節會更詳細地講解。
|
||||
|
||||
```go
|
||||
gopl.io/ch1/lissajous
|
||||
@@ -62,19 +62,19 @@ blackIndex)
|
||||
|
||||
```
|
||||
|
||||
當我們import了一個包路徑包含有多個單詞的package時,比如image/color(image和color兩個單詞),我們隻需要用最後那個單詞錶示這個包就可以。所以當我們寫color.White時,這個變量指曏的是image/color包裏的變量,衕理gif.GIF是屬於image/gif包裏的變量。
|
||||
當我們import了一個包路徑包含有多個單詞的package時,比如image/color(image和color兩個單詞),我們隻需要用最後那個單詞表示這個包就可以。所以當我們寫color.White時,這個變量指向的是image/color包裡的變量,同理gif.GIF是屬於image/gif包裡的變量。
|
||||
|
||||
這個程序裏的常量聲明給齣了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會齣現在包級彆,所以這些常量在整個包中都是可以共享的,或者你也可以把常量聲明定義在函數體內部,那麼這種常量就隻能在函數體內用。常量聲明的值必鬚是一個數字值、字符串或者一個固定的boolean值。
|
||||
這個程序裡的常量聲明給齣了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會齣現在包級別,所以這些常量在整個包中都是可以共享的,或者你也可以把常量聲明定義在函數體內部,那麽這種常量就隻能在函數體內用。常量聲明的值必鬚是一個數字值、字符串或者一個固定的boolean值。
|
||||
|
||||
[]color.Color{...}和gif.GIF{...}這兩個錶達式就是我們說的復閤聲明(4.2和4.4.1節有說明)。這是實例化Go語言裏的復閤類型的一種寫法。這裏的前者生成的是一個slice,後者生成的是一個struct。
|
||||
[]color.Color{...}和gif.GIF{...}這兩個表達式就是我們說的復合聲明(4.2和4.4.1節有說明)。這是實例化Go語言裡的復合類型的一種寫法。這裡的前者生成的是一個slice,後者生成的是一個struct。
|
||||
|
||||
gif.GIF是一個struct類型(參考4.4節)。struct是一組值或者叫字段的集閤,不衕的類型集閤在一個struct可以讓我們以一個統一的單元進行處理。anim是一個gif.GIF類型的struct變量。這種寫法會生成一個struct變量,併且其內部變量LoopCount字段會被設置為nframes;而其它的字段會被設置為各自類型默認的零值。struct內部的變量可以以一個點(.)來進行訪問,就像在最後兩個賦值語句中顯式地更新了anim這個struct的Delay和Image字段。
|
||||
gif.GIF是一個struct類型(參考4.4節)。struct是一組值或者叫字段的集合,不同的類型集合在一個struct可以讓我們以一個統一的單元進行處理。anim是一個gif.GIF類型的struct變量。這種寫法會生成一個struct變量,並且其內部變量LoopCount字段會被設置爲nframes;而其它的字段會被設置爲各自類型默認的零值。struct內部的變量可以以一個點(.)來進行訪問,就像在最後兩個賦值語句中顯式地更新了anim這個struct的Delay和Image字段。
|
||||
|
||||
lissajous函數內部有兩層嵌太的for循環。外層循環會循環64次,每一次都會生成一個單獨的動畫幀。它生成了一個包含兩種顔色的201&201大小的圖片,白色和黑色。所有像素點都會被默認設置為其零值(也就是palette裏的第0個值),這裏我們設置的是白色。每次經過內存循環都會通過設置像素為黑色,生成一張新圖片。其結果會append到之前結果之後。這裏我們用到了append(參考4.2.1)這個內置函數,將結果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)點來染黑色。
|
||||
內存循環設置了兩個偏振。x軸偏振使用的是一個sin函數。y軸偏振也是一個正絃波,但是其其相對x軸的偏振是一個0-3的隨機值,並且初始偏振值是一個零值,並隨着動畫的每一幀逐漸增加。循環會一直跑到x軸完成五次完整的循環。每一步它都會調用SetColorIndex來爲(x, y)點來染黑色。
|
||||
|
||||
main函數調用了lissajous函數,併且用它來曏標準輸齣中打印信息,所以下麫這個命令會像圖1.1中產生一個GIF動畫。
|
||||
main函數調用了lissajous函數,並且用它來向標準輸齣中打印信息,所以下面這個命令會像圖1.1中產生一個GIF動畫。
|
||||
|
||||
```bash
|
||||
$ go build gopl.io/ch1/lissajous
|
||||
@@ -82,6 +82,6 @@ $ ./lissajous >out.gif
|
||||
```
|
||||
|
||||
```
|
||||
Exercise 1.5: 脩改前麫的Lissajous程序裏的調色闆,由緑色改為黑色。我們可以用color.RGBA{0xRR, 0xGG, 0xBB}來得到#RRGGBB這個色值,三個十六進製的字符串分彆代錶紅、緑、藍像素。
|
||||
Exercise 1.6: 脩改Lissajous程序,脩改其調色闆來生成更豐富的顔色,然後脩改SetColorIndex的第三個參數,看看顯示結果吧。
|
||||
Exercise 1.5: 脩改前面的Lissajous程序裡的調色闆,由緑色改爲黑色。我們可以用color.RGBA{0xRR, 0xGG, 0xBB}來得到#RRGGBB這個色值,三個十六進製的字符串分別代表紅、緑、藍像素。
|
||||
Exercise 1.6: 脩改Lissajous程序,脩改其調色闆來生成更豐富的顏色,然後脩改SetColorIndex的第三個參數,看看顯示結果吧。
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## 1.5 穫取URL
|
||||
對於很多應用來說,訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go在net這個大package下提供了一繫列的package來做這件事情,使用這些包可以更簡單地用網絡收髮信息,還可以建立更底層的網絡連接,編寫服務器程序。在這些情景下,Go原生的併髮特性(在第八章中會介紹)就顯得尤其好用了。
|
||||
## 1.5 獲取URL
|
||||
對於很多應用來說,訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go在net這個大package下提供了一繫列的package來做這件事情,使用這些包可以更簡單地用網絡收發信息,還可以建立更底層的網絡連接,編寫服務器程序。在這些情景下,Go原生的並發特性(在第八章中會介紹)就顯得尤其好用了。
|
||||
|
||||
為了最簡單地展示基於HTTP穫取信息的方式,下麫給齣一個示例程序fetch,這個程序將穫取對應的url,併將其源文本打印齣來;這個例子的靈感來源於curl工具(譯註:unix下的一個工具)。當然了,curl提供的功能更為復雜豐富,這裏我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。
|
||||
爲了最簡單地展示基於HTTP獲取信息的方式,下面給齣一個示例程序fetch,這個程序將獲取對應的url,並將其源文本打印齣來;這個例子的靈感來源於curl工具(譯註:unix下的一個工具)。當然了,curl提供的功能更爲復雜豐富,這裡我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。
|
||||
|
||||
```go
|
||||
gopl.io/ch1/fetch
|
||||
@@ -32,7 +32,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寫齣到標準輸齣流中。
|
||||
|
||||
```bash
|
||||
$ go build gopl.io/ch1/fetch
|
||||
@@ -43,17 +43,17 @@ $ ./fetch http://gopl.io
|
||||
...
|
||||
```
|
||||
|
||||
HTTP請求如果失敗了的話,會得到下麫這樣的結果:
|
||||
HTTP請求如果失敗了的話,會得到下面這樣的結果:
|
||||
|
||||
```bash
|
||||
$ ./fetch http://bad.gopl.io
|
||||
fetch: Get http://bad.gopl.io: dial tcp: lookup bad.gopl.io: no such host
|
||||
```
|
||||
|
||||
無論哪種失敗原因,我們的程序都用了os.Exit函數來終止進程,併且返迴一個status錯誤碼,其值為1。
|
||||
無論哪種失敗原因,我們的程序都用了os.Exit函數來終止進程,並且返迴一個status錯誤碼,其值爲1。
|
||||
|
||||
```
|
||||
Exercise1.7: 函數調用io.Copy(dst, src)會從src中讀取內容,併將讀到的結果寫入到dst中,使用這個函數替代掉例子中的ioutil.ReadAll來拷貝響應結構體到os.Stdout,避免申請一個緩衝區(例子中的b)來存儲。記得處理io.Copy返迴結果中的錯誤。
|
||||
Exercise 1.8: 脩改fetch這個範例,如果輸入的url參數沒有http://前綴的話,為這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。
|
||||
Exercise1.7: 函數調用io.Copy(dst, src)會從src中讀取內容,並將讀到的結果寫入到dst中,使用這個函數替代掉例子中的ioutil.ReadAll來拷貝響應結構體到os.Stdout,避免申請一個緩沖區(例子中的b)來存儲。記得處理io.Copy返迴結果中的錯誤。
|
||||
Exercise 1.8: 脩改fetch這個範例,如果輸入的url參數沒有http://前綴的話,爲這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。
|
||||
Exercise 1.9: 脩改fetch打印齣HTTP協議的狀態碼,可以從resp.Status變量得到該狀態碼。
|
||||
```
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## 1.6 併髮穫取多個URL
|
||||
## 1.6 並發獲取多個URL
|
||||
|
||||
Go語言最有意思併且最新奇的特性就是其對併髮編程的支持了。併髮編程是一個大話題,在第八章和第九章中會講到。這裏我們隻淺嚐輒止地來體驗一下Go語言裏的goroutine和channel。
|
||||
Go語言最有意思並且最新奇的特性就是其對並發編程的支持了。並發編程是一個大話題,在第八章和第九章中會講到。這裡我們隻淺嘗輒止地來體驗一下Go語言裡的goroutine和channel。
|
||||
|
||||
下麫的例子fetchall,和上麫的fetch程序所要做的工作是一緻的,但是這個fetchall的特彆之處在於它會衕時去穫取所有的URL,所以這個程序的穫取時間不會超過執行時間最長的那一個任務,而不會像前麫的fetch程序一樣,執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印穫取的內容大小和經過的時間,不會像上麫那樣打印齣穫取的內容。
|
||||
下面的例子fetchall,和上面的fetch程序所要做的工作是一緻的,但是這個fetchall的特別之處在於它會同時去獲取所有的URL,所以這個程序的獲取時間不會超過執行時間最長的那一個任務,而不會像前面的fetch程序一樣,執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印獲取的內容大小和經過的時間,不會像上面那樣打印齣獲取的內容。
|
||||
|
||||
```go
|
||||
gopl.io/ch1/fetchall
|
||||
@@ -48,7 +48,7 @@ func fetch(url string, ch chan<- string) {
|
||||
}
|
||||
|
||||
```
|
||||
下麫是一個使用的例子
|
||||
下面是一個使用的例子
|
||||
|
||||
```bash
|
||||
$ go build gopl.io/ch1/fetchall
|
||||
@@ -58,10 +58,10 @@ $ ./fetchall https://golang.org http://gopl.io https://godoc.org
|
||||
0.48s 2475 http://gopl.io
|
||||
0.48s elapsed
|
||||
```
|
||||
goroutine是一種函數的併行執行方式,而channel是用來在goroutine之間進行參數傳遞。main函數卽運行在一個goroutine中,而go function則錶示創建一個新的goroutine,併讓這個函數去這個新的goroutine裏執行。
|
||||
goroutine是一種函數的並行執行方式,而channel是用來在goroutine之間進行參數傳遞。main函數卽運行在一個goroutine中,而go function則表示創建一個新的goroutine,並讓這個函數去這個新的goroutine裡執行。
|
||||
|
||||
main函數中用make函數創建了一個傳遞string類型參數的channel,對每一個命令行參數,我們都用go這個關鍵字來創建一個goroutine,併且讓函數在這個goroutine異步執行http.Get方法。這個程序裏的io.Copy會把響應的Body內容拷貝到ioutil.Discard輸齣流中,因為我們需要這個方法返迴的字節數,但是又不想要其內容。每當請求返迴內容時,fetch函數都會往ch這個channel裏寫入一個字符串,由main函數裏的第二個for循環來處理併打印channel裏的這個字符串。
|
||||
main函數中用make函數創建了一個傳遞string類型參數的channel,對每一個命令行參數,我們都用go這個關鍵字來創建一個goroutine,並且讓函數在這個goroutine異步執行http.Get方法。這個程序裡的io.Copy會把響應的Body內容拷貝到ioutil.Discard輸齣流中,因爲我們需要這個方法返迴的字節數,但是又不想要其內容。每當請求返迴內容時,fetch函數都會往ch這個channel裡寫入一個字符串,由main函數裡的第二個for循環來處理並打印channel裡的這個字符串。
|
||||
|
||||
當一個goroutine嚐試在一個channel上做send或者receive操作時,這個goroutine會阻塞在調用處,直到另一個goroutine往這個channel裏寫入、或者接收了值,這樣兩個goroutine纔會繼續執行操作channel完成之後的邏輯。在這個例子中,每一個fetch函數在執行時都會往channel裏髮送一個值(ch <- expression),主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串,可以避免在goroutine異步執行時衕時結束。
|
||||
當一個goroutine嘗試在一個channel上做send或者receive操作時,這個goroutine會阻塞在調用處,直到另一個goroutine往這個channel裡寫入、或者接收了值,這樣兩個goroutine纔會繼續執行操作channel完成之後的邏輯。在這個例子中,每一個fetch函數在執行時都會往channel裡發送一個值(ch <- expression),主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串,可以避免在goroutine異步執行時同時結束。
|
||||
|
||||
Exercise 1.10: 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差彆,併且每次穫取到的響應內容是否一緻,脩改本節中的程序,將響應結果輸齣,以便於進行對比。
|
||||
Exercise 1.10: 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差別,並且每次獲取到的響應內容是否一緻,脩改本節中的程序,將響應結果輸齣,以便於進行對比。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## 1.7. Web服務
|
||||
|
||||
Go的內置庫讓我們寫一個像fetch這樣例子的web服務器變得異常地簡單。在本節中,我們會展示一個微型服務器,這個服務的功能是返迴當前用戶正在訪問的URL。也就是說比如用戶訪問的是http://localhost:8000/hello,那麼響應是URL.Path = "hello"。
|
||||
Go的內置庫讓我們寫一個像fetch這樣例子的web服務器變得異常地簡單。在本節中,我們會展示一個微型服務器,這個服務的功能是返迴當前用戶正在訪問的URL。也就是說比如用戶訪問的是http://localhost:8000/hello,那麽響應是URL.Path = "hello"。
|
||||
```go
|
||||
gopl.io/ch1/server1
|
||||
// Server1 is a minimal "echo" server.
|
||||
@@ -23,15 +23,15 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
```
|
||||
|
||||
我們隻用了八九行就實現了這個程序,這都是多虧了標準庫裏的方法已經幫我們處理了大多數的工作。main函數會將所有髮送到/目彔下的請求和handler函數關聯起來,/開頭的請求其實就是所有髮送到當前站點上的請求,我們的服務跑在了8000端口上。髮送到這個服務的“請求”是一個http.Request類型的對象,這個對象中包含了請求中的一繫列相關字段,其中就包括我們需要的URL。當請求到達服務器時,這個請求會被傳給handler函數來處理,這個函數會將/hello這個路徑從請求的URL中解析齣來,然後把其髮送到響應中,這裏我們用的是標準輸齣流的fmt.Fprintf。Web服務會在第7.7節中詳細闡述。
|
||||
我們隻用了八九行就實現了這個程序,這都是多虧了標準庫裡的方法已經幫我們處理了大多數的工作。main函數會將所有發送到/目録下的請求和handler函數關聯起來,/開頭的請求其實就是所有發送到當前站點上的請求,我們的服務跑在了8000端口上。發送到這個服務的“請求”是一個http.Request類型的對象,這個對象中包含了請求中的一繫列相關字段,其中就包括我們需要的URL。當請求到達服務器時,這個請求會被傳給handler函數來處理,這個函數會將/hello這個路徑從請求的URL中解析齣來,然後把其發送到響應中,這裡我們用的是標準輸齣流的fmt.Fprintf。Web服務會在第7.7節中詳細闡述。
|
||||
|
||||
讓我們在後颱運行這個服務程序。如果你的操作繫統是Mac OS X或者Linux,那麼在運行命令的末尾加上一個&符號,卽可讓程序簡單地跑在後颱,而在windows下,你需要在另外一個命令行窗口去運行這個程序了。
|
||||
讓我們在後檯運行這個服務程序。如果你的操作繫統是Mac OS X或者Linux,那麽在運行命令的末尾加上一個&符號,卽可讓程序簡單地跑在後檯,而在windows下,你需要在另外一個命令行窗口去運行這個程序了。
|
||||
|
||||
```
|
||||
$ go run src/gopl.io/ch1/server1/main.go &
|
||||
```
|
||||
|
||||
現在我們可以通過命令行來髮送客戶端請求了:
|
||||
現在我們可以通過命令行來發送客戶端請求了:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch1/fetch
|
||||
@@ -41,10 +41,10 @@ $ ./fetch http://localhost:8000/help
|
||||
URL.Path = "/help"
|
||||
```
|
||||
|
||||
另外我們還可以直接在瀏覽器裏訪問這個URL,然後得到返迴結果,如圖1.2:
|
||||
另外我們還可以直接在瀏覽器裡訪問這個URL,然後得到返迴結果,如圖1.2:
|
||||

|
||||
|
||||
在這個服務的基礎上疊加特性是很容易的。一種比較實用的脩改是為訪問的url添加某種狀態。比如,下麫這個版本輸齣了衕樣的內容,但是會對請求的次數進行計算;對URL的請求結果會包含各種URL被訪問的總次數,直接對/count這個URL的訪問要除外。
|
||||
在這個服務的基礎上疊加特性是很容易的。一種比較實用的脩改是爲訪問的url添加某種狀態。比如,下面這個版本輸齣了同樣的內容,但是會對請求的次數進行計算;對URL的請求結果會包含各種URL被訪問的總次數,直接對/count這個URL的訪問要除外。
|
||||
|
||||
```go
|
||||
gopl.io/ch1/server2
|
||||
@@ -83,9 +83,9 @@ func counter(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
```
|
||||
|
||||
這個服務器有兩個請求處理函數,請求的url會決定具體調用哪一個:對/count這個url的請求會調用到count這個函數,其它所有的url都會調用默認的處理函數。如果你的請求pattern是以/結尾,那麼所有以該url為前綴的url都會被這條規則匹配。在這些代碼的背後,服務器每一次接收請求處理時都會另起一個goroutine,這樣服務器就可以衕一時間處理多數請求。然而在併髮情況下,假如眞的有兩個請求衕一時刻去更新count,那麼這個值可能併不會被正確地增加;這個程序可能會被引髮一個嚴重的bug:競態條件(參見9.1)。為了避免這個問題,我們必鬚保證每次脩改變量的最多隻能有一個goroutine,這也就是代碼裏的mu.Lock()和mu.Unlock()調用將脩改count的所有行為包在中間的目的。第九章中我們會進一步講解共享變量。
|
||||
這個服務器有兩個請求處理函數,請求的url會決定具體調用哪一個:對/count這個url的請求會調用到count這個函數,其它所有的url都會調用默認的處理函數。如果你的請求pattern是以/結尾,那麽所有以該url爲前綴的url都會被這條規則匹配。在這些代碼的背後,服務器每一次接收請求處理時都會另起一個goroutine,這樣服務器就可以同一時間處理多數請求。然而在並發情況下,假如眞的有兩個請求同一時刻去更新count,那麽這個值可能並不會被正確地增加;這個程序可能會被引發一個嚴重的bug:競態條件(參見9.1)。爲了避免這個問題,我們必鬚保証每次脩改變量的最多隻能有一個goroutine,這也就是代碼裡的mu.Lock()和mu.Unlock()調用將脩改count的所有行爲包在中間的目的。第九章中我們會進一步講解共享變量。
|
||||
|
||||
下麫是一個更為豐富的例子,handler函數會把請求的http頭和請求的form數據都打印齣來,這樣可以讓檢査和調試這個服務更為方便
|
||||
下面是一個更爲豐富的例子,handler函數會把請求的http頭和請求的form數據都打印齣來,這樣可以讓檢査和調試這個服務更爲方便
|
||||
|
||||
```go
|
||||
gopl.io/ch1/server3
|
||||
@@ -106,7 +106,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
```
|
||||
|
||||
我們用http.Request這個struct裏的字段來輸齣下麫這樣的內容:
|
||||
我們用http.Request這個struct裡的字段來輸齣下面這樣的內容:
|
||||
|
||||
```
|
||||
GET /?q=query HTTP/1.1
|
||||
@@ -117,7 +117,7 @@ RemoteAddr = "127.0.0.1:59911"
|
||||
Form["q"] = ["query"]
|
||||
```
|
||||
|
||||
可以看到這裏的ParseForm被嵌套在了if語句中。Go語言允許這樣的一個簡單的語句結果作為循環的變量聲明齣現在if語句的最前麫,這一點對錯誤處理很有用處。我們還可以像下麫這樣寫(當然看起來就長了一些):
|
||||
可以看到這裡的ParseForm被嵌套在了if語句中。Go語言允許這樣的一個簡單的語句結果作爲循環的變量聲明齣現在if語句的最前面,這一點對錯誤處理很有用處。我們還可以像下面這樣寫(當然看起來就長了一些):
|
||||
|
||||
```go
|
||||
err := r.ParseForm()
|
||||
@@ -125,13 +125,13 @@ if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
```
|
||||
用if和ParseForm結閤可以讓代碼更加簡單,併且可以限製err這個變量的作用域,這麼做是很不錯的。我們會在2.7節中講解作用域。
|
||||
用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服務器的代碼裡加入下面這幾行。
|
||||
|
||||
```
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -147,13 +147,13 @@ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
```
|
||||
|
||||
HandleFunc函數的第二個參數是一個函數的字麫值,也就是一個在使用時定義的匿名函數。這些內容我們會在5.6節中講解。
|
||||
HandleFunc函數的第二個參數是一個函數的字面值,也就是一個在使用時定義的匿名函數。這些內容我們會在5.6節中講解。
|
||||
|
||||
|
||||
做完這些脩改之後,在瀏覽器裏訪問http://localhost:8000。每次你載入這個頁麫都可以看到一個像圖1.3那樣的動畫。
|
||||
做完這些脩改之後,在瀏覽器裡訪問http://localhost:8000。每次你載入這個頁面都可以看到一個像圖1.3那樣的動畫。
|
||||
|
||||
```
|
||||
Exercise 1.12:脩改Lissajour服務,從URL讀取變量,比如你可以訪問http://localhost:8000/?cycles=20這個URL,這樣訪問可以將程序裏的cycles默認的5脩改為20。字符串轉換為數字可以調用strconv.Atoi函數。你可以在dodoc裏査看strconv.Atoi的詳細說明。
|
||||
Exercise 1.12:脩改Lissajour服務,從URL讀取變量,比如你可以訪問http://localhost:8000/?cycles=20這個URL,這樣訪問可以將程序裡的cycles默認的5脩改爲20。字符串轉換爲數字可以調用strconv.Atoi函數。你可以在dodoc裡査看strconv.Atoi的詳細說明。
|
||||
```
|
||||

|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## 1.8. 本章要點
|
||||
|
||||
本章中對Go語言做了一些介紹,實際上Go語言還有很多方麫在這有限的篇幅中還沒有覆蓋到。這裏我們會把沒有講到的內容也做一些簡單的介紹,這樣讀者在之後看到完整的內容之前,也可以簡單有個印象。
|
||||
本章中對Go語言做了一些介紹,實際上Go語言還有很多方面在這有限的篇幅中還沒有覆蓋到。這裡我們會把沒有講到的內容也做一些簡單的介紹,這樣讀者在之後看到完整的內容之前,也可以簡單有個印象。
|
||||
|
||||
控製流:在本章我們隻介紹了if控製和for,但是沒有提到switch多路選擇。這裏是一個簡單的switch的例子:
|
||||
控製流:在本章我們隻介紹了if控製和for,但是沒有提到switch多路選擇。這裡是一個簡單的switch的例子:
|
||||
|
||||
```go
|
||||
switch coinflip() {
|
||||
@@ -15,9 +15,9 @@ switch coinflip() {
|
||||
}
|
||||
```
|
||||
|
||||
在翻轉硬幣的時候,例子裏的coinflip函數返迴幾種不衕的結果,每一個case都會對應個返迴結果,這裏需要註意,Go語言併不需要顯式地去在每一個case後寫break,語言默認執行完case後的邏輯語句會自動退齣。當然了,如果你想要相鄰的幾個case都執行衕一邏輯的話,需要自己顯式地寫上一個fallthrough語句來覆蓋這種默認行為。不過fallthrough語句在一般的編程中用到得很少。
|
||||
在翻轉硬幣的時候,例子裡的coinflip函數返迴幾種不同的結果,每一個case都會對應個返迴結果,這裡需要註意,Go語言並不需要顯式地去在每一個case後寫break,語言默認執行完case後的邏輯語句會自動退齣。當然了,如果你想要相鄰的幾個case都執行同一邏輯的話,需要自己顯式地寫上一個fallthrough語句來覆蓋這種默認行爲。不過fallthrough語句在一般的編程中用到得很少。
|
||||
|
||||
go裏的switch還可以不帶操作對象;可以直接羅列多種條件,像其它語言裏麫的多個if else一樣,下麫是一個例子:
|
||||
go裡的switch還可以不帶操作對象;可以直接羅列多種條件,像其它語言裡面的多個if else一樣,下面是一個例子:
|
||||
|
||||
```go
|
||||
func Signum(x int) int {
|
||||
@@ -34,11 +34,11 @@ func Signum(x int) int {
|
||||
|
||||
這種形式叫做無tag switch(tagless switch);這和switch true是等價的。
|
||||
|
||||
像for和if控製語句一樣,switch也可以緊跟一個簡短的變量聲明,一個自增錶達式、賦值語句,或者一個函數調用。
|
||||
像for和if控製語句一樣,switch也可以緊跟一個簡短的變量聲明,一個自增表達式、賦值語句,或者一個函數調用。
|
||||
|
||||
break和continue語句會改變控製流。和其它語言中的break和continue一樣,break會中斷當前的循環,併開始執行循環之後的內容,而continue會中跳過當前循環,併開始執行下一次循環。這兩個語句除了可以控製for循環,還可以用來控製switch和select語句(之後會講到),在1.3節中我們看到,continue會跳過是內層的循環,如果我們想跳過的是更外層的循環的話,我們可以在相應的位置加上label,這樣break和continue就可以根據我們的想法來continue和break任意循環。這看起來甚至有點像goto語句的作用了。當然,一般程序員也不會用到這種操作。這兩種行為更多地被用到機器生成的代碼中。
|
||||
break和continue語句會改變控製流。和其它語言中的break和continue一樣,break會中斷當前的循環,並開始執行循環之後的內容,而continue會中跳過當前循環,並開始執行下一次循環。這兩個語句除了可以控製for循環,還可以用來控製switch和select語句(之後會講到),在1.3節中我們看到,continue會跳過是內層的循環,如果我們想跳過的是更外層的循環的話,我們可以在相應的位置加上label,這樣break和continue就可以根據我們的想法來continue和break任意循環。這看起來甚至有點像goto語句的作用了。當然,一般程序員也不會用到這種操作。這兩種行爲更多地被用到機器生成的代碼中。
|
||||
|
||||
命名類型:類型聲明使得我們可以很方便地給一個特殊類型一個名字。因為struct類型聲明通常非常地長,所以我們總要給這種struct取一個名字。本章中就有這樣一個例子,2d點類型:
|
||||
命名類型:類型聲明使得我們可以很方便地給一個特殊類型一個名字。因爲struct類型聲明通常非常地長,所以我們總要給這種struct取一個名字。本章中就有這樣一個例子,2d點類型:
|
||||
```go
|
||||
type Point struct {
|
||||
X, Y int
|
||||
@@ -48,13 +48,13 @@ var p Point
|
||||
|
||||
類型聲明和命名類型會在第二章中介紹。
|
||||
|
||||
指鍼:Go語言提供了指鍼。指鍼是一種直接存儲了變量的內存地址的數據結構。在其它語言中,比如C語言,指鍼是完全不受約束的。在另外一些語言中,指鍼一般被稱為“引用”,除了到處傳遞這些指鍼之外,併不能對這些指鍼做太多事情。go在這兩種範圍中取得了一個平衡。指鍼是可見的內存地址,&操作符可以返迴一個變量的內存地址,併且*操作符可以穫取指鍼指曏的變量內容,但是在go語言裏沒有指鍼運算,也就是不像c語言裏可以對指鍼進行加或減操作。我們會在2.3.2中進行詳細介紹。
|
||||
指鍼:Go語言提供了指鍼。指鍼是一種直接存儲了變量的內存地址的數據結構。在其它語言中,比如C語言,指鍼是完全不受約束的。在另外一些語言中,指鍼一般被稱爲“引用”,除了到處傳遞這些指鍼之外,並不能對這些指鍼做太多事情。go在這兩種範圍中取得了一個平衡。指鍼是可見的內存地址,&操作符可以返迴一個變量的內存地址,並且*操作符可以獲取指鍼指向的變量內容,但是在go語言裡沒有指鍼運算,也就是不像c語言裡可以對指鍼進行加或減操作。我們會在2.3.2中進行詳細介紹。
|
||||
|
||||
方法和接口:方法是和命名類型關聯的一類函數。Go語言裏比較特殊的是方法可以被關聯到任意一種命名類型。在第六章我們會詳細地講方法。接口是一種抽象類型,這種類型可以讓我們以衕樣的方式來處理不衕的固有類型,不用關心它們的具體實現,而隻需要關註它們提供的方法。第七章中會詳細說明這些內容。
|
||||
方法和接口:方法是和命名類型關聯的一類函數。Go語言裡比較特殊的是方法可以被關聯到任意一種命名類型。在第六章我們會詳細地講方法。接口是一種抽象類型,這種類型可以讓我們以同樣的方式來處理不同的固有類型,不用關心它們的具體實現,而隻需要關註它們提供的方法。第七章中會詳細說明這些內容。
|
||||
|
||||
包(packages):Go語言提供了一些很好用的package,併且這些package是可以擴展的。Go語言社區已經創造併且分享了很多很多。所以Go語言編程大多數情況下就是用已有的package來寫我們自己的代碼。通過這本書,我們會講解一些重要的標準庫內的package,但是還是有很多我們沒有篇幅去說明,因為我們沒法在這樣的厚度的書裏去做一部代碼大全。
|
||||
包(packages):Go語言提供了一些很好用的package,並且這些package是可以擴展的。Go語言社區已經創造並且分享了很多很多。所以Go語言編程大多數情況下就是用已有的package來寫我們自己的代碼。通過這本書,我們會講解一些重要的標準庫內的package,但是還是有很多我們沒有篇幅去說明,因爲我們沒法在這樣的厚度的書裡去做一部代碼大全。
|
||||
|
||||
在你開始寫一個新程序之前,最好先去檢査一下是不是已經有了現成的庫可以幫助你更高效地完成這件事情。你可以在https://golang.org/pkg 和 https://godoc.org 中找到標準庫和社區寫的package。godoc這個工具可以讓你直接在本地命令行閱讀標準庫的文檔。比如下麫這個例子。
|
||||
在你開始寫一個新程序之前,最好先去檢査一下是不是已經有了現成的庫可以幫助你更高效地完成這件事情。你可以在https://golang.org/pkg 和 https://godoc.org 中找到標準庫和社區寫的package。godoc這個工具可以讓你直接在本地命令行閱讀標準庫的文檔。比如下面這個例子。
|
||||
|
||||
```
|
||||
$ go doc http.ListenAndServe
|
||||
@@ -64,7 +64,7 @@ func ListenAndServe(addr string, handler Handler) error
|
||||
calls Serve with handler to handle requests on incoming connections.
|
||||
...
|
||||
```
|
||||
註釋:我們之前已經提到過了在源文件的開頭寫的註釋是這個源文件的文檔。在每一個函數之前寫一個說明函數行為的註釋也是一個好習慣。這些慣例很重要,因為這些內容會被像godoc這樣的工具檢測到,併且在執行命令時顯示這些註釋。具體可以參考10.7.4。
|
||||
註釋:我們之前已經提到過了在源文件的開頭寫的註釋是這個源文件的文檔。在每一個函數之前寫一個說明函數行爲的註釋也是一個好習慣。這些慣例很重要,因爲這些內容會被像godoc這樣的工具檢測到,並且在執行命令時顯示這些註釋。具體可以參考10.7.4。
|
||||
|
||||
多行註釋可以用/* ... */來包裹,和其它大多數語言一樣。在文件一開頭的註釋一般都是這種形式,或者一大段的解釋性的註釋文字也會被這符號包住,來避免每一行都需要加//。在註釋中//和/*是沒什麼意義的,所以不要在註釋中再嵌入註釋。
|
||||
多行註釋可以用/* ... */來包裹,和其它大多數語言一樣。在文件一開頭的註釋一般都是這種形式,或者一大段的解釋性的註釋文字也會被這符號包住,來避免每一行都需要加//。在註釋中//和/*是沒什麽意義的,所以不要在註釋中再嵌入註釋。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 第1章 入門
|
||||
|
||||
本章會介紹Go語言裏的一些基本組件。我們希望用信息和例子盡快帶你入門。本章和之後章節的例子都是鍼對眞實的開髮案例給齣。本章我們隻是簡單地為你介紹一些Go的入門例子,從簡單的文件處理、圖像處理到互聯網併髮客戶端和服務端程序。當然,在第一章我們不會詳盡地一一去說明細枝末節,不過用這些程序來學習一門新語言肯定是很有效的。
|
||||
當你學習一門新語言時,你會有去用這門新語言去重寫自己以前熟悉語言例子的傾曏。在學習Go的過程中,盡量避免這麼做。我們會曏你演示如何纔能寫齣好的Go程序,所以請使用這裏的代碼作為你寫自己的Go程序時的指南。
|
||||
本章會介紹Go語言裡的一些基本組件。我們希望用信息和例子儘快帶你入門。本章和之後章節的例子都是鍼對眞實的開發案例給齣。本章我們隻是簡單地爲你介紹一些Go的入門例子,從簡單的文件處理、圖像處理到互聯網並發客戶端和服務端程序。當然,在第一章我們不會詳儘地一一去說明細枝末節,不過用這些程序來學習一門新語言肯定是很有效的。
|
||||
當你學習一門新語言時,你會有去用這門新語言去重寫自己以前熟悉語言例子的傾向。在學習Go的過程中,儘量避免這麽做。我們會向你演示如何纔能寫齣好的Go程序,所以請使用這裡的代碼作爲你寫自己的Go程序時的指南。
|
||||
|
||||
Reference in New Issue
Block a user