Go语言与C系列语言的声明语法不同,此文比较了两种声明方式,并且解释了Go语言的声明为何如此。
C采用了一种独特且聪明的声明语法。没有使用特殊的语法来描述类型,而是使用了一个涉及被声明项的表达式,然后说明此表达式的类型。
int x;
note:声明x未int,即表达式’x’具有类型int。
通常,为了给新变量编写类型类型,编写一个涉及此变量的表达式,此表达式的结果为基础类型,然后将基础类型放在左侧,表达式放在右侧。
int *p
int a[3]
note:p是int的指针,因为表达式’*p’的类型为int。
note:a是int的数组,因为表达式’a[3]'的类型为int。
C的函数声明最初将参数类型写在括号外,即:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
note:main是一个函数,因为表达式’main(argc, argv)'返回一个int。
现在,通常写为:
int main(int argc, char *argv[]) { /* ... */ }
不过基础结构相同。
这是一个适用于简单类型的聪明语法,但是很快就令人产生困惑。最有名的例子是声明一个函数指针。
int (*fp)(int a, int b);
note: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)
此时难以看出参数名称的位置,并且难以看出它是一个函数指针。
如果返回类型也是一个函数指针,此时便很难看出此声明与fp有关。
int (*(*fp)(int (*)(int, int), int))(int, int)
除此之外,由于类型与声明语法相同,在中间类型的表达式解析起来就十分困难。这也是C转化总是将类型用括号括起来的原因。
(int)M_PI
C系类之外的语言同时在声明时采用不同的语法。一种可行的方案是,名称通常在前,后跟冒号。表现如下:
x: int
p: pointer to int
a: array[3] of int
note:此语言为虚构的说明性语言。
Go根此得到灵感,并且出于简洁性需要,将关键字与分号去除:
x int
p *int
a [3]int
[3]int的形式与如何在表达式中使用并没有直接的关联。这样,通过单独语法便获得了简洁性。
据此声明机制,将main函数以Go写出便如下:
func main(agrc int, argv [] string) int
虽然表面上看,此形式与C差距不大,除了将char数组转为strings,安实它的可读性较高:
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
类型语法与表达式语法之间的区别使得在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
指针符号本可以使用Pascal的^,例如
var p ^int
x = p^
也许应该这么设计(并且选择另一个操作符用于异或),因为类型与表达式上的前缀星号令许多方面的事情都变得复杂。