diff --git a/ch1/ch1-02.md b/ch1/ch1-02.md index d5a8ed1..b5282be 100644 --- a/ch1/ch1-02.md +++ b/ch1/ch1-02.md @@ -1,14 +1,14 @@ ## 1.2. 命令行參數 -大多數的程序都是處理輸入,産生輸出;這也正是“計算”的定義。但是, 程序如何獲取要处理的輸入数据呢?一些程序生成自己的數據,但通常情況下,輸入來自於程序外部:文件、網絡連接、其它程序的輸出、敲鍵盤的用户、命令行參數或其它類似輸入源。下面幾個例子會討論其中几个輸入源,首先是命令行參數。 +大多數的程序都是處理輸入,産生輸出;這也正是“計算”的定義。但是, 程序如何獲取要處理的輸入數據呢?一些程序生成自己的數據,但通常情況下,輸入來自於程序外部:文件、網絡連接、其它程序的輸出、敲鍵盤的用戶、命令行參數或其它類似輸入源。下面幾個例子會討論其中幾個輸入源,首先是命令行參數。 -`os`包以跨平台的方式,提供了一些與操作繫統交互的函數和變量。程序的命令行參數可从os包的Args變量获取;os包外部使用os.Args访问該變量。 +`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:]。 +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:]。 -下面是Unix里echo命令的一份實現,echo把它的命令行參數打印成一行。程序导入了兩個包,用括號把它们括起来写成列表形式, 而没有分开写成独立的`import`声明。两种形式都合法,列表形式习惯上用得多。包導入順序併不重要;gofmt工具格式化時按照字母順序对包名排序。(示例有多个版本时,我们会对示例编号, 这样可以明确当前正在讨论的是哪个。) +下面是Unix里echo命令的一份實現,echo把它的命令行參數打印成一行。程序導入了兩個包,用括號把它們括起來寫成列表形式, 而沒有分開寫成獨立的`import`聲明。兩種形式都合法,列表形式習慣上用得多。包導入順序併不重要;gofmt工具格式化時按照字母順序對包名排序。(示例有多個版本時,我們會對示例編號, 這樣可以明確當前正在討論的是哪個。) gopl.io/ch1/echo1 ```go @@ -30,37 +30,37 @@ func main() { } ``` -註釋语句以`//`开头。对于程序员来说,//之后到行末之间所有的内容都是註釋,被編譯器忽略。按照慣例,我们在每个包的包声明前添加注释;對於`main package`,註釋包含一句或几句话,从整体角度对程序做个描述。 +註釋語句以`//`開頭。對於程序員來説,//之後到行末之間所有的內容都是註釋,被編譯器忽略。按照慣例,我們在每個包的包聲明前添加註釋;對於`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是一樣的)。所以表達式: +對數值類型,Go語言提供了常規的數值和邏輯運算符。而對string類型,`+`運算符連接字符串(譯註:和C++或者js是一樣的)。所以表達式: ```go sep + os.Args[i] ``` -表示連接字符串sep和os.Args。程序中使用的语句: +表示連接字符串sep和os.Args。程序中使用的語句: ```go s += sep + os.Args[i] ``` -是一条*赋值语句*, 將s的旧值跟sep與os.Args[i]連接后賦值回s,等价于: +是一條*賦值語句*, 將s的舊值跟sep與os.Args[i]連接後賦值迴s,等價於: ```go s = s + sep + os.Args[i] ``` -運算符`+=`是賦值運算符(assignment operator),每種數值运算符或邏輯運算符,如`+`或`*`,都有對應的賦值運算符。 +運算符`+=`是賦值運算符(assignment operator),每種數值運算符或邏輯運算符,如`+`或`*`,都有對應的賦值運算符。 -echo程序可以每循環一次輸出一個參數,這个版本却是不斷地把新文本追加到末尾来构造字符串。字符串s开始为空,即值为"",每次循環會添加一些文本;第一次迭代之後,还会再插入一個空格,因此循环结束时每个参数中間都有一個空格。這是一種二次加工(quadratic process),当參數數量龐大时,开销很大,但是对于echo,这种情形不大可能出现。本章会介紹echo的若干改進版,下一章解决低效问题。 +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`也非法。 +自增語句`i++`給`i`加1;這和`i += 1`以及`i = i + 1`都是等價的。對應的還有`i--`給`i`減1。它們是語句,而不像C繫的其它語言那樣是表達式。所以`j = i++`非法,而且++和--都隻能放在變量名後面,因此`--i`也非法。 -Go語言隻有for循環这一種循環语句。for循環有多种形式,其中一种如下所示: +Go語言隻有for循環這一種循環語句。for循環有多種形式,其中一種如下所示: ```go for initialization; condition; post { @@ -68,11 +68,11 @@ for initialization; condition; post { } ``` -for循環三个部分不需括號包围。大括号强制要求, 左大括號必须和*post*语句在同一行。 +for循環三個部分不需括號包圍。大括號強製要求, 左大括號必須和*post*語句在同一行。 -*initialization*语句是可選的,在循環开始前執行。*initalization*如果存在,必須是一条*簡單語句*(simple statement),即,短變量聲明、自增语句、賦值語句或函數調用。`condition`是一個布尔表達式(boolean expression),其值在每次循環迭代开始时计算。如果为`true`则执行循环体语句。`post`語句在循環体执行结束后執行,之後再次对`conditon`求值。`condition`值爲`false`时,循環結束。 +*initialization*語句是可選的,在循環開始前執行。*initalization*如果存在,必須是一條*簡單語句*(simple statement),卽,短變量聲明、自增語句、賦值語句或函數調用。`condition`是一個布爾表達式(boolean expression),其值在每次循環迭代開始時計算。如果爲`true`則執行循環體語句。`post`語句在循環體執行結束後執行,之後再次對`conditon`求值。`condition`值爲`false`時,循環結束。 -for循環的这三個部分每个都可以省略,如果省略`initialization`和`post`,分號也可以省略: +for循環的這三個部分每個都可以省略,如果省略`initialization`和`post`,分號也可以省略: ```go // a traditional "while" loop @@ -90,9 +90,9 @@ for { } ``` -这就變成一個無限循環,尽管如此,還可以用其他方式終止循環, 如一条`break`或`return`语句。 +這就變成一個無限循環,盡管如此,還可以用其他方式終止循環, 如一條`break`或`return`語句。 -`for`循环的另一种形式, 在某种数据类型的区间(range)上遍历,如字符串或切片。`echo`的第二版本展示了这种形式: +`for`循環的另一種形式, 在某種數據類型的區間(range)上遍歷,如字符串或切片。`echo`的第二版本展示了這種形式: gopl.io/ch1/echo2 ```go @@ -113,11 +113,11 @@ func main() { } ``` -每次循環迭代,`range`产生一對值;索引以及在該索引处的元素值。這個例子不需要索引,但`range`的语法要求, 要處理元素, 必须处理索引。一种思路是把索引赋值给一个臨時變量, 如`temp`, 然后忽略它的值,但Go語言不允許使用无用的局部变量(local variables),因为这会导致編譯錯誤。 +每次循環迭代,`range`産生一對值;索引以及在該索引處的元素值。這個例子不需要索引,但`range`的語法要求, 要處理元素, 必須處理索引。一種思路是把索引賦值給一個臨時變量, 如`temp`, 然後忽略它的值,但Go語言不允許使用無用的局部變量(local variables),因爲這會導致編譯錯誤。 -Go語言中這種情況的解決方法是用`空標識符`(blank identifier),即`_`(也就是下劃線)。空標識符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。大多數的Go程序員都會像上面這樣使用`range`和`_`寫`echo`程序,因为隱式地而非显示地索引os.Args,容易写对。 +Go語言中這種情況的解決方法是用`空標識符`(blank identifier),卽`_`(也就是下劃線)。空標識符可用於任何語法需要變量名但程序邏輯不需要的時候, 例如, 在循環里,丟棄不需要的循環索引, 保留元素值。大多數的Go程序員都會像上面這樣使用`range`和`_`寫`echo`程序,因爲隱式地而非顯示地索引os.Args,容易寫對。 -`echo`的這個版本使用一条短变量声明来声明并初始化`s`和`seps`,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等價: +`echo`的這個版本使用一條短變量聲明來聲明併初始化`s`和`seps`,也可以將這兩個變量分開聲明,聲明一個變量有好幾種方式,下面這些都等價: ```go s := "" @@ -126,11 +126,11 @@ var s = "" var s string = "" ``` -用哪种不用哪种,为什么呢?第一種形式,是一条短变量声明,最简洁,但隻能用在函數內部,而不能用于包變量。第二種形式依賴於字符串的默认初始化零值機製,被初始化为""。第三種形式用得很少,除非同時聲明多個變量。第四種形式顯式地標明變量的類型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。實踐中一般使用前兩種形式中的某个,初始值重要的话就顯式地指定變量的類型,否则使用隱式初始化。 +用哪種不用哪種,爲什麽呢?第一種形式,是一條短變量聲明,最簡潔,但隻能用在函數內部,而不能用於包變量。第二種形式依賴於字符串的默認初始化零值機製,被初始化爲""。第三種形式用得很少,除非同時聲明多個變量。第四種形式顯式地標明變量的類型,當變量類型與初值類型相同時,類型冗餘,但如果兩者類型不同,變量類型就必須了。實踐中一般使用前兩種形式中的某個,初始值重要的話就顯式地指定變量的類型,否則使用隱式初始化。 -如前文所述,每次循環迭代字符串s的內容都会更新。`+=`连接原字符串、空格和下个参数,产生新字符串, 并把它赋值给`s`。`s`原来的内容已经不再使用,将在适当时机对它进行垃圾回收。 +如前文所述,每次循環迭代字符串s的內容都會更新。`+=`連接原字符串、空格和下個參數,産生新字符串, 併把它賦值給`s`。`s`原來的內容已經不再使用,將在適當時機對它進行垃圾迴收。 -如果連接涉及的数据量很大,这种方式代价高昂。一種簡單且高效的解决方案是使用`strings`包的`Join`函數: +如果連接涉及的數據量很大,這種方式代價高昂。一種簡單且高效的解決方案是使用`strings`包的`Join`函數: gopl.io/ch1/echo3 ```go @@ -139,16 +139,16 @@ func main() { } ``` -最後,如果不關心輸出格式,隻想看看輸出值,或许只是为了调试,可以用`Println`为我们格式化輸出。 +最後,如果不關心輸出格式,隻想看看輸出值,或許隻是爲了調試,可以用`Println`爲我們格式化輸出。 ```go fmt.Println(os.Args[1:]) ``` -這条语句的輸出結果跟`strings.Join`得到的結果很像,隻是被放到了一对方括號里。切片都會被打印成這種格式。 +這條語句的輸出結果跟`strings.Join`得到的結果很像,隻是被放到了一對方括號里。切片都會被打印成這種格式。 -**練習 1.1:** 脩改`echo`程序,使其能夠打印`os.Args[0]`,即被执行命令本身的名字。 +**練習 1.1:** 脩改`echo`程序,使其能夠打印`os.Args[0]`,卽被執行命令本身的名字。 -**練習 1.2:** 脩改`echo`程序,使其打印每个参数的索引和值,每個一行。 +**練習 1.2:** 脩改`echo`程序,使其打印每個參數的索引和值,每個一行。 -**練習 1.3:** 做实验测量潜在低效的版本和使用了`strings.Join`的版本的运行时间差异。(1.6节讲解了部分`time`包,11.4节展示了如何写标准测试程序,以得到系统性的性能评测。) +**練習 1.3:** 做實驗測量潛在低效的版本和使用了`strings.Join`的版本的運行時間差異。(1.6節講解了部分`time`包,11.4節展示了如何寫標準測試程序,以得到繫統性的性能評測。)