介绍
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)
这开始变得难以阅读。
当然,我们在声明函数时可以省略参数的名称,因此可以声明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中真正的main函数不带参数,但让我们转录在main中的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)
Pointers
指针是证明规则的例外。 请注意,例如,在数组和切片中,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的语法更易于理解,尤其是当事情变得复杂时。