W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch1/ch1-02.html
大多數(shù)的程序都是處理輸入,產(chǎn)生輸出;這也正是“計(jì)算”的定義。但是,程序如何獲取要處理的輸入數(shù)據(jù)呢?一些程序生成自己的數(shù)據(jù),但通常情況下,輸入來自于程序外部:文件、網(wǎng)絡(luò)連接、其它程序的輸出、敲鍵盤的用戶、命令行參數(shù)或其它類似輸入源。下面幾個(gè)例子會(huì)討論其中幾個(gè)輸入源,首先是命令行參數(shù)。
?os
?包以跨平臺(tái)的方式,提供了一些與操作系統(tǒng)交互的函數(shù)和變量。程序的命令行參數(shù)可從 ?os
?包的 ?Args
?變量獲?。?os
?包外部使用 ?os.Args
? 訪問該變量。
os.Args
變量是一個(gè)字符串(string)的 切片(slice)(譯注:slice 和 Python 語言中的切片類似,是一個(gè)簡(jiǎn)版的動(dòng)態(tài)數(shù)組),切片是 Go 語言的基礎(chǔ)概念,稍后詳細(xì)介紹?,F(xiàn)在先把切片 s
當(dāng)作數(shù)組元素序列,序列的長(zhǎng)度動(dòng)態(tài)變化,用 s[i]
訪問單個(gè)元素,用 s[m:n]
獲取子序列(譯注:和
Python 里的語法差不多)。序列的元素?cái)?shù)目為 len(s)
。和大多數(shù)編程語言類似,區(qū)間索引時(shí),Go 語言里也采用左閉右開形式,即,區(qū)間包括第一個(gè)索引元素,不包括最后一個(gè),因?yàn)檫@樣可以簡(jiǎn)化邏輯。(譯注:比如 a=[1,2,3,4,5]
, a[0:3]=[1,2,3]
,不包含最后一個(gè)元素)。比如 s[m:n]
這個(gè)切片,0≤m≤n≤len(s)
,包含 n-m
個(gè)元素。
os.Args
的第一個(gè)元素:os.Args[0]
,是命令本身的名字;其它的元素則是程序啟動(dòng)時(shí)傳給它的參數(shù)。s[m:n]
形式的切片表達(dá)式,產(chǎn)生從第 m
個(gè)元素到第 n-1
個(gè)元素的切片,下個(gè)例子用到的元素包含在 os.Args[1:len(os.Args)]
切片中。如果省略切片表達(dá)式的 m
或 n
,會(huì)默認(rèn)傳入 0
或 len(s)
,因此前面的切片可以簡(jiǎn)寫成 os.Args[1:]
。
下面是 Unix 里 echo
命令的一份實(shí)現(xiàn),echo
把它的命令行參數(shù)打印成一行。程序?qū)肓藘蓚€(gè)包,用括號(hào)把它們括起來寫成列表形式,而沒有分開寫成獨(dú)立的 import
聲明。兩種形式都合法,列表形式習(xí)慣上用得多。包導(dǎo)入順序并不重要;gofmt
工具格式化時(shí)按照字母順序?qū)Π判?。(示例有多個(gè)版本時(shí),我們會(huì)對(duì)示例編號(hào),這樣可以明確當(dāng)前正在討論的是哪個(gè)。)
gopl.io/ch1/echo1
// 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)
}
注釋語句以 //
開頭。對(duì)于程序員來說,//
之后到行末之間所有的內(nèi)容都是注釋,被編譯器忽略。按照慣例,我們?cè)诿總€(gè)包的包聲明前添加注釋;對(duì)于 main package
,注釋包含一句或幾句話,從整體角度對(duì)程序做個(gè)描述。
var
聲明定義了兩個(gè) string
類型的變量 s
和 sep
。變量會(huì)在聲明時(shí)直接初始化。如果變量沒有顯式初始化,則被隱式地賦予其類型的 零值(zero value),數(shù)值類型是 0
,字符串類型是空字符串 ""
。這個(gè)例子里,聲明把 s
和 sep
隱式地初始化成空字符串。第
2 章再來詳細(xì)地講解變量和聲明。
對(duì)數(shù)值類型,Go 語言提供了常規(guī)的數(shù)值和邏輯運(yùn)算符。而對(duì) string
類型,+
運(yùn)算符連接字符串(譯注:和 C++ 或者 JavaScript 是一樣的)。所以表達(dá)式:sep + os.Args[i]
表示連接字符串 sep
和 os.Args
。程序中使用的語句:s+=sep+os.Args[i]
是一條 賦值語句,將 s
的舊值跟 sep
與 os.Args[i]
連接后賦值回 s
,等價(jià)于:s=s+sep+os.Args[i]
。
運(yùn)算符 +=
是賦值運(yùn)算符(assignment operator),每種數(shù)值運(yùn)算符或邏輯運(yùn)算符,如 +
或 *
,都有對(duì)應(yīng)的賦值運(yùn)算符。
echo
程序可以每循環(huán)一次輸出一個(gè)參數(shù),這個(gè)版本卻是不斷地把新文本追加到末尾來構(gòu)造字符串。字符串 s
開始為空,即值為 ""
,每次循環(huán)會(huì)添加一些文本;第一次迭代之后,還會(huì)再插入一個(gè)空格,因此循環(huán)結(jié)束時(shí)每個(gè)參數(shù)中間都有一個(gè)空格。這是一種二次加工(quadratic process),當(dāng)參數(shù)數(shù)量龐大時(shí),開銷很大,但是對(duì)于 echo
,這種情形不大可能出現(xiàn)。本章會(huì)介紹 echo
的若干改進(jìn)版,下一章解決低效問題。
循環(huán)索引變量 i
在 for
循環(huán)的第一部分中定義。符號(hào) :=
是 短變量聲明(short variable declaration)的一部分,這是定義一個(gè)或多個(gè)變量并根據(jù)它們的初始值為這些變量賦予適當(dāng)類型的語句。下一章有這方面更多說明。
自增語句 i++
給 i
加 1
;這和 i+=1
以及 i=i+1
都是等價(jià)的。對(duì)應(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
}
for
循環(huán)三個(gè)部分不需括號(hào)包圍。大括號(hào)強(qiáng)制要求,左大括號(hào)必須和 post
語句在同一行。
initialization
語句是可選的,在循環(huán)開始前執(zhí)行。initalization
如果存在,必須是一條 簡(jiǎn)單語句(simple statement),即,短變量聲明、自增語句、賦值語句或函數(shù)調(diào)用。condition
是一個(gè)布爾表達(dá)式(boolean expression),其值在每次循環(huán)迭代開始時(shí)計(jì)算。如果為 true
則執(zhí)行循環(huán)體語句。post
語句在循環(huán)體執(zhí)行結(jié)束后執(zhí)行,之后再次對(duì) condition
求值。condition
值為 false
時(shí),循環(huán)結(jié)束。
for 循環(huán)的這三個(gè)部分每個(gè)都可以省略,如果省略 initialization
和 post
,分號(hào)也可以省略:
// a traditional "while" loop
for condition {
// ...
}
如果連 condition
也省略了,像下面這樣:
// a traditional infinite loop
for {
// ...
}
這就變成一個(gè)無限循環(huán),盡管如此,還可以用其他方式終止循環(huán),如一條 ?break
?或 ?return
?語句。
?for
?循環(huán)的另一種形式,在某種數(shù)據(jù)類型的區(qū)間(range)上遍歷,如字符串或切片。?echo
?的第二版本展示了這種形式:
gopl.io/ch1/echo2
// 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)生一對(duì)值;索引以及在該索引處的元素值。這個(gè)例子不需要索引,但 range
的語法要求,要處理元素,必須處理索引。一種思路是把索引賦值給一個(gè)臨時(shí)變量(如 temp
)然后忽略它的值,但 Go 語言不允許使用無用的局部變量(local variables),因?yàn)檫@會(huì)導(dǎo)致編譯錯(cuò)誤。
Go 語言中這種情況的解決方法是用 空標(biāo)識(shí)符(blank identifier),即 _
(也就是下劃線)??諛?biāo)識(shí)符可用于在任何語法需要變量名但程序邏輯不需要的時(shí)候(如:在循環(huán)里)丟棄不需要的循環(huán)索引,并保留元素值。大多數(shù)的 Go 程序員都會(huì)像上面這樣使用 range
和 _
寫 echo
程序,因?yàn)殡[式地而非顯式地索引 os.Args
,容易寫對(duì)。
echo
的這個(gè)版本使用一條短變量聲明來聲明并初始化 s
和 seps
,也可以將這兩個(gè)變量分開聲明,聲明一個(gè)變量有好幾種方式,下面這些都等價(jià):
s := ""
var s string
var s = ""
var s string = ""
用哪種不用哪種,為什么呢?第一種形式,是一條短變量聲明,最簡(jiǎn)潔,但只能用在函數(shù)內(nèi)部,而不能用于包變量。第二種形式依賴于字符串的默認(rèn)初始化零值機(jī)制,被初始化為 ""
。第三種形式用得很少,除非同時(shí)聲明多個(gè)變量。第四種形式顯式地標(biāo)明變量的類型,當(dāng)變量類型與初值類型相同時(shí),類型冗余,但如果兩者類型不同,變量類型就必須了。實(shí)踐中一般使用前兩種形式中的某個(gè),初始值重要的話就顯式地指定變量的類型,否則使用隱式初始化。
如前文所述,每次循環(huán)迭代字符串 s
的內(nèi)容都會(huì)更新。+=
連接原字符串、空格和下個(gè)參數(shù),產(chǎn)生新字符串,并把它賦值給 s
。s
原來的內(nèi)容已經(jīng)不再使用,將在適當(dāng)時(shí)機(jī)對(duì)它進(jìn)行垃圾回收。
如果連接涉及的數(shù)據(jù)量很大,這種方式代價(jià)高昂。一種簡(jiǎn)單且高效的解決方案是使用 strings
包的 Join
函數(shù):
gopl.io/ch1/echo3
func main() {
fmt.Println(strings.Join(os.Args[1:], " "))
}
最后,如果不關(guān)心輸出格式,只想看看輸出值,或許只是為了調(diào)試,可以用 Println
為我們格式化輸出。
fmt.Println(os.Args[1:])
這條語句的輸出結(jié)果跟 strings.Join
得到的結(jié)果很像,只是被放到了一對(duì)方括號(hào)里。切片都會(huì)被打印成這種格式。
練習(xí) 1.1: 修改 echo
程序,使其能夠打印 os.Args[0]
,即被執(zhí)行命令本身的名字。
練習(xí) 1.2: 修改 echo
程序,使其打印每個(gè)參數(shù)的索引和值,每個(gè)一行。
練習(xí) 1.3: 做實(shí)驗(yàn)測(cè)量潛在低效的版本和使用了 strings.Join
的版本的運(yùn)行時(shí)間差異。(1.6 節(jié)講解了部分 time
包,11.4 節(jié)展示了如何寫標(biāo)準(zhǔn)測(cè)試程序,以得到系統(tǒng)性的性能評(píng)測(cè)。)
![]() |
![]() |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: