ch1-02 review

This commit is contained in:
chai2010 2015-12-22 20:49:16 +08:00
parent 378361f564
commit 39539dbc80
4 changed files with 53 additions and 50 deletions

View File

@ -1,6 +1,6 @@
## 更多的信息 ## 更多的信息
最佳的幫助信息來自Go語言的官方網站https://golang.org 它提供了完善的參考文檔包括編程語言規范和標準庫等諸多權威的幫助信息。同時也包含了如何編寫更地道的Go程序的基本敎程還有各種各樣的在線文本資源和視頻資源它們是本書最有價值的補充。Go語言的官方博客 https://blog.golang.org 會不定期發布一些Go語言最好的實踐文章包括當前語言的發展狀態、未來的計劃、會議報告和Go語言相關的各種會議的主題等信息译注: http://talks.golang.org/ 包含了官方收录的各种报告的讲稿)。 最佳的幫助信息來自Go語言的官方網站https://golang.org 它提供了完善的參考文檔包括編程語言規范和標準庫等諸多權威的幫助信息。同時也包含了如何編寫更地道的Go程序的基本敎程還有各種各樣的在線文本資源和視頻資源它們是本書最有價值的補充。Go語言的官方博客 https://blog.golang.org 會不定期發布一些Go語言最好的實踐文章包括當前語言的發展狀態、未來的計劃、會議報告和Go語言相關的各種會議的主題等信息譯註: http://talks.golang.org/ 包含了官方收録的各種報告的講稿)。
在線訪問的一個有價值的地方是可以從web頁面運行Go語言的程序而紙質書則沒有這麽便利了。這個功能由來自 https://play.golang.org 的 Go Playground 提供,併且可以方便地嵌入到其他頁面中,例如 https://golang.org 的主頁,或 godoc 提供的文檔頁面中。 在線訪問的一個有價值的地方是可以從web頁面運行Go語言的程序而紙質書則沒有這麽便利了。這個功能由來自 https://play.golang.org 的 Go Playground 提供,併且可以方便地嵌入到其他頁面中,例如 https://golang.org 的主頁,或 godoc 提供的文檔頁面中。

View File

@ -3,7 +3,7 @@
我們以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 ```go
//gopl.io/ch1/helloworld gopl.io/ch1/helloworld
package main package main
import "fmt" import "fmt"

View File

@ -1,37 +1,42 @@
## 1.2. 命令行參數 ## 1.2. 命令行參數
大多數的程序都是處理輸入,産生輸齣;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸齣、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。 大多數的程序都是處理輸入,産生輸齣;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸齣、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。
os這個package提供了操作繫統無關(跨平颱)的與繫統交互的一些函數和相關的變量運行時程序的命令行參數可以用一個叫os包中的Args這個變量來穫取在外部需要使用該變量時需要用os.Args來訪問。 os這個package提供了操作繫統無關跨平颱與繫統交互的一些函數和相關的變量運行時程序的命令行參數可以通過os包中一個叫Args的這個變量來穫取當在os包外部使用該變量時需要用os.Args來訪問。
os.Args這個變量是一個字符串(string)的sliceslice在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]這個slice0 ≤ m ≤ n ≤ len(s)包含n-m個元素。 os.Args這個變量是一個字符串string的slice譯註slice和Python語言中的切片類似是一個簡版的動態數組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]這個slice0 ≤ 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 ```go
gopl.io/ch1/echo1 gopl.io/ch1/echo1
// Echo1 prints its command-line arguments. // Echo1 prints its command-line arguments.
package main package main
import ( import (
"fmt" "fmt"
"os" "os"
) )
func main() { func main() {
var s, sep string var s, sep string
for i := 1; i < len(os.Args); i++ { for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i] s += sep + os.Args[i]
sep = " " sep = " "
} }
fmt.Println(s) 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 ```go
sep + os.Args[i] sep + os.Args[i]
@ -43,7 +48,7 @@ sep + os.Args[i]
s += sep + os.Args[i] s += sep + os.Args[i]
``` ```
會將sep與os.Args[i]連接然後再將得到的結果與s進行連接這種方式和下面的表達是等價的 會將sep與os.Args[i]連接然後再將得到的結果與s進行連接併賦值運給s,這種方式和下面的表達是等價的:
```go ```go
s = s + sep + os.Args[i] s = s + sep + os.Args[i]
@ -51,43 +56,45 @@ s = s + sep + os.Args[i]
運算符+=是一個賦值運算符(assignment operator),每一種數值和邏輯運算符,例如*或者+都有其對應的賦值運算符。 運算符+=是一個賦值運算符(assignment operator),每一種數值和邏輯運算符,例如*或者+都有其對應的賦值運算符。
echo程序可以每循環一次輸齣一個參數不過我們這里的版本是不斷地將其結果連接到一個字符串的末尾。s這個字符串在聲明的時候是一個空字符串而之後循環每次都會被在末尾添加一段字符串第一次迭代之後一個空格會被插入到字符串末尾所以每插入一個新值都會和前一個中間有一個空格隔開。這是一種非線性的操作當我們的參數數量變得龐大的時候(當然不是説這里的echo一般echo也不會有太多參數)其運行開銷也會變得龐大。下面我們會介紹一繫列的echo改進版來應對這里説到的運行效率低下。 echo程序可以每循環一次輸齣一個參數不過我們這里的版本是不斷地將其結果連接到一個字符串的末尾。s這個字符串在聲明的時候是一個空字符串而之後循環每次都會被在末尾添加一段字符串第一次迭代之後一個空格會被插入到字符串末尾所以每插入一個新值都會和前一個中間有一個空格隔開。這是一種非線性的操作當我們的參數數量變得龐大的時候當然不是説這里的echo一般echo也不會有太多參數其運行開銷也會變得龐大。下面我們會介紹一繫列的echo改進版來應對這里説到的運行效率低下。
在for循環中我們用到了i來做下標索引可以看到我們用了:=符號來給i進行初始化和賦值這是var xxx=yyy的一種簡寫形式Go會根據等號右邊的值的類型自動判斷左邊的值類型下一章會對這一點進行詳細説明。 在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 ```go
for initialization; condition; post { for initialization; condition; post {
// zero or more statements // 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 ```go
// a traditional "while" loop // a traditional "while" loop
for condition { for condition {
// ... // ...
} }
``` ```
當然了如果你連唯一的條件都省了那麽for循環就會變成一個無限循環像下面這樣 當然了如果你連唯一的條件都省了那麽for循環就會變成一個無限循環像下面這樣
```go ```go
// a traditional infinite loop // a traditional infinite loop
for { for {
// ... // ...
} }
``` ```
在無限循環中你還是可以靠break或者return來終止掉循環。 在無限循環中你還是可以靠break或者return語句來終止掉循環。
如果你的遍歷對象是string或者slice里的值的話還有另外一種循環的寫法我們來看看另一個版本的echo 如果你的遍歷對象是string或者slice類型值的話還有另外一種循環的寫法我們來看看另一個版本的echo
```go ```go
gopl.io/ch1/echo2 gopl.io/ch1/echo2
@ -95,23 +102,22 @@ gopl.io/ch1/echo2
package main package main
import ( import (
"fmt" "fmt"
) )
func main() { func main() {
s, sep := "", "" s, sep := "", ""
for _, arg := range os.Args[1:] { for _, arg := range os.Args[1:] {
s += sep + arg s += sep + arg
sep = " " sep = " "
} }
fmt.Println(s) fmt.Println(s)
} }
``` ```
每一次循環迭代range都會返迴一對結果當前迭代的下標以及在該下標處的元素的值。在這個例子里我們不需要這個下標但是因爲range的處理要求我們必鬚要同時處理下標和值。我們可以在這里聲明一個接收index的臨時變量來解決這個問題但是go語言又不允許隻聲明而在後續代碼里不使用這個變量如果你這樣做了編譯器會返迴一個編譯錯誤。 每一次循環迭代range都會返迴一對結果當前迭代的下標以及在該下標處的元素的值。在這個例子里我們不需要這個下標但是因爲range的處理要求我們必鬚要同時處理下標和值。我們可以在這里聲明一個接收index的臨時變量來解決這個問題但是Go語言又不允許隻聲明而在後續代碼里不使用這個變量如果你這樣做了編譯器會返迴一個編譯錯誤。
在Go語言中應對這種情況的解決方法是用空白標識符就是上面那個下劃線_。空白標識符可以在任何你接收自己不需要處理的值時使用。在這里我們用他來忽略掉range返迴的那個沒用的下標值。大多數的Go程序員都會像上面這樣來寫類似的os.Args遍歷可以避免錯誤的下標引用。(這里可能有翻譯錯,附上原文) 在Go語言中應對這種情況的解決方法是用空白標識符就是上面那個下劃線_。空白標識符可以在任何你接收自己不需要處理的值時使用。在這里我們用它來忽略掉range返迴的那個沒用的下標值。大多數的Go程序員都會像上面這樣來寫類似的os.Args遍歷由於遍歷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的聲明和初始化都放到了一起但是我們可以等價地將聲明和賦值分開來寫下面這些寫法都是等價的
@ -122,17 +128,16 @@ var s = ""
var s string = "" var s string = ""
``` ```
那麽這些等價的形式應該怎麽做選擇呢?這里提供一些建議:第一種形式,最好隻用在一個函數內部而package級别的變量請不要使用這樣的聲明方式。第二種形式依賴於string類型的內部初始化機製被初始化爲空字符串。第三種形式使用得很少除非同時聲明多個變量。第四種形式會顯式地標明變量的類型在多變量同時聲明時可以用到。實踐中你應該隻使用上面的前兩種形式顯式地指定變量的類型讓編譯器自己去初始化其值或者直接用隱式初始化表明初始值怎麽樣併不重要。 那麽這些等價的形式應該怎麽做選擇呢?這里提供一些建議:第一種形式,隻用在一個函數內部而package級别的變量禁止用這樣的聲明方式。第二種形式依賴於string類型的內部初始化機製被初始化爲空字符串。第三種形式使用得很少除非同時聲明多個變量。第四種形式會顯式地標明變量的類型在多變量同時聲明時可以用到。實踐中你應該隻使用上面的前兩種形式顯式地指定變量的類型讓編譯器自己去初始化其值或者直接用隱式初始化表明初始值怎麽樣併不重要。
像上面提到的每次循環中字符串s都會得到一個新內容。+=語句會分配一個新的字符串,併將老字符串連接起來的值賦予給它。而目標字符串的老字面值在得到新值以後就失去了用處,這些臨時值會被go的垃圾收集器榦掉。 像上面提到的,每次循環迭代中字符串s都會得到一個新內容。+=語句會分配一個新的字符串,併將老字符串連接起來的值賦予給它。而目標字符串的老字面值在得到新值以後就失去了用處,這些臨時值會被Go語言的垃圾收集器榦掉。
如果不斷連接的數據量很大那麽上面這種操作就是成本非常高的操作。更簡單併且有效的一種方式是使用strings包提供的Join函數像下面這樣
如果不斷連接的數據量很大那麽上面這種操作就是成本非常高的操作。更簡單併且有效的一種方式是使用字符串的Join函數像下面這樣
```go ```go
gopl.io/ch1/echo3 gopl.io/ch1/echo3
func main() { func main() {
fmt.Println(strings.Join(os.Args[1:], " ")) fmt.Println(strings.Join(os.Args[1:], " "))
} }
``` ```
@ -142,13 +147,11 @@ func main() {
fmt.Println(os.Args[1:]) 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併觀察輸齣結果的區别。
```
Exercise 1.1:脩改echo程序使其能夠打印os.Args[0]。
Exercise 1.2:脩改echo程序使其打印value和index每個value和index顯示一行。
Exercise 1.3:上手實踐前面提到的strings.Join和直接Println併觀察輸齣結果的區别。
```

View File

@ -22,7 +22,7 @@
*“Go是一個開源的編程語言它很容易用於構建簡單、可靠和高效的軟件。”摘自Go語言官方網站http://golang.org * *“Go是一個開源的編程語言它很容易用於構建簡單、可靠和高效的軟件。”摘自Go語言官方網站http://golang.org *
Go語言由來自Google公司的Robert GriesemerRob Pike和Ken Thompson三位大牛於2007年9月開始設計和實現然後於2009年的11月對外正式發布译注关于Go语言的创世纪细节请参考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。 Go語言由來自Google公司的Robert GriesemerRob Pike和Ken Thompson三位大牛於2007年9月開始設計和實現然後於2009年的11月對外正式發布譯註關於Go語言的創世紀細節請參考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。
Go語言有着和C語言類似的語法外表和C語言一樣是專業程序員的必備工具可以用最小的代價穫得最大的戰果。 Go語言有着和C語言類似的語法外表和C語言一樣是專業程序員的必備工具可以用最小的代價穫得最大的戰果。
但是它不僅僅是一個更新的C語言。它還從其他語言借鑒了很多好的想法同時避免引入過度的複雜性。 但是它不僅僅是一個更新的C語言。它還從其他語言借鑒了很多好的想法同時避免引入過度的複雜性。