马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
Go 语言语法定义
注:本文档由 Diu 翻译,小甲鱼校对。
原文链接 -> 传送门
1. 介绍
初入 Go 语言的新人可能想知道:为什么 Go 语言的声明语法与传统的、建立在 C 语言家族中的声明语法不同。
在这篇帖子中,我们将比较这两种方案,并解释 Go 语言的声明语法为什么是目前这样。
2. C 语法
首先,我们来谈谈 C 语言语法。C 语言为声明语法采取了一种不寻常并且聪明的方案。通过一个包含将被声明的条目的表达式,并且说明该表达式具有的类型,而不是使用特定的语法来描述类型。
因此:
声明 x 是 int 类型:表达式 'x' 将具有 int 类型。
通常,为了弄清如何写出一个新变量的类型,首先写出一个包含该变量的表达式,该变量将被赋予一个基本类型,然后将该基本类型放在左边,表达式放在右边。
因此,声明:
说明了 p 是一个指向 int 的指针,因为 '*p' 具有类型 int,并且 a 是一个 int 类型数组,因为 a[3](忽略特定的索引值,索引值同时用来表示数组的大小)具有类型 int。
那么函数声明呢?起初,C 语言的函数声明将参数类型写在括号外面,像这样:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
同样,我们知道 main 是一个函数,因为表达式 main(argc, argv) 返回一个 int,现代的声明中,我们像这样写:
int main(int argc, char *argv[]) { /* ... */ }
但是基本结构是相同的。
这是一种聪明语法方案,并且对于简单类型工作良好,但是很快会令人困扰。
著名的例子就是 一个函数指针。按照上面的规则,你将得到:
在这里,fp 是一个指向函数的指针,因为如果你写下表达式 (*fp)(a, b),你将调用一个返回 int 的函数,那么如果 fp 的其中一个参数是一个函数,情况又是什么样子呢?
int (*fp)(int (*ff)(int x, int y), int b)
这理解起来已经开始有些困难了。
当然,当我们声明一个函数时,我们能够省略参数的名称,所以 main 函数可以声明为:
还记得 argv 被声明为:
你在它的声明中间舍弃了名称来构造类型。虽然你通过把参数名称放在 char *[] 中间来声明某个变量为 char *[] 类型也不是很明显。
如果你不命名参数,看看 fp 的声明发生了什么:
int (*fp)(int (*)(int, int), int)
将名称放在
里面也不明显,根本弄不清楚该函数指针的声明。而如果返回类型是一个函数指针呢?
int (*(*fp)(int (*)(int, int), int))(int, int)
这甚至很难看出这个一个关于 fp 的声明。
你可以构造更多更复杂的例子,但是这些应该说明了 C 语言声明语法的困难。
还需要指出一点,虽然,类型和声明语法是相同的,在中途使用类型来解析表达式是困难的。这也是为什么,C 语言进行数据类型转换时总是将类型用括号括起来。
例如:
3. Go 语法
C 语言家族之外的语言通常在声明上使用不同类型的语法。虽然这是一个单独的点,名称通常在前面,紧跟着一个冒号,因此我们的例子像这样:(一个虚拟的,作为解释的语言)
x: int
p: pointer to int
a: array[3] of int
这些声明很清楚,如果冗长,你只需从左向右读。
Go 语言从此获取灵感,但是为了简介的利益,舍弃了冒号和一些关键字:
在 [3]int 和如何在表达式中使用它们没有直接的对应关系(我们将在下一节讨论指针),你以一个单独语法的代价获取了更清楚的声明。
现在,我们来考虑函数。我们抄录 Go 语言中 main 函数的声明,虽然 Go 中真正的 main 函数不需要参数:func main(argc int, argv []string) int
从表面上看,这与 C 语言没有多大不同,除了将 char 数组换成了 strings,但是从左向右读会很清楚。
main 函数需要一个 int 参数和一个 strings 切片,并且返回一个 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)
4. 指针
指针是证明该规则的例外。注意,在数组和切片中,Go 的类型声明语法将方括号放在类型的左边,但是在表达式语法中将他们放在表达式的右边:
为了熟悉,Go 语言的指针像 C 一样使用 * 符号,但是我们不能将指针类型做相似的反转。因此指针使用应该像这样:
而不能
因为后缀 * 与乘法混为一谈。我们可以使用 Pascal ^,例如:
或许我们应该如此使用(并且为异或重新选择一个操作符),因为类型和表达式的前缀星号在许多方面会使事情复杂化。例如,虽然我们可以这样写:
作转换,那么如果类型以一个 * 开头,我们必须将类型用括号括起来:
如果我们愿意放弃*作为指针的语法,这些圆括号将是不必要的。
所以 Go 的指针语法与熟悉的C 形式联系在一起,但这些关系意味着我们不能完全在语法上使用括号来区分类型和表达式。
总体来说,我们相信 Go 的类型声明语法比 C 的更易理解,尤其是在复杂的声明上。
注意
Go 语言的声明从左向右读。有人指出,C 语言是以螺旋阅读声明,参见 David Anderson 写的 The "Clockwise/Spiral Rule"。
By Rob Pike
|