>[info] 原文地址: https://blog.golang.org/gos-declaration-syntax
**介紹**
Go的入門者會疑惑為什么聲明語法和傳統C家族的語法不一樣。本文我們會比較這2種方式,并且解釋為什么Go會這樣聲明。
**C語法**
首先,我們說說C語法。C在聲明語法上用了一個不同尋常很聰明的做法。并沒有使用特殊語法來描述類型,而是編寫一個包含已被聲明項的表達式,并說明這個表達式將具有什么類型。因此
~~~
int x;
~~~
聲明x為int類型:表達式'x'將會具有int類型。一般來說,為了弄明白如何書寫一個新變量的類型,就要編寫一個包含該變量的表達式,假如其求值為基本類型,那就把基本類型放在左邊,表達式放在右邊。
如下,聲明如:
~~~
int *p;
int a[3];
~~~
表明p是一個指向int類型的指針,因為'\*p'具有類型int;并且a是一個int數組,因為a[3](忽略掉這個索引值,只是為了說明數組長度為3)具有int類型。
那么方法該如何聲明類型呢?原本,C的方法聲明在括號外面書寫參數類型,就像這樣:
~~~
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
~~~
這里,我們再一次看到main是一個方法,因為表達式main(argc, argv)返回了一個int。而在現代記法中,我們這樣寫:
~~~
int main(int argc, char *argv[]) { /* ... */ }
~~~
但是基本結構是一樣的。
這是一種聰明的句法處理方式,對于簡單類型它表現的很好,但是很快便會讓人困惑。著名的例子是聲明一個方法指針。根據以上原則你會得到如下表達式:
~~~
int (*fp)(int a, int b);
~~~
這里,fp是一個指向方法的指針,因為如果你寫了表達式(\*fp)(a, b),你將調用一個方法返回int。那如果fp的其中一個參數本身就是一個方法呢?
~~~
int (*fp)(int (*ff)(int x, int y), int b)
~~~
這里就開始難以閱讀了。
當然了,我們在聲明一個方法時,我們可以不寫參數的名字,因此main可以被聲明為:
~~~
int main(int, char *[])
~~~
回憶起argv被這樣聲明:
~~~
char *argv[]
~~~
所以你從構建類型聲明的正中間去除掉了這個名字。然而這種做法并不很明白,即你通過把名字放在中間,來聲明一個char \*[]類型的東西。
再來看看如果你不命名參數,fp的聲明會變成什么樣子:
~~~
int (*fp)(int (*)(int, int), int)
~~~
這不僅是不清楚應該把名字放在哪里
~~~
int (*)(int, int)
~~~
甚至并不清楚這是一個方法指針的聲明。并且如果返回類型是一個方法指針呢,事情將會更加糟糕:
~~~
int (*(*fp)(int (*)(int, int), int))(int, int)
~~~
這個甚至很難看出是對fp的聲明表達式。
你還可以構建出更加復雜的例子,但上面這些應該能夠說明C的聲明語法會引入的一些困難。
此外,還有一點需要重點說明。因為類型和聲明語法是一樣的,所以在編譯的中間過程,分析帶有類型的表達式就會變得困難。這就是為什么,比如,C的強制類型轉換總是需要將類型用括號括起來,例如:
~~~
(int)M_PI
~~~
**Go語法**
C家族以外的語言,在聲明上通常使用另一種類型語法。也算是一個不同點:名稱通常出現在最前面,通常后面跟上一個冒號。因此上面的例子就變成了如下這樣(使用的是一個虛構用來說明的語言)
~~~
x: int
p: pointer to int
a: array[3] of int
~~~
這個聲明很清晰明了,如果遇到冗長的聲明 -- 你只需要從左向右閱讀它們即可領會。Go就從這里吸取了啟發,并且出于簡潔,它丟棄了冒號,并移除了部分關鍵字:
~~~
x int
p *int
a [3]int
~~~
在[3]int的書寫方式,和如何在表達式中使用a這兩者之間并沒有直接對應關系。下一節將講述指針。付出分隔語法的代價,你可以清晰的閱讀代碼。
再看看方法。讓我們將main的聲明改寫為Go的方式,雖然Go中真實的main函數不接受參數:
~~~
func main(argc int, argv []string) int
~~~
表面上看,和C并沒有太多區別,除了char數組變成了string類型,但是它更適合從左往右讀:
main方法接收一個int參數,和一個strings片段參數,然后返回一個int。
丟掉參數名字,它同樣清晰 -- 名字總是出現在最前面,所以不會產生困惑。
~~~
func main(int, []string) int
~~~
這種從左到右風格的優點是,當類型變得越來越復雜時,它表現的也如此之好。下面是一個方法變量的聲明(類似于C中的方法指針):
~~~
f func(func(int,int) int, int) int
~~~
或者如果f返回一個方法
~~~
f func(func(int,int) int, int) func(int, int) int
~~~
它依然讀起來很清晰,從左向右,因為名字總是出現在最前面,所以整個句子的意思總是顯而易見。
類型和表達式語法之間的區別,使Go編寫和調用閉包很簡單:
~~~
sum := func(a, b int) int { return a+b } (3, 4)
~~~
**指針**
指針是驗證這個規則的例外。留意下數組和片段中,比如,Go的類型語法將括號放在類型的左邊,但是表達式語法將其放在表達式的右邊:
~~~
var a []int
x = a[1]
~~~
為了容易熟悉,Go的指針使用了C的\*記法,但是我們不能對指針類型做類似的反轉。因此指針工作像這樣:
~~~
var p *int
x = *p
~~~
而不能寫成:
~~~
var p *int
x = p*
~~~
因為詞尾\*會和乘法運算混淆。我們本可以使用Pascal的^來表示指針,比如:
~~~
var p ^int
x = p^
~~~
而且很可能我們應該這樣做(同時要給異或運算選一個其它的操作符),因為類型和表達式中的前綴星號都會使事情復雜化。例如,盡管可以這樣進行類型轉換:
~~~
[]int("hi")//將其轉換為int數組
~~~
但是如果類型前面以\*開始,就必須將其括起來:
~~~
(*int)(nil)
~~~
假如我們愿意放棄\*作為指針語法,那么這些括號就不是必須的了。
所以Go的指針語法,依賴于熟悉的C形式,但是這種聯系,也意味著我們不能完全打破陳規:使用加括號,來消除類型和表達式在語法上的歧義。
總之,無論怎樣,我們相信Go的類型語法比C的更容易懂,特別是在復雜的情況下。
**備注**
Go的聲明閱讀順序是從左到右。而已經有人指出,C的聲明讀法則是螺旋式的。可以參見David Anderson的[The "Clockwise/Spiral Rule"](http://c-faq.com/decl/spiral.anderson.html)
本文由Rob Pike著