mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 06:18:56 +00:00
polish the rest of ch1-02.md
This commit is contained in:
parent
4a3dbe68b4
commit
b13957dc51
@ -4,7 +4,7 @@
|
||||
|
||||
`os`包以跨平台的方式,提供了一些與操作繫統交互的函數和變量。程序的命令行參數可从os包的Args變量获取;os包外部使用os.Args访问該變量。
|
||||
|
||||
os.Args變量是一個字符串(string)的*切片*(slice)(譯註:slice和Python語言中的切片類似,是一個簡版的動態數組),切片是Go語言的基礎概念,稍后会详细介绍。現在先把切片s当作数组元素序列, 序列的成长度动态变化, 用`s[i]`访问单个元素,用`s[m:n]`獲取子序列(譯註:和python里的語法差不多)。序列的元素数目为len(s)。和大多數編程語言類似,区间索引时,Go言里也采用左閉右開形式, 即,区间包括第一个索引元素,不包括最後一个, 因为这样可以簡化邏輯。(譯註:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最後一個元素)。比如s[m:n]這個切片,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。
|
||||
os.Args變量是一個字符串(string)的*切片*(slice)(譯註:slice和Python語言中的切片類似,是一個簡版的動態數組),切片是Go語言的基礎概念,稍后详细介绍。現在先把切片s当作数组元素序列, 序列的成长度动态变化, 用`s[i]`访问单个元素,用`s[m:n]`獲取子序列(譯註:和python里的語法差不多)。序列的元素数目为len(s)。和大多數編程語言類似,区间索引时,Go言里也采用左閉右開形式, 即,区间包括第一个索引元素,不包括最後一个, 因为这样可以簡化邏輯。(譯註:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最後一個元素)。比如s[m:n]這個切片,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。
|
||||
|
||||
os.Args的第一個元素,os.Args[0], 是命令本身的名字;其它的元素則是程序启动时傳給它的參數。s[m:n]形式的切片表達式,产生从第m个元素到第n-1個元素的切片,下個例子用到的元素包含在os.Args[1:len(os.Args)]切片中。如果省略切片表达式的m或n,会默认传入0或len(s),因此前面的切片可以简写成os.Args[1:]。
|
||||
|
||||
@ -32,7 +32,7 @@ func main() {
|
||||
|
||||
註釋语句以`//`开头。对于程序员来说,//之后到行末之间所有的内容都是註釋,被編譯器忽略。按照慣例,我们在每个包的包声明前添加注释;對於`main package`,註釋包含一句或几句话,从整体角度对程序做个描述。
|
||||
|
||||
var声明定义了兩個string类型的變量s和sep。變量会在聲明时直接初始化。如果变量沒有顯式初始化,则会被隐式地賦予其類型的*零值*(zero value),數值類型是0,字符串類型是空字符串""。這個例子里,声明把s和sep隱式地初始化成空字符串。第2章再来詳細地講解變量和聲明。
|
||||
var声明定义了兩個string类型的變量s和sep。變量会在聲明时直接初始化。如果变量沒有顯式初始化,则被隐式地賦予其類型的*零值*(zero value),數值類型是0,字符串類型是空字符串""。這個例子里,声明把s和sep隱式地初始化成空字符串。第2章再来詳細地講解變量和聲明。
|
||||
|
||||
對數值類型,Go語言提供了常規的數值和邏輯運算符。而對string類型,`+`运算符连接字符串(譯註:和C++或者js是一樣的)。所以表達式:
|
||||
|
||||
@ -56,7 +56,7 @@ s = s + sep + os.Args[i]
|
||||
|
||||
echo程序可以每循環一次輸出一個參數,這个版本却是不斷地把新文本追加到末尾来构造字符串。字符串s开始为空,即值为"",每次循環會添加一些文本;第一次迭代之後,还会再插入一個空格,因此循环结束时每个参数中間都有一個空格。這是一種二次加工(quadratic process),当參數數量龐大时,开销很大,但是对于echo,这种情形不大可能出现。本章会介紹echo的若干改進版,下一章解决低效问题。
|
||||
|
||||
循环索引变量i在for循环的第一部分中定义。符號`:=`是短变量声明(short variable declaration)的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。下一章有这方面更多説明。
|
||||
循环索引变量i在for循环的第一部分中定义。符號`:=`是*短变量声明*(short variable declaration)的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。下一章有这方面更多説明。
|
||||
|
||||
自增语句`i++`给`i`加1;這和`i += 1`以及`i = i + 1`都是等價的。對應的還有`i--`給`i`減1。它们是語句,而不像C繫的其它語言那样是表達式。所以`j = i++`非法,而且++和--都隻能放在變量名後面,因此`--i`也非法。
|
||||
|
||||
@ -70,9 +70,9 @@ for initialization; condition; post {
|
||||
|
||||
for循環三个部分不需括號包围。大括号强制要求, 左大括號必须和*post*语句在同一行。
|
||||
|
||||
initialization部分是可選的,如果你寫了這部分的話,在for循環之前這部分的邏輯會被執行。initalization部分必須是一個簡單的語句,具體可以是一個簡短的變量聲明,一個賦值語句,或是一個函數調用。condition部分必須是一個結果爲boolean值的表達式,在每次循環之前,語言都會檢査當前是否滿足這個條件,若不滿足的話便會結束循環;post部分的語句則是在每次循環迭代結束之後被執行,之後conditon部分會在下一次執行前再次進行判斷,依此往複。當condition條件里的判斷結果變爲false之後,循環卽結束。
|
||||
*initialization*语句是可選的,在循環开始前執行。*initalization*如果存在,必須是一条*簡單語句*(simple statement),即,短變量聲明、自增语句、賦值語句或函數調用。`condition`是一個布尔表達式(boolean expression),其值在每次循環迭代开始时计算。如果为`true`则执行循环体语句。`post`語句在循環体执行结束后執行,之後再次对`conditon`求值。`condition`值爲`false`时,循環結束。
|
||||
|
||||
上面提到是for循環里的三個部分(initialization/condition/post)都是可以被省略的,如果你把initialization和post部分都省略的話,那麽連中間隔離他們的分號也是可以被省略的,比如下面這種for循環,和傳統的while循環效果完全一致:
|
||||
for循環的这三個部分每个都可以省略,如果省略`initialization`和`post`,分號也可以省略:
|
||||
|
||||
```go
|
||||
// a traditional "while" loop
|
||||
@ -81,7 +81,7 @@ for condition {
|
||||
}
|
||||
```
|
||||
|
||||
當然了,如果你連唯一的條件都省了,那麽for循環就會變成一個無限循環,像下面這樣:
|
||||
如果連`condition`也省略了,像下面這樣:
|
||||
|
||||
```go
|
||||
// a traditional infinite loop
|
||||
@ -90,9 +90,9 @@ for {
|
||||
}
|
||||
```
|
||||
|
||||
在無限循環中,你還是可以靠break或者return語句來終止掉循環。
|
||||
这就變成一個無限循環,尽管如此,還可以用其他方式終止循環, 如一条`break`或`return`语句。
|
||||
|
||||
如果你的遍歷對象是string或者slice類型值的話,還有另外一種循環的寫法,我們來看看另一個版本的echo:
|
||||
`for`循环的另一种形式, 在某种数据类型的区间(range)上遍历,如字符串或切片。`echo`的第二版本展示了这种形式:
|
||||
|
||||
<u><i>gopl.io/ch1/echo2</i></u>
|
||||
```go
|
||||
@ -113,11 +113,11 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
每一次循環迭代,range都會返迴一對兒結果;當前迭代的下標以及在該下標處的元素的值。這個例子不需要這個下標,但是因爲range函數要求我們必須同時處理下標和元素兩個返迴值。這種時候可以在聲明一個接收下標的臨時變量來解決這個問題,但Go語言又不允許隻聲明變量而在後續代碼里不使用,如果你這樣做了編譯器會返迴一個編譯錯誤。
|
||||
每次循環迭代,`range`产生一對值;索引以及在該索引处的元素值。這個例子不需要索引,但`range`的语法要求, 要處理元素, 必须处理索引。一种思路是把索引赋值给一个臨時變量, 如`temp`, 然后忽略它的值,但Go語言不允許使用无用的局部变量(local variables),因为这会导致編譯錯誤。
|
||||
|
||||
Go語言中這種情況的解決方法是用空白標識符,對,就是代碼里那個下劃線_。空白標識符可以在任何你需要接收自己不想處理的值時使用。這里使用它來忽略掉range返迴的那個沒什麽用的下標值。大多數的Go程序員都會像上面這樣來寫類似的os.Args遍歷,由於遍歷os.Args的下標索引是隱式自動生成的,這里也併不需要關心。
|
||||
Go語言中這種情況的解決方法是用`空標識符`(blank identifier),即`_`(也就是下劃線)。空標識符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。大多數的Go程序員都會像上面這樣使用`range`和`_`寫`echo`程序,因为隱式地而非显示地索引os.Args,容易写对。
|
||||
|
||||
上面這個版本將s和sep的聲明和初始化都放到了一起,但是我們可以等價地將聲明和賦值分開來寫,下面這些寫法都是等價的:
|
||||
`echo`的這個版本使用一条短变量声明来声明并初始化`s`和`seps`,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等價:
|
||||
|
||||
```go
|
||||
s := ""
|
||||
@ -126,11 +126,11 @@ var s = ""
|
||||
var s string = ""
|
||||
```
|
||||
|
||||
那麽這些等價的形式應該怎麽做選擇呢?這里提供一些建議:第一種形式,隻能用在一個函數內部,而package級别的變量不應該這麽做。第二種形式依賴於string類型的內部初始化機製,被初始化爲空字符串。第三種形式使用得很少,除非同時聲明多個變量。第四種形式會顯式地標明變量的類型,在多變量同時聲明時可以用到。實踐中你應該隻使用上面的前兩種形式,顯式地指定變量的類型,讓編譯器自己去初始化其值,或者直接用隱式初始化,表明初始值怎麽樣併不重要。
|
||||
用哪种不用哪种,为什么呢?第一種形式,是一条短变量声明,最简洁,但隻能用在函數內部,而不能用于包變量。第二種形式依賴於字符串的默认初始化零值機製,被初始化为""。第三種形式用得很少,除非同時聲明多個變量。第四種形式顯式地標明變量的類型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。實踐中一般使用前兩種形式中的某个,初始值重要的话就顯式地指定變量的類型,否则使用隱式初始化。
|
||||
|
||||
像上面提到的,每次循環迭代中字符串s都會得到一個新內容。+=語句會分配一個新的字符串,併將老字符串連接起來的值賦予給它。而目標字符串的舊的字面值在得到新值以後就失去了用處,這些臨時值會被Go語言的垃圾收集器榦掉。
|
||||
如前文所述,每次循環迭代字符串s的內容都会更新。`+=`连接原字符串、空格和下个参数,产生新字符串, 并把它赋值给`s`。`s`原来的内容已经不再使用,将在适当时机对它进行垃圾回收。
|
||||
|
||||
如果不斷連接的字符串數量衆多,那麽上面這種操作就是成本非常高的操作。更簡單併且有效的一種方式是使用strings包提供的Join函數,像下面這樣:
|
||||
如果連接涉及的数据量很大,这种方式代价高昂。一種簡單且高效的解决方案是使用`strings`包的`Join`函數:
|
||||
|
||||
<u><i>gopl.io/ch1/echo3</i></u>
|
||||
```go
|
||||
@ -139,16 +139,16 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
最後,如果我們對輸出的格式也不是很關心,隻是想簡單地輸出值得的話,還可以像下面這麽寫,Println函數會爲我們自動格式化輸出。
|
||||
最後,如果不關心輸出格式,隻想看看輸出值,或许只是为了调试,可以用`Println`为我们格式化輸出。
|
||||
|
||||
```go
|
||||
fmt.Println(os.Args[1:])
|
||||
```
|
||||
|
||||
這個輸出結果和前面的strings.Join得到的結果很相似,隻是輸出被放到了一個方括號里,對slice類型調用Println函數都會被打印成這種形式的結果。
|
||||
這条语句的輸出結果跟`strings.Join`得到的結果很像,隻是被放到了一对方括號里。切片都會被打印成這種格式。
|
||||
|
||||
**練習 1.1:** 脩改echo程序,使其能夠打印os.Args[0]。
|
||||
**練習 1.1:** 脩改`echo`程序,使其能夠打印`os.Args[0]`,即被执行命令本身的名字。
|
||||
|
||||
**練習 1.2:** 脩改echo程序,使其打印value和index,每個value和index顯示一行。
|
||||
**練習 1.2:** 脩改`echo`程序,使其打印每个参数的索引和值,每個一行。
|
||||
|
||||
**練習 1.3:** 上手實踐前面提到的strings.Join和直接Println,併觀察輸出結果的區别。
|
||||
**練習 1.3:** 做实验测量潜在低效的版本和使用了`strings.Join`的版本的运行时间差异。(1.6节讲解了部分`time`包,11.4节展示了如何写标准测试程序,以得到系统性的性能评测。)
|
||||
|
Loading…
Reference in New Issue
Block a user