Go语言声明语法
GO语言声明语法
介绍
Go新手想知道为什么Go的声明语法与C语言等的传统声明语法不同。在这篇文章中,我们将比较这两种方法,并解释为什么Go的声明是这样的。
C语法
首先,让我们谈谈C语言的语法。C使用了一个不寻常但聪明的声明语法。没有使用特殊的语法来描述类型,而是编写一个涉及被声明字段的表达式,并说明该字段的类型。即
int x;
声明x为int类型: 表达式x
将具有int类型。一般来说,要弄清楚新变量的类型,需要编写一个涉及该变量的表达式,然后将基本类型放在表达式左边,将表达式放在右边。
比如
int *p;
int a[3];
声明 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[]) { /* ... */ }
基本结构还是一样的。
这是一个巧妙的声明语法,适用于简单声明,但在一些状况下,这种声明语法会让人感到困惑。最著名的例子是声明一个函数指针。按照规则,声明如下:
int (*fp)(int a, int b);
在这里,fp 是一个指向函数的指针,如果您编写表达式 (*fp)(a, b)
,您将调用一个返回 int 的函数。但如果 fp 的一个参数本身就是一个函数怎么办?
int (*fp)(int (*ff)(int x, int y), int b)
这个表达式开始变得难以阅读。
当然,我们还可以在声明函数时省略参数名,可以这样声明
int main(int, char *[])
回想一下 argv 是这样声明的:
char *argv[]
从它的声明中去掉名字来构造它的类型。但是这个例子还不够明显
如果你不命名参数,看看 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 之间没有直接对应关系。(我们将在下一节中回到指针)您以单独的语法为代价获得声明的清晰。
现在考虑函数。让我们将main
的声明转换成 Go 中的样子,尽管 Go 中的 main 函数不带参数:
func main(argc int, argv []string) int
从表面上看,除了从char数组
到字符串数组
的变化之外,它与 C 并没有太大区别,但从左到右读起来很流畅:
函数 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的“顺时针/螺旋规则”。