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

本文对比了Go和C语言的声明语法,解释了Go为何采用从左至右的清晰语法,而非C的螺旋形读法。文章详细讨论了C语言声明的复杂性和Go语言在类型声明上的改进。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

译注

摸鱼翻译

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

作者: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 编写

1. 用户与身体信息管理模块 用户信息管理: 注册登录:支持手机号 / 邮箱注册,密码加密存储,提供第三方快捷登录(模拟) 个人资料:记录基本信息(姓名、年龄、性别、身高、体重、职业) 健康目标:用户设置目标(如 “减重 5kg”“增肌”“维持健康”)及期望周期 身体状态跟踪: 体重记录:定期录入体重数据,生成体重变化曲线(折线图) 身体指标:记录 BMI(自动计算)、体脂率(可选)、基础代谢率(根据身高体重估算) 健康状况:用户可填写特殊情况(如糖尿病、过敏食物、素食偏好),系统据此调整推荐 2. 膳食记录与食物数据库模块 食物数据库: 基础信息:包含常见食物(如米饭、鸡蛋、牛肉)的名称、类别(主食 / 肉类 / 蔬菜等)、每份重量 营养成分:记录每 100g 食物的热量(kcal)、蛋白质、脂肪、碳水化合物、维生素、矿物质含量 数据库维护:管理员可添加新食物、更新营养数据,支持按名称 / 类别检索 膳食记录功能: 快速记录:用户选择食物、输入食用量(克 / 份),系统自动计算摄入的营养成分 餐次分类:按早餐 / 午餐 / 晚餐 / 加餐分类记录,支持上传餐食照片(可选) 批量操作:提供常见套餐模板(如 “三明治 + 牛奶”),一键添加到记录 历史记录:按日期查看过往膳食记录,支持编辑 / 删除错误记录 3. 营养分析模块 每日营养摄入分析: 核心指标计算:统计当日摄入的总热量、蛋白质 / 脂肪 / 碳水化合物占比(按每日推荐量对比) 微量营养素分析:检查维生素(如维生素 C、钙、铁)的摄入是否达标 平衡评估:生成 “营养平衡度” 评分(0-100 分),指出摄入过剩或不足的营养素 趋势分析: 周 / 月营养趋势:用折线图展示近 7 天 / 30 天的热量、三大营养素摄入变化 对比分析:将实际摄入与推荐量对比(如 “蛋白质摄入仅达到推荐量的 70%”) 目标达成率:针对健
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值