以下翻譯自Rob Pike 編寫的Go's Declaration Syntax(Go的聲明語法)
介紹
Go的新人想知道為什么聲明語法與C系列中建立的傳統不同。在這篇文章中,我們將比較兩種方法,并解釋為什么Go的聲明看起來很像。
C語法
首先,說說C語法。C采用了一種不同尋常且聰明的方法來生命語法。不是用特殊的語法描述類型,而是編寫涉及聲明的項的表達式,并說明該表達式將具有的類型,例如
int x;
將x聲明為int:表達式‘x’將具有int類型。通常,要弄清楚如何編寫新的變量的類型,請編寫一個涉及變量的表達式,該變量的計算結果為基本類型,然后將基本類型放在左側,將表達式放在右側。
例如下邊的聲明
int *p;
int a[3];
聲明p是一個指向int的指針,因為'*p'的類型是int,而a[3]是一個int類型的數組,因為有類型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之間沒有直接的對應關系。(我們將在下一節回到指點。)您可以清楚地了解單獨語法的成本。
現在考慮函數。讓我們在Go中讀取main的聲明,盡管Go中的真正主要函數沒有參數:
func main(argc int, argv []string) int
表面上看起來與C沒什么不同,除了從char數組到字符串的變化,但它從左到右讀得很好:
function main接受一個int和一個字符串切片并返回一個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^
也許我們應該(并為xor選擇另一個運算符),因為類型和表達式上的前綴星號以多種方式使事情復雜化。例如,雖然可以寫
[]int("hi")
作為轉換,如果以*開頭,則必須將該類型括起來:
(*int)(nil)
如果我們愿意放棄*作為指針語法,那么這些括號將是不必要的。
因此Go的指針語法與熟悉的C形式相關聯,但這些關系意味著我們不能完全擺脫使用括號來消除語法中的類型和表達式的歧義。
總的來說,我們相信Go的類型語法比C語言更容易理解,特別是當事情變得復雜時。
最后
Go的聲明從左到右閱讀。有人指出C是螺旋式閱讀!參見David Anderson的“The "Clockwise/Spiral Rule(順時針/螺旋規則)"
”