【譯】Go語言聲明語法

引言

Go語言的初學者可能會好奇爲何Go的類型聲明語法和傳統的C系語言不一樣。本篇文章咱們將比較這兩種不一樣的類型聲明方式,解釋爲何Go的聲明會是這樣子。html

C語法

首先,咱們談談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
複製代碼

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改爲了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)
複製代碼

原文

Go's Declaration Syntax

相關文章
相關標籤/搜索