很多新手对于 Go 不同于 C 系列语言的声明语法感到非常惊讶,本文就为什么 Go 语言声明语法为何不同于其他语言进行说明。
C 语法
首先来说是 C 语言语法,C 采用不寻常但非常聪明的方式定义语法,反过来使用特殊语法申明明类型。一次写一个表达式来声明,并指明是什么类型的表达式。像这样:
int x;
申明 x
是一个int,表达式x
就有一个类型int
,一般情况下需要弄清楚如何确定一个新变量的类型,写一个表达式将涉及变量计算结果的基础数据类型。基础数据类型写在左边,表达式写在右边。像这样写表达式:
int *p;
int a[3];
此时,变量p
在表达式中通过*
来取指针地址,这样*p
就是int
类型。同理a[3]
是从 int 数组a
中取下标为3
的元素,故a[3]
为int
类型。
那么函数的定义呢?聪明的做法,C 语言函数是在括号中声明参数类型。
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
我们看到,main
就是一个函数,表达式是包含int
返回类型和main(argc, argv)
,现在写成:
int main(int argc, char *argv[]) { /* ... */ }
但基本机构是一样的。
对于简单类型,能很好的工作,这是很棒的语法思想。但是很快就变得混乱,典型的例子是申明一个函数指针,按照语法规范,是这样写的:
int (*fp)(int a, int b);
这里,fp 是一个函数指针,返回 int 数据的表达式 (*fp)(int a, int b)。如何 fp 入参就是它本身时,是怎样的?
int (*fp)(int (*ff)(int x, int y), int b)
这就变得非常难以阅读。当然,我们声明函数时,可以只保留入参类型,如:
int main(int, char *[])
记得,参数值是这样声明的:
char *argv[]
这样,你就将参数名放置在表达式的中间来声明类型,可是非常不明显,即使你是将参数名放在定义类型的符号*[]
中间。
在看看没有参数名的 fp 函数变成什么样了。
int (*fp)(int (*)(int, int), int)
不仅仅不明显的将名字放到中间
int (*)(int, int)
还完全不清楚是一个函数指针申明,如何返回类型也是一个函数指针呢?
int (*(*fp)(int (*)(int, int), int))(int, int)
很难看清楚是关于fp的申明。
你可以列举些更复杂的示例,但这里已经说明了 C 语言的声明语法有点儿困难。
多说一点的是,尽管 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
和语法表达式没有直接的对应关系,这个后面会详细说明。
现在思考下函数的声明,先复制下在 Go 中声明 main 函数。虽然 Go 的实际入口函数 main 没有入参。
func main(argc int, argv []string) int
从表面上看,除了将 char 类型参数换成 string 类型,没有太大的变化。但它更利于从左到右阅读。
函数 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
从左到右阅读很清晰,一眼就知道定义的变量名f
。数据类型和表达式语法间的区别,可以容易的在 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^
或许我们可以选择其他符号来做或与运算,会变得复杂,尽管可以写成:
[]int("hi")
作为转换,需要使用括号来包含”*“,
(*int)(nil)
即使我们放弃使用*
来当作指针语法,这里的括号也是必须的。
因此 Go 的指针语法是采用通用的 C 指针语法。可是我们没法通过去掉括号来消除表达式和类型间的歧义。
虽然如此,但整体上我们还是相信 Go 的类型语法是比 C 语言更容易理解,尤其是在复杂的情况下。
注意
Go 的声明是从左到右的,这里已经指出 C 语言螺旋式阅读,请看: David Anderson写的文章The “Clockwise/Spiral Rule”。
本文翻译自 Go 文章Go’s Declaration Syntax。翻译有不足之处欢迎指正。
通过阅读本文,你也许知道 Go 声明语法的缘由。一门新的语言总是能从之前的语言中吸取经验和改善体验。高效编程,化繁为简。