go的声明语法

简介

刚接触go的人一定会有个疑惑,为什么go的声明语法与传统的C簇类语言不一样。本文我们将通过比较两种语言语法,来解释为啥go语言要这样做。

C簇语法

首先,我们来谈一谈C的语法,其采用了一种不常用且巧妙的方式,可称为”解方程式”的语法。一个表达式涉及了描述实例和类型定义。例如:

int x;

定义x是一个int:因为x具有int类型。通常情况,C声明一个新变量,都需要思考它是什么类型,将类型写在左侧,变量名写在右侧。

int *p;
int a[3];

p是一个指向int的指针,这是因为*p的类型是int,同样道理, a[3]是一个int数组,因为a[3]的类型是int (数值3表示数组的大小)
那函数是怎么样呢?最初C函数的参数类型声明是写在()外面,例如:

int main(argc, argv)
    int argc;
    char *argv[];
{ /* ... */ }

可以看出,定义了 一个main函数,有两个参数 argc,argv,参数的定义写在了main(argc, argv)的下面,函数返回值是int。在来看看现代C的写法:

int main(int argc, char *argv[]) { /* ... */ }

两种写法基本结构差不多,这种解方程式的语法思想,适用于简单的类型定义,如果一复杂就容易引起混淆。最有名的的一个例子就是声明一个函数指针,遵循上面语法思想,可以写成:

int (*fp)(int a, int b);

上面表达式声明了fp是一个指向函数的指针,因为 (*fp)(a, b)的返回类型是int。让我们再来思考一个问题,如果函数的参数也是一个函数,那又该如何。

int (*fp)(int (*ff)(int x, int y), int b)

可以发现这个函数声明变得难读了。
当然我们也可以尝试去掉参数名,来定义函数,main函数的声明就简化如下:

int main(int, char *[]) { /* ... */ }

回想一下,argv是这样声明的

char *argv[]

所以去掉参数名后,变的非常不直观。这时我们再来看看,如果去掉参数名,fp的声明将会变成什么样:

int (*fp)(int (*)(int, int), int)

看其中一个参数:

int (*)(int, int)

不仅在哪里放置参数名不明显,而且也并不能清晰的描述这是一个函数指针。我们再来看一个:

int (*(*fp)(int (*)(int, int), int))(int, int)

有没有晕的感觉,像这样的例子可以举出很多,所以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函数(真正的go的main函数是没有参数):

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

与c有些不同,比如 char变成了string,从左到右阅读代码,更符合习惯。
函数main定义两个参数,一个是int,一个是string数组,并返回int。
移除参数名来看看:

func main(int, []string) int

这种从左到右的分格,在碰到复杂的情况下,也能一目了然,用函数参数为例:

f func(func(int,int) int, int) int

或者返回值为一个函数:

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*

因为后缀*会与乘法相混淆。 试想一下使用 ^代替,例如:

var p ^int
x = p^

也许我们应该这么样做(重新给异或定义一个字符),因为前缀*对于类型和表达式在很多方面都是复杂的。 例如,虽然可以写:

[]int("hi"

如果以*开头,则必须将该类型括起来:

(*int)(nil)

如果我们放弃*做为指针语法,这样这些括号就没有必要了。
Go的指针语法与C有千丝万缕的关系,这意味着我们在类型和表达式语法中无法消除括号,但总的来说,我们认为Go的类型语法比C更易懂,特别是在越复杂的情景下体现的越明显。

欢迎关注公众号
这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go言是一种静态类型、编译型的编程语言,它支持面向对象、过程式和函数式编程范式。以下是Go语言的一些基本语法: 1. 变量声明和赋值 Go语言中的变量声明和赋值使用关键字var和:=符号。例如: ``` var a int a = 10 b := "hello, world!" ``` 2. 控制流语句 Go语言中的控制流语句包括if、for、switch和select。例如: ``` if a > 0 { fmt.Println("a is positive") } else { fmt.Println("a is non-positive") } for i := 0; i < 10; i++ { fmt.Println(i) } switch day { case "Monday": fmt.Println("Today is Monday") case "Tuesday": fmt.Println("Today is Tuesday") default: fmt.Println("Today is another day") } select { case msg1 := <-ch1: fmt.Println("Received message from ch1:", msg1) case msg2 := <-ch2: fmt.Println("Received message from ch2:", msg2) default: fmt.Println("No message received") } ``` 3. 函数定义和调用 Go语言中的函数定义使用关键字func,函数调用使用函数名和参数列表。例如: ``` func add(a, b int) int { return a + b } sum := add(1, 2) fmt.Println(sum) ``` 4. 结构体和方法 Go语言中的结构体定义使用关键字type,方法定义使用关键字func和结构体类型。例如: ``` type Person struct { Name string Age int } func (p *Person) SayHello() { fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age) } person := Person{Name: "Alice", Age: 25} person.SayHello() ``` 这些是Go语言的一些基本语法,当然还有很多其他的语法特性和细节需要了解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值