【翻译】Go语言的声明语法

译注

摸鱼翻译

翻译过程中找到了好多略读时没有发现的知识。

作者:Rob Pike

译者:LittleFall

原地址:Go’s Declaration Syntax

引入

Go的新使用者想知道为什么声明语法和C风格很不相似,在这篇文章中我们会比较这两种语法并且解释为什么go的声明是这样的。

C语言的语法

首先谈一谈C风格,C使用了一个不寻常且聪明的声明方式。代替描述类型用特殊的语法,一个人写下一个表达式包含正在被声明的项目,然后陈述表达式将会拥有的类型,因此

int x;

声明了一个整形变量x:表达式x会拥有类型int。通常,为了找出如何写新变量的类型,写一个表达式包含评估为基本类型的变量,然后把基本类型放在左边,表达式放在右边。

因此,声明

int *p;
int a[3];

说明了p是一个指向int的指针因为*p是int类型,a是一个int数组因为a[3](忽略同时说明了数组大小的特殊索引值)是int类型。

函数呢?起初,C的函数声明会把参数类型写在括号外面,就像这样:

int main(argc, argc)
	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 之间没有直接联系(将在下一节回到指针),你从语法分离的代价中得到了清晰性。

现在考虑函数,让我们写下main函数如果在go中出现时的声明(实际上,go中确实有main函数,但是没有参数):

func main(argc int, argv []string) int

表面上除了把char数组换成string以外和C没有多少不同,但它从左到右读起来会很棒:

函数main接受一个int和一个string的切片,返回一个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^

而且或许我们应当拥有(并选择另一个操作符用来表示异或),因为前缀星号作用在类型和表达式上会在很多方面让事情复杂化。实例中,即使我们可以写出

[]int("hi")

作为一个类型转换,当类型以*开头时必须用括号包起来。

(*int)(nil)

如果我们愿意放弃*作为指针语法,这些括号就会变得不再必要。

所以go的指针语法和类似于C的形式搅在了一起,但是这些混合意味着我们不能彻底打破用括号消除语法在类型和表达式上的歧义。

尽管,总的来说,我们相信go的类型语法是比C的更容易理解的,特别是当事情变得复杂时。

注意

Go语言的声明是从左到右读的,而C语言声明的阅读方法已经被指出是螺旋形的!见David Anderson的The “Clockwise/Spiral Rule”.

Rob Pike 编写

发布了375 篇原创文章 · 获赞 305 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/103846249