translated from this article
Rob Pike
7 July 2010
简介
Go声明类型的语法和传统的C语言系不一样,下面比较一下两种方式。
C语言的语法
C语言的类型和声明语法都是将名字写成表达式,然后再说明表达式的类型。
类型 (定义) 语法
int x;
声明x为int=>表达式x
是int类型。新变量的类型是这么指定的,先写变量的表达式,其结果为基本类型,然后将基本类型放在左边,将表达式放在右边。
声明语法
int *p;
int a[3];
*p
前面是int类型,说明第一行的名称p
是一个指向int类型的指针,a[3]
前面是int类型,那第二行的名称a
是int值的数组。
C语言原来是把入参类型写道圆括号外边,就像这种:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
同样地,由于表达式main(argc,argv
返回一个int类型的结果,名称main就表示一个函数。现在时这么写的:
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 *[]
中间来声明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从这里汲取了模式,但是为了简洁起见,Go又删除了冒号和一些关键字。
x int
p *int
a [3]int
[3]int的文法和如何再表达式使用没有直接的关系(下一个小结再讲指针)。这里做的只是以一个特殊的语法换来清晰的结构。
那么回到函数,现在按Go语言规则转写main,忽略Go的main函数没参数这一点。
fun main(argc int, argv []string) int
表面上看,除了char数组变成了字符串slice这样更适合从左往右看:
函数main接收一个int和一个字符串slice
没有参数名依然清晰有序——名称总是再前面,不会混淆。
func main(int, []string) int
这种left-to-right (从左到又) 的样式有一个优点,在类型变复杂的情况下,它的表现依旧令人满意。这里声明一个函数变量 (类比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
错误用法
var p *int
x = p*
上面是因为后缀*
同时也是乘法运算符。乘法还能用Pascal ^:
var p ^int
x = p^
也许是个备选 (那样又要换xor运算符),因为星号前缀绑定了太多用法。比如虽然可以写
[]int("hi")
规避过去,如果碰到*
开头就必须将类型像这样放到括号里:
(*int)(nil)
假使舍得放弃*
指针语法,就不需要那些括号。
这不可能,Go的指针语法偏要抓住C语言的大腿,但是这一抓就完全打碎了在语法中用括号消除类型和表达式起义的节操。
总之,就算这样,请相信Go的类型语法比C的更容易理解,情况越复杂就越明显。
注意
Go语言的声明顺序是从左往右,不过有人指出C语言它是螺线顺序的!请参阅David Anderson的文章"Clockwise/Spiral (顺时针/螺旋规则)"。
(Fin)