【译】Go语言声明语法

引言

Go语言的初学者可能会好奇为何Go的类型声明语法和传统的C系语言不一样。本篇文章咱们将比较这两种不一样的类型声明方式,解释为何Go的声明会是这样子。html

C语法

首先,咱们谈谈C的语法。C采用了一种不一样寻常且聪明的类型声明语法。它没有使用特殊语法来描述变量类型,而是使用一个包含要声明变量的表达式,并陈述这个表达式的类型。所以golang

int x;
复制代码

声明x是一个int,由于表达式'X'是int类型。一般,为了弄清楚如何声明一个新变量,首先得写一个包含这个变量的表达式,这个表达式自己是一个基础类型。而后,把这个基础类型放在左边,表达式放在右边。
所以,下面的几个类型声明:express

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。如今的写法是:bash

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能够这样声明spa

int main(int, char *[]) 复制代码

回忆一下,参数argv的声明是这样的,指针

char *argv[]
复制代码

因此你从这个声明语句的中间去掉了变量的名字来构建它的类型。这样若是你须要声明一个char *[]的变量,你得把变量名放在char *[]中间,显然这样不够直观。
若是不声明参数的名字,来看看fp的声明会是什么样?code

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的形式和如何在表达式中使用变量a没有什么直接的联系。经过这种分开的语法,表达的更清楚。
再看看函数。将上面的main函数使用Go表达出来(Go中main是没有参数的):

func main(argc int, argv []string) int 复制代码

表面上看起来彷佛和C没有多大差异,仅仅把char改为了strings。但Go版本更加适合从左向右阅读:
main函数接受一个int和一个strings的切片,返回一个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 复制代码

从左到右读起来依然很是清晰,变量的名字也很是明显,由于它老是第一次出现。
类型转换和表达式之间的语法区别使得写和调用闭包都很是容易:

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*
复制代码

由于后缀*与乘法冲突。咱们也曾使用过^这个符合,例如:

var p ^int
x = p^
复制代码

或许咱们应该这么写(使用其余的符号表示异或),由于在类型声明和表达式中同时使用前缀*在一些状况下会很复杂。例如

[]int("hi")
复制代码

为了类型转换,若是类型以*开头就必须加括号:

(*int)(nil)
复制代码

若是不使用*做为指针语法,那就不用打括号了。
因此Go的指针尝试接近C的形式,这就意味着不能彻底去掉区分类型声明和表达式的括号。 总的来讲,咱们相信Go的类型声明语法比C更容易理解,特别是在复杂的状况下。

总结

Go的类型声明语法是从左到右的,而C的类型声明语法则是螺旋形的!具体参考David Anderson的这篇文章 The "Clockwise/Spiral Rule"

我的总结

Go语言类型声明语法中,永远将变量名放在最前面,这比C系语言更加清晰,更适合从左向右阅读。
可是类型转换彷佛没有多大区别,都须要括号,只是括号的对象不一样而已:

C:  (type_name) expression;
Go: type_name(expression)
复制代码

原文

Go's Declaration Syntax

相关文章
相关标签/搜索