Go語言的初學者可能會好奇爲何Go的類型聲明語法和傳統的C系語言不一樣。本篇文章咱們將比較這兩種不一樣的類型聲明方式,解釋爲何Go的聲明會是這樣子。html
首先,咱們談談C的語法。C採用了一種不一樣尋常且聰明的類型聲明語法。它沒有使用特殊語法來描述變量類型,而是使用一個包含要聲明變量的表達式,並陳述這個表達式的類型。所以golang
int x;
複製代碼
聲明x
是一個int
,由於表達式'X'是int類型。一般,爲了弄清楚如何聲明一個新變量,首先得寫一個包含這個變量的表達式,這個表達式自己是一個基礎類型。而後,把這個基礎類型放在左邊,表達式放在右邊。
所以,下面的幾個類型聲明:express
int *p;
int a[3];
複製代碼
聲明p是一個int指針,由於表達式」*p「是int類型。而a是一個int數組,由於a[3](忽略其中的數字,這個數字是用來指定數組的大小的)表達式是int類型。
再來看看函數。最開始,C的函數聲明將參數的類型寫在小括號外,像這樣:數組
int main(argc, argv) int argc;
char *argv[];
{ /* ... */ }
複製代碼
一樣的,咱們看到main是一個函數,由於表達式main(argc, argv)返回一個int。如今的寫法是:bash
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能夠這樣聲明spa
int main(int, char *[]) 複製代碼
回憶一下,參數argv的聲明是這樣的,指針
char *argv[]
複製代碼
因此你從這個聲明語句的中間去掉了變量的名字來構建它的類型。這樣若是你須要聲明一個char *[]的變量,你得把變量名放在char *[]中間,顯然這樣不夠直觀。
若是不聲明參數的名字,來看看fp的聲明會是什麼樣?code
int (*fp)(int (*)(int, int), int)
複製代碼
這樣不只不知道變量名該放在哪
int (*)(int, int)
複製代碼
並且很難看出來這是在聲明一個函數指針。進一步,若是該函數的返回類型也是一個函數指針呢?
int (*(*fp)(int (*)(int, int), int))(int, int)
複製代碼
這裏甚至很難看出這其中變量名爲fp。
這樣精心構建的例子還有不少,這就說明C的變量聲明語法能有多複雜。
還有一點學要提下,就是類型轉換和類型聲明語法是同樣的,這使得C語言很難解析表達式中的類型轉換,這也就是爲何C系語言老是在類型轉換的時候打括號,例如
(int)M_PI
複製代碼
非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改爲了strings。但Go版本更加適合從左向右閱讀:
main函數接受一個int和一個strings的切片,返回一個int。
去掉參數名也同樣簡潔,由於變量名永遠在聲明的最前面,因此這不會形成迷惑。
func main(int, []string) int 複製代碼
這種從左到右的聲明語法有一個很是實用的地方:當類型很是複雜的時候,它也能很清晰。下面是一個函數變量的聲明(類比C中的函數指針):
f func (func(int, int) int, int) int 複製代碼
或者返回值也是一個函數
f func(func(int, int) int, int) func(int, int) int 複製代碼
從左到右讀起來依然很是清晰,變量的名字也很是明顯,由於它老是第一次出現。
類型轉換和表達式之間的語法區別使得寫和調用閉包都很是容易:
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*
複製代碼
由於後綴*與乘法衝突。咱們也曾使用過^這個符合,例如:
var p ^int
x = p^
複製代碼
或許咱們應該這麼寫(使用其餘的符號表示異或),由於在類型聲明和表達式中同時使用前綴*在一些狀況下會很複雜。例如
[]int("hi")
複製代碼
爲了類型轉換,若是類型以*開頭就必須加括號:
(*int)(nil)
複製代碼
若是不使用*做爲指針語法,那就不用打括號了。
因此Go的指針嘗試接近C的形式,這就意味着不能徹底去掉區分類型聲明和表達式的括號。 總的來講,咱們相信Go的類型聲明語法比C更容易理解,特別是在複雜的狀況下。
Go的類型聲明語法是從左到右的,而C的類型聲明語法則是螺旋形的!具體參考David Anderson的這篇文章 The "Clockwise/Spiral Rule"
Go語言類型聲明語法中,永遠將變量名放在最前面,這比C系語言更加清晰,更適合從左向右閱讀。
可是類型轉換彷佛沒有多大區別,都須要括號,只是括號的對象不一樣而已:
C: (type_name) expression;
Go: type_name(expression)
複製代碼