以下翻译自Rob Pike 编写的Go's Declaration Syntax(Go的声明语法)
介绍
Go的新人想知道为什么声明语法与C系列中建立的传统不同。在这篇文章中,我们将比较两种方法,并解释为什么Go的声明看起来很像。
C语法
首先,说说C语法。C采用了一种不同寻常且聪明的方法来生命语法。不是用特殊的语法描述类型,而是编写涉及声明的项的表达式,并说明该表达式将具有的类型,例如
int x;
将x声明为int:表达式‘x’将具有int类型。通常,要弄清楚如何编写新的变量的类型,请编写一个涉及变量的表达式,该变量的计算结果为基本类型,然后将基本类型放在左侧,将表达式放在右侧。
例如下边的声明
int *p;
int a[3];
声明p是一个指向int的指针,因为'*p'的类型是int,而a[3]是一个int类型的数组,因为有类型int(忽略特定的索引值,它被认为是数组的大小)。
函数是什么样子呢?最初,C的函数声明再括号之外编写参数类型,如下所示:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
同样,我们看到main是一个函数,因为表达式main(argc, argv)返回一个int类型的值,在现在符号中我们写做
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 * []类型的东西并不明显。
如果你没有命名参数,看看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从这里开始提示,但为了简洁起见,它会删除冒号并删除一些关键字:
x int
p *int
a [3]int
[3] int表面上与如何在表达式中使用a之间没有直接的对应关系。(我们将在下一节回到指点。)您可以清楚地了解单独语法的成本。
现在考虑函数。让我们在Go中读取main的声明,尽管Go中的真正主要函数没有参数:
func main(argc int, argv []string) int
表面上看起来与C没什么不同,除了从char数组到字符串的变化,但它从左到右读得很好:
function main接受一个int和一个字符串切片并返回一个int。
删除参数名称,它也很清楚 - 它们总是第一个,所以没有混淆。
func main(int, []string) int
这种从左到右的风格的一个优点是它的工作效果随着类型变得更加复杂。
这是一个函数变量的声明(类似于C中的函数指针):
f func(func(int,int) int, int) int
或者如果f返回一个函数
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的“The "Clockwise/Spiral Rule(顺时针/螺旋规则)"
”
网友评论