命令行參數(shù)
os包提供了一些與操作系統(tǒng)交互的函數(shù)和變量聚磺,并且go對其做了一些封裝鹃栽。程序的命令行參數(shù)可以從os包的Args變量獲却┌狻;os包外部使用os.Args訪問該變量借尿。
os.Args變量是一個字符串的切片(slice)刨晴。學(xué)過Python的同學(xué)可以很容易理解切片的概念。現(xiàn)在先把切片s當(dāng)做數(shù)組元素序列路翻,序列的成長度動態(tài)變化狈癞,用s[i]訪問單個元素,用s[m:n]獲取子序列茂契。序列的元素個數(shù)為len(s)亿驾。和大多數(shù)編程語言類似,區(qū)間索引時账嚎,Go言里也采用左閉右開形式, 即莫瞬,區(qū)間包括第一個索引元素,不包括最后一個, 因?yàn)檫@樣可以簡化邏輯郭蕉。
os.Args的第一個元素疼邀,os.Args[0], 是命令本身的名字;其它的元素則是程序啟動時傳給它的參數(shù)召锈。s[m:n]形式的切片表達(dá)式旁振,產(chǎn)生從第m個元素到第n-1個元素的切片,下個例子用到的元素包含在os.Args[1:len(os.Args)]切片中涨岁。如果省略切片表達(dá)式的m或n拐袜,會默認(rèn)傳入0或len(s),因此前面的切片可以簡寫成os.Args[1:]梢薪。
下面是Unix里echo命令的一份實(shí)現(xiàn)蹬铺,echo把它的命令行參數(shù)打印成一行。程序?qū)肓藘蓚€包秉撇,用括號把它們括起來寫成列表形式, 而沒有分開寫成獨(dú)立的import聲明甜攀。兩種形式都合法,列表形式習(xí)慣上用得多琐馆。包導(dǎo)入順序并不重要规阀;gofmt工具格式化時按照字母順序?qū)Π判颉?/p>
// Echo1 prints its command-line arguments.
package main
import (
"fmt"
"os"
)
func main() {
var s, sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i]
sep = " "
}
fmt.Println(s)
}
注釋語句以//開頭。對于程序員來說瘦麸,//之后到行末之間所有的內(nèi)容都是注釋谁撼,被編譯器忽略。按照慣例滋饲,我們在每個包的包聲明前添加注釋厉碟;對于main package喊巍,注釋包含一句或幾句話,從整體角度對程序做個描述墨榄。
var聲明定義了兩個string類型的變量s和sep。變量會在聲明時直接初始化勿她。如果變量沒有顯式初始化袄秩,則被隱式地賦予其類型的零值(zero value),數(shù)值類型是0逢并,字符串類型是空字符串""之剧。這個例子里,聲明把s和sep隱式地初始化成空字符串砍聊。第2章再來詳細(xì)地講解變量和聲明背稼。
對數(shù)值類型,Go語言提供了常規(guī)的數(shù)值和邏輯運(yùn)算符玻蝌。而對string類型蟹肘,+運(yùn)算符連接字符串。所以表達(dá)式:
sep + os.Args[i]
表示連接字符串sep和os.Args俯树。程序中使用的語句:
s += sep + os.Args[i]
是一條賦值語句, 將s的舊值跟sep與os.Args[i]連接后賦值回s帘腹,等價于:
s = s + sep + os.Args[i]
運(yùn)算符+=是賦值運(yùn)算符(assignment operator),每種數(shù)值運(yùn)算符或邏輯運(yùn)算符许饿,如+或*阳欲,都有對應(yīng)的賦值運(yùn)算符。
echo程序可以每循環(huán)一次輸出一個參數(shù)陋率,這個版本卻是不斷地把新文本追加到末尾來構(gòu)造字符串球化。字符串s開始為空,即值為""瓦糟,每次循環(huán)會添加一些文本筒愚;第一次迭代之后,還會再插入一個空格菩浙,因此循環(huán)結(jié)束時每個參數(shù)中間都有一個空格锨能。這是一種二次加工(quadratic process),當(dāng)參數(shù)數(shù)量龐大時芍耘,開銷很大址遇,但是對于echo,這種情形不大可能出現(xiàn)斋竞。本章會介紹echo的若干改進(jìn)版倔约,下一章解決低效問題。
循環(huán)索引變量i在for循環(huán)的第一部分中定義坝初。符號:=是短變量聲明(short variable declaration)的一部分, 這是定義一個或多個變量并根據(jù)它們的初始值為這些變量賦予適當(dāng)類型的語句浸剩。下一章有這方面更多說明钾军。
自增語句i++給i加1;這和i += 1以及i = i + 1都是等價的绢要。對應(yīng)的還有i--給i減1吏恭。它們是語句,而不像C系的其它語言那樣是表達(dá)式重罪。所以j = i++非法樱哼,而且++和--都只能放在變量名后面,因此--i也非法剿配。
Go語言只有for循環(huán)這一種循環(huán)語句搅幅。for循環(huán)有多種形式,其中一種如下所示:
for initialization; condition; post {
// zero or more statements
}
or循環(huán)三個部分不需括號包圍呼胚。大括號強(qiáng)制要求, 左大括號必須和post語句在同一行茄唐。
initialization語句是可選的,在循環(huán)開始前執(zhí)行蝇更。initalization如果存在沪编,必須是一條簡單語句(simple statement),即年扩,短變量聲明漾抬、自增語句、賦值語句或函數(shù)調(diào)用常遂。condition是一個布爾表達(dá)式(boolean expression)纳令,其值在每次循環(huán)迭代開始時計(jì)算。如果為true則執(zhí)行循環(huán)體語句克胳。post語句在循環(huán)體執(zhí)行結(jié)束后執(zhí)行平绩,之后再次對conditon求值。condition值為false時漠另,循環(huán)結(jié)束捏雌。
for循環(huán)的這三個部分每個都可以省略,如果省略initialization和post笆搓,分號也可以省略:
// a traditional "while" loop
for condition {
// ...
}
如果連condition也省略了性湿,像下面這樣:
// a traditional infinite loop
for {
// ...
}
這就變成一個無限循環(huán),盡管如此满败,還可以用其他方式終止循環(huán), 如一條break或return語句肤频。
for循環(huán)的另一種形式, 在某種數(shù)據(jù)類型的區(qū)間(range)上遍歷,如字符串或切片算墨。echo的第二版本展示了這種形式:
// Echo2 prints its command-line arguments.
package main
import (
"fmt"
"os"
)
func main() {
s, sep := "", ""
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
fmt.Println(s)
}
每次循環(huán)迭代宵荒,range產(chǎn)生一對值;索引以及在該索引處的元素值。這個例子不需要索引报咳,但range的語法要求, 要處理元素, 必須處理索引侠讯。一種思路是把索引賦值給一個臨時變量, 如temp, 然后忽略它的值,但Go語言不允許使用無用的局部變量(local variables)暑刃,因?yàn)檫@會導(dǎo)致編譯錯誤厢漩。
Go語言中這種情況的解決方法是用空標(biāo)識符(blank identifier),即(也就是下劃線)岩臣×锸龋空標(biāo)識符可用于任何語法需要變量名但程序邏輯不需要的時候, 例如, 在循環(huán)里,丟棄不需要的循環(huán)索引, 保留元素值婿脸。大多數(shù)的Go程序員都會像上面這樣使用range和寫echo程序粱胜,因?yàn)殡[式地而非顯示地索引os.Args柄驻,容易寫對狐树。
echo的這個版本使用一條短變量聲明來聲明并初始化s和seps,也可以將這兩個變量分開聲明鸿脓,聲明一個變量有好幾種方式抑钟,下面這些都等價:
s := ""
var s string
var s = ""
var s string = ""
用哪種不用哪種,為什么呢野哭?第一種形式在塔,是一條短變量聲明,最簡潔拨黔,但只能用在函數(shù)內(nèi)部蛔溃,而不能用于包變量。第二種形式依賴于字符串的默認(rèn)初始化零值機(jī)制篱蝇,被初始化為""贺待。第三種形式用得很少,除非同時聲明多個變量零截。第四種形式顯式地標(biāo)明變量的類型麸塞,當(dāng)變量類型與初值類型相同時,類型冗余涧衙,但如果兩者類型不同哪工,變量類型就必須了。實(shí)踐中一般使用前兩種形式中的某個弧哎,初始值重要的話就顯式地指定變量的類型雁比,否則使用隱式初始化。
如前文所述撤嫩,每次循環(huán)迭代字符串s的內(nèi)容都會更新章贞。+=連接原字符串、空格和下個參數(shù),產(chǎn)生新字符串, 并把它賦值給s鸭限。s原來的內(nèi)容已經(jīng)不再使用蜕径,將在適當(dāng)時機(jī)對它進(jìn)行垃圾回收。
如果連接涉及的數(shù)據(jù)量很大败京,這種方式代價高昂兜喻。一種簡單且高效的解決方案是使用strings包的Join函數(shù):
func main() {
fmt.Println(strings.Join(os.Args[1:], " "))
}
最后,如果不關(guān)心輸出格式赡麦,只想看看輸出值朴皆,或許只是為了調(diào)試,可以用Println為我們格式化輸出泛粹。
fmt.Println(os.Args[1:])
這條語句的輸出結(jié)果跟strings.Join得到的結(jié)果很像遂铡,只是被放到了一對方括號里。切片都會被打印成這種格式晶姊。
練習(xí) 1.1: 修改echo程序扒接,使其能夠打印os.Args[0],即被執(zhí)行命令本身的名字们衙。
package main
import (
"fmt"
"os"
)
func main() {
s, sep := "", ""
fmt.Println("file:",os.Args[0])
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
fmt.Println(s)
}
練習(xí) 1.2: 修改echo程序钾怔,使其打印每個參數(shù)的索引和值,每個一行蒙挑。
package main
import (
"fmt"
"os"
)
func main() {
for idx, arg := range os.Args[1:] {
fmt.Println(idx,arg)
}
}
練習(xí) 1.3: 做實(shí)驗(yàn)測量潛在低效的版本和使用了strings.Join的版本的運(yùn)行時間差異宗侦。(1.6節(jié)講解了部分time包,11.4節(jié)展示了如何寫標(biāo)準(zhǔn)測試程序忆蚀,以得到系統(tǒng)性的性能評測矾利。)