很多新手对于 Go 不同于 C 系列语言的声明语法感到非常惊讶,本文就为什么 Go 语言声明语法为何不同于其他语言进行说明。

C 语法

首先来说是 C 语言语法,C 采用不寻常但非常聪明的方式定义语法,反过来使用特殊语法申明明类型。一次写一个表达式来声明,并指明是什么类型的表达式。像这样:

int x;

申明 x 是一个int,表达式x就有一个类型int,一般情况下需要弄清楚如何确定一个新变量的类型,写一个表达式将涉及变量计算结果的基础数据类型。基础数据类型写在左边,表达式写在右边。像这样写表达式:

int *p;
int a[3];

此时,变量p在表达式中通过*来取指针地址,这样*p就是int类型。同理a[3]是从 int 数组a中取下标为3的元素,故a[3]int类型。

那么函数的定义呢?聪明的做法,C 语言函数是在括号中声明参数类型。

int main(argc, argv)
    int argc;
    char *argv[];
{ /* ... */ }

我们看到,main就是一个函数,表达式是包含int返回类型和main(argc, argv),现在写成:

int main(int argc, char *argv[]) { /* ... */ }

但基本机构是一样的。

对于简单类型,能很好的工作,这是很棒的语法思想。但是很快就变得混乱,典型的例子是申明一个函数指针,按照语法规范,是这样写的:

int (*fp)(int a, int b);

这里,fp 是一个函数指针,返回 int 数据的表达式 (*fp)(int a, int b)。如何 fp 入参就是它本身时,是怎样的?

int (*fp)(int (*ff)(int x, int y), int b)

这就变得非常难以阅读。当然,我们声明函数时,可以只保留入参类型,如:

int main(int, char *[])

记得,参数值是这样声明的:

char *argv[]

这样,你就将参数名放置在表达式的中间来声明类型,可是非常不明显,即使你是将参数名放在定义类型的符号*[]中间。

在看看没有参数名的 fp 函数变成什么样了。

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和语法表达式没有直接的对应关系,这个后面会详细说明。

现在思考下函数的声明,先复制下在 Go 中声明 main 函数。虽然 Go 的实际入口函数 main 没有入参。

func main(argc int, argv []string) int

从表面上看,除了将 char 类型参数换成 string 类型,没有太大的变化。但它更利于从左到右阅读。

函数 main 包含一个 int 和 一个 string 切片入参,同时返回一个 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

从左到右阅读很清晰,一眼就知道定义的变量名f。数据类型和表达式语法间的区别,可以容易的在 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)(nil)

即使我们放弃使用*来当作指针语法,这里的括号也是必须的。

因此 Go 的指针语法是采用通用的 C 指针语法。可是我们没法通过去掉括号来消除表达式和类型间的歧义。

虽然如此,但整体上我们还是相信 Go 的类型语法是比 C 语言更容易理解,尤其是在复杂的情况下。

注意

Go 的声明是从左到右的,这里已经指出 C 语言螺旋式阅读,请看: David Anderson写的文章The “Clockwise/Spiral Rule”

本文翻译自 Go 文章Go’s Declaration Syntax。翻译有不足之处欢迎指正。

通过阅读本文,你也许知道 Go 声明语法的缘由。一门新的语言总是能从之前的语言中吸取经验和改善体验。高效编程,化繁为简。