Go 的類(lèi)型聲明的設(shè)計(jì)健民。
本篇文章為官方文檔 Go's Declaration Syntax 的翻譯抒巢。
引言
剛剛?cè)腴T(mén) Go 語(yǔ)言的菜鳥(niǎo)們常常會(huì)很疑惑,為什么類(lèi)型聲明的語(yǔ)法會(huì)與 C 語(yǔ)言家族建立的傳統(tǒng)不同秉犹。這篇文檔將會(huì)比較這兩種聲明的方式蛉谜,并解釋為什么 Go 語(yǔ)言的聲明語(yǔ)法要如此設(shè)計(jì)。
C 的語(yǔ)法
首先我們看看 C 的語(yǔ)法凤优。C 采用了一種不同尋常悦陋,但是很聰明的聲明語(yǔ)法。我們只需在帶有目標(biāo)變量名的表達(dá)式里指明該表達(dá)式本身的類(lèi)型即可筑辨,而不需要特定的聲明語(yǔ)法俺驶。因此
int x;
聲明了 x
變量,其類(lèi)型為 int
:這個(gè)表達(dá)式 x
的類(lèi)型為 int
棍辕。一般情況下暮现,為了指明新變量的類(lèi)型,我們要寫(xiě)出一個(gè)含有我們要聲明的變量的表達(dá)式楚昭,并且這個(gè)表達(dá)式的值屬于某種基本類(lèi)型栖袋;我們把這個(gè)基本類(lèi)型寫(xiě)到表達(dá)式的左邊。
所以抚太,聲明
int *p;
int a[3];
指明了 p
是一個(gè)int
類(lèi)型的指針塘幅,因?yàn)楸磉_(dá)式 *p
的類(lèi)型為 int
;而 a
是一個(gè) int
類(lèi)型的數(shù)組尿贫,因?yàn)?a[3]
的類(lèi)型為 int
(這里的索引值不用管电媳,它只是表明數(shù)組的長(zhǎng)度)。
那函數(shù)的類(lèi)型聲明呢庆亡?一開(kāi)始在 C 的函數(shù)聲明中匾乓,參數(shù)的類(lèi)型是寫(xiě)在括號(hào)外的,像下面這樣:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
如前所述又谋,我們可以看到 main
之所以是函數(shù)拼缝,是因?yàn)楸磉_(dá)式 main(argc, argv)
返回 int
娱局。在現(xiàn)代記法中我們會(huì)寫(xiě)作:
int main(int argc, char *argv[]) { /* ... */ }
因?yàn)榛镜慕Y(jié)構(gòu)是一樣的。
這種聰明的語(yǔ)法在簡(jiǎn)單類(lèi)型的聲明上表現(xiàn)不錯(cuò)咧七,但實(shí)際上很快就能讓人犯迷糊衰齐。著名的例子就是聲明函數(shù)指針。按照這套聲明規(guī)則猪叙,我們要寫(xiě)成:
int (*fp)(int a, int b);
這里 fp
之所以是一個(gè)函數(shù)指針是因?yàn)?(*fp)(a, b)
這個(gè)表達(dá)式將會(huì)調(diào)用一個(gè)函數(shù)娇斩,并返回 int
類(lèi)型的值。如果當(dāng) fp
的某個(gè)參數(shù)本身又是一個(gè)函數(shù)穴翩,情況會(huì)怎樣呢犬第?
int (*fp)(int (*ff)(int x, int y), int b)
這時(shí)候讀起來(lái)就點(diǎn)難了。
當(dāng)然芒帕,我們聲明函數(shù)時(shí)是可以忽略參數(shù)名的歉嗓,因此 main
函數(shù)可以聲明為:
int main(int, char *[])
回想一下,之前 argv 是這樣聲明的:
char *argv[]
所以你其實(shí)是從聲明的中間去掉變量名背蟆,從而構(gòu)造出其變量類(lèi)型鉴分。這樣看起來(lái)非常不直觀(guān),你聲明某個(gè) char *[]
類(lèi)型的變量的時(shí)候带膀,竟然要把變量名放在了變量類(lèi)型的中間志珍。
如果我們忽略掉 fp
所有的參數(shù)名會(huì)怎樣呢:
int (*fp)(int (*)(int, int), int)
你不僅不能直觀(guān)地知道參數(shù)名原本應(yīng)該放在哪里:
int (*)(int, int)
而且它不能很清楚地表達(dá)出,這是一個(gè)函數(shù)指針聲明垛叨。我們接著看看伦糯,如果返回值也是個(gè)函數(shù)指針會(huì)怎么樣?
int (*(*fp)(int (*)(int, int), int))(int, int)
這已經(jīng)很難看出是關(guān)于 fp
的聲明了嗽元。
你自己還可以構(gòu)建出比這更復(fù)雜的例子敛纲,但上面的例子已經(jīng)足以解釋 C 的聲明語(yǔ)法引入的復(fù)雜性了。
還有一點(diǎn)需要指出剂癌,由于類(lèi)型語(yǔ)法和聲明語(yǔ)法是一樣的淤翔,要解析中間帶有類(lèi)型的表達(dá)式會(huì)有些難度。這也就是為什么佩谷,例如 C 在做類(lèi)型轉(zhuǎn)換的時(shí)候總是要把類(lèi)型用括號(hào)括起來(lái)旁壮,像這樣
(int)M_PI
Go 的語(yǔ)法
非 C 家族的語(yǔ)言通常在聲明時(shí)使用一種不同的類(lèi)型語(yǔ)法。雖然它們都有一個(gè)分割的符號(hào)谐檀,但通常還是變量名先出現(xiàn)抡谐,然后常常跟著一個(gè)冒號(hào)。按照這樣來(lái)寫(xiě)稚补,我們上面的所舉的例子就會(huì)變成下面這樣(一種虛構(gòu)的童叠,用于說(shuō)明的語(yǔ)言):
x: int
p: pointer to int
a: array[3] of int
這樣的聲明即便有些冗長(zhǎng)框喳,當(dāng)至少是明確的 —— 你只需從左向右讀就行课幕。Go 語(yǔ)言所采用的方案就是以此為基礎(chǔ)的厦坛,但為了追求簡(jiǎn)潔,Go 語(yǔ)言丟掉了冒號(hào)并去掉了部分關(guān)鍵詞:
x int
p *int
a [3]int
在 [3]int
和表達(dá)式中 a
的用法沒(méi)有直接的對(duì)應(yīng)關(guān)系(我們?cè)谙乱还?jié)會(huì)回過(guò)頭來(lái)探討指針的問(wèn)題)乍惊。你付出了的語(yǔ)法不統(tǒng)一的代價(jià)杜秸,但獲得更明確的語(yǔ)法。
下面我們來(lái)考慮函數(shù)的問(wèn)題润绎。雖然在 Go 語(yǔ)言里撬碟,main
函數(shù)實(shí)際上沒(méi)有參數(shù),但是我們直接改編一下之前的 main
函數(shù)的聲明:
func main(argc int, argv []string) int
除了 char
類(lèi)型的數(shù)組變成了 string
外莉撇,粗略看來(lái)和 C 沒(méi)什么不同呢蛤,不過(guò)自左向右讀起來(lái)非常順暢:
-
main
函數(shù)接受一個(gè)int
變量和string
類(lèi)型的slice
變量,并返回一個(gè)int
棍郎。 - 如果此時(shí)把參數(shù)名去掉其障,它還是很明確 —— 因?yàn)閰?shù)名總在類(lèi)型的前面,所以不會(huì)引起混淆涂佃。
func main(int, *[]byte) int1
這種自左向右的聲明的一個(gè)好處在于励翼,當(dāng)類(lèi)型變得更復(fù)雜時(shí),它仍然表示得很明確辜荠。下面是一個(gè)函數(shù)變量的聲明(相當(dāng)于 C 里的函數(shù)指針):
f func(func(int,int) int, int) int
或者當(dāng)它返回一個(gè)函數(shù)時(shí):
f func(func(int,int) int, int) func(int, int) int
上面的聲明讀起來(lái)還是很明確汽抚,自左向右,當(dāng)前聲明的是哪一個(gè)變量名 —— 因?yàn)樽兞棵肋h(yuǎn)在首位伯病。
類(lèi)型語(yǔ)法和表達(dá)式語(yǔ)法帶來(lái)的差別使得在 Go 語(yǔ)言里調(diào)用閉包也變得更簡(jiǎn)單:
sum := func(a, b int) int { return a+b } (3, 4)
指針
指針有些例外造烁。注意在數(shù)組 (array)和切片 (slice) 中,Go 的類(lèi)型語(yǔ)法把方括號(hào)放在了類(lèi)型的左邊狱从,但是在表達(dá)式語(yǔ)法中卻又把方括號(hào)放到了右邊:
var a []int
x = a[1]
類(lèi)似的膨蛮,Go 的指針沿用了 C 的 *
記法,但是指針類(lèi)型的寫(xiě)法就不是像上面一樣反過(guò)來(lái)季研。指針要寫(xiě)成這樣:
var p *int
x = *p
而不能寫(xiě)成下面這樣:
var p *int
x = p*
因?yàn)楹缶Y的 *
可能會(huì)和乘法運(yùn)算混淆敞葛,也許我們可以改用 Pascal 的 ^
標(biāo)記,像這樣
var p ^int
x = p^
我們也許還真的應(yīng)該把 *
像上面這樣改成 ^
(然后用另一個(gè)運(yùn)算符代表 xor)与涡,因?yàn)樵陬?lèi)型和表達(dá)式中的 *
前綴確實(shí) 許多事都搞得有點(diǎn)復(fù)雜惹谐。例如,雖然我們可以像下面這樣寫(xiě)
[]int("hi")
但在轉(zhuǎn)換時(shí)驼卖,如果類(lèi)型是以 *
開(kāi)頭的氨肌,就得加上括號(hào):
(*int)(nil)
如果有一天我們?cè)敢夥艞売?*
作為指針語(yǔ)法的話(huà),那么上面的括號(hào)就可以省略了酌畜。
因此 Go 的指針語(yǔ)法和 C 是相似的怎囚,但這種相似也意味著我們無(wú)法徹底避免在文法中使用括號(hào)來(lái)避免類(lèi)型和表達(dá)式的歧義。
綜上所述,我們相信 Go 的類(lèi)型語(yǔ)法要比 C 的容易懂恳守。特別是當(dāng)類(lèi)型比較復(fù)雜時(shí)考婴。