Go lang变量声明
学习过Go语言
的同学可能会对变量类型的声明感到奇怪,觉得为什么跟C
、Java
这类语言不一样呢,为什么要把类型放在变量名的后面呢,最近在golang
官方博客上看到一篇文章,解释了为什么要这么设计。
文章原文:https://blog.golang.org/gos-declaration-syntax
我通过机器翻译和我自己的理解,把这篇文件翻译了一下,与大家分享一下。
Go的变量声明语法
引言
新来者想知道为什么声明语法不同于 C语言
的传统。 在这篇文章中,我们将比较这两种方法,并解释为什么 Go
的声明看起来和他们一样。
C 语法
首先,我们来谈谈C语言
语法。 对于声明语法,C语言
采取了一种不同寻常且聪明的方法。 不是描述具有特殊语法的类型,而是写一个涉及被声明项的表达式,并说明该表达式将具有什么类型。
声明x
是 int
类型,表达式'x'
将具有整型类型。 一般来说,为了找出如何写新变量的类型,写一个包含这个变量的表达式,这个变量对一个基本类型进行计算,然后将左边的基本类型和右边的表达式放在左边。
int x;
因此,如下这些声明指出p
是 int
的一个指针,因为"p"
具有 int
类型,而 a
是 int
的一个数组,因为一个[3]
(忽略了特定的索引值,它被认为是数组的大小)有int
。
int *p;
int a[3];
那函数呢? 最初,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)
,你会调用一个返回整型的函数。 如果 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
之间没有直接的对应关系。 (我们将在下一节回顾指针。) 你以一个单独的语法为代价获得清晰度。
现在考虑函数。 让我们转录一下主要声明,就像它在 Go
中读到的那样,尽管 Go
中真正的主要函数没有参数:
func main(argc int, argv []string) int
从表面上看,这与 C语言
没有太大的不同,除了从字符串变成字符串,但从左到右读起来还不错: 函数主要取一个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^
也许我们应该(并且为 xor
选择另一个运算符) ,因为在这两种类型和表达式上的前缀星号在许多方面使事情复杂化。 例如,尽管人们可以写作
[]int("hi")
作为转换,如果类型以 *
开头,则必须括号
(*int)(nil)
如果我们愿意放弃 *
作为指针语法,那些括号将是不必要的。
因此,Go
的指针语法与常见的 C语言
形式相关联,但是这些关系意味着我们不能完全打破使用括号来消除语法中的类型和表达式。
总的来说,我们相信 Go
的类型语法比 C语言
语法更容易理解,尤其是当事情变得复杂的时候。
注意事项
Go
的声明从左到右读取。 有人指出,C语言
是在螺旋上读的! 参见David Anderson的《The "Clockwise/Spiral Rule"》。
Comments