C++ 理解复杂声明
本文转载于https://www.cnblogs.com/BinarySong/p/12913504.html
C和C++中的复杂声明往往难以理解,这里为大家总结出理解它们的系统化方法。
前置知识
1.声明
C++标准对声明的定义是:声明规定名字将如何被解释。一个声明只解释一个名字。这个名字是声明的“主语”,是描述的中心,其余的名字都并非重点。例如:
int fun(int a);
这个声明包含两个名字fun
和a
。然而只有fun
是“主语”。后面的部分会阐述如何确定这唯一的“主语”。
2.基础(Fundamental)类型和衍生(Derived)类型
一个声明语句有且只有一个基础类型。基础类型写在声明的最左边。
以下语句有的声明了变量,有的声明了函数。但它们的基础类型都是int。
int *a;
int b[10];
int c();
int (&d(char (*)[10]))[10];
当然,同样基础类型的声明可以连写,中间用逗号隔开。简便起见,本文中的“一个声明”,都是指不连写的情况。
int *a, b[10], c(), (&d(char (*)[10]))[10];//与前例等价
“主语”被符号修饰后,形成衍生类型。衍生类型也可以衍生出新的衍生类型,例如指针、引用、函数等。
3.符号和优先级
声明中可能出现各种符号,例如:
i[]
表示数组i()
表示函数*i
表示指针&i
表示引用
和表达式中的运算符一样,声明中的符号也有优先级。规则十分简单:- “主语”右侧符号的优先级高于左侧符号。
- 有分组符号()的先解释分组符号内的
- 注意,这里的 表示分组的() 和 表示函数的() 一定要区分开来。区分的方法很简单:
- 空括号()一定表示函数
- 包含类型的括号一定表示函数,如(int a)、(double,int)等。
分析声明语句
有了以上前置知识,我们现在来研究如何把一个声明翻译成“人话”。
1.找“主语”
对于“主语”没有省略的声明来说,方法很简单,把所有函数符号()里的形参名字去掉,剩下的名字就是“主语”。
例如:
int *(*f)(int *a(), int (&b)[10]);
用前述方法可以看出,(int *a(), int (&b)[10])
是表示函数的括号,去掉其中的名字a、b,能得到等价的声明:
int *(*f)(int *(), int (&)[10]);
显然,这里的f
是“主语”。
对于“主语”省略的声明,首先按前述方法两种括号区分开,“主语”就隐含在最内层分组符号里、前缀符号(*、&)和后缀符号([]、())之间。
如声明:
int (&(*[5])())[5];
省略的“主语”S位置如下:
int (&(*S[5])())[5];
2.按优先级(先右后左)顺序,从“主语”逐步翻译
确定主语的位置后,我们从主语开始,按照先右后左的顺序,一个一个符号解释。每个符号对应的译文如下表所示(按语境略改动,使之通顺):
符号 | 译文 |
---|---|
* | …指针,这个指针指向一个… |
& | …引用,这个引用引用一个… |
[N] | …数组,这个数组的元素是N个… |
(形参列表) | …形参为 形参列表 的函数,这个函数的返回值是… |
译文以“主语 是一个…”开头,以 基本类型 结束。每解释一个符号,就将该符号的对应的译文添加到当前译文末尾。
比如我们要翻译int (*(**A())[10])(double);
这个声明,流程如下:
(为了清楚,我们将已经解释的符号划掉,将当前符号和译文用红色标记)
原文:int (*(**A())[10])(double);
译文:A是一个…
解释:从主语开始翻译。
原文:int (*(**A ())[10])(double);
译文:A是一个形参列表为空的函数,这个函数的返回值是一个…
解释:按先右后左的顺序,我们先翻译()
,将其对应的译文添加到末尾。可以看出,即使只翻译第一步,我们就已经能知道A实际上是一个函数,可以对它有最基本的把握。之后的信息全部是关于A的返回值的。
原文:int (*(**A() )[10])(double);
译文:A是一个
形参列表为空的函数,这个函数的返回值是一个
指针,这个指针指向一个…
解释:右侧已经没有符号(右侧的“)”表示分组),我们翻译左侧的*
,将其对应的译文添加到末尾。
原文:int (*(**A() )[10])(double);
译文:A是一个
形参列表为空的函数,这个函数的返回值是一个
指针,这个指针指向一个
指针,这个指针指向一个…
解释:同上,我们翻译左侧的*
,将其对应的译文添加到末尾。
原文:int (*(**A())[10])(double);
译文:A是一个
形参列表为空的函数,这个函数的返回值是一个
指针,这个指针指向一个
指针,这个指针指向一个
数组,这个数组的元素是10个…
解释:分组符号()
内部翻译完成,划掉,优先翻译右侧的[]
。
原文:int (*(**A())[10] )(double);
译文:A是一个
形参列表为空的函数,这个函数的返回值是一个
指针,这个指针指向一个
指针,这个指针指向一个
数组,这个数组的元素是10个
指针,这些指针指向…
解释:右侧已经没有符号(右侧的)
表示分组),我们翻译左侧的*
。
原文:int (*(**A())[10])(double);
译文:A是一个
形参列表为空的函数,这个函数的返回值是一个
指针,这个指针指向一个
指针,这个指针指向一个
数组,这个数组的元素是10个
指针,这些指针指向
形参列表为(double)的函数,这个函数的返回值是一个…
解释:分组符号()
内部翻译完成,划掉。优先翻译右侧表示函数的(double)
。
原文:int (*(**A())[10])(double) ;
译文:A是一个
形参列表为空的函数,这个函数的返回值是一个
指针,这个指针指向一个
指针,这个指针指向一个
数组,这个数组的元素是10个
指针,这些指针指向
形参列表为(double)的函数,这个函数的返回值是一个
int
解释:最后以基础类型int
结尾。
翻译结果:A是一个形参列表为空的函数,这个函数的返回值是一个指针,这个指针又指向一个指针,后者指向一个数组,这个数组的元素是10个指针,这些指针各自指向一个形参列表为(double)、返回int的函数。
虽然很复杂,但本质上就是一个函数,而这点我们从第一步就可以得出,这也是这个方法的优势。
如果你愿意,还可以把它转换成常见的定语前置句:
A是一个形参列表为空且返回值为指向指向含有10个指向形参列表为(double)且返回int的函数的指针的数组的指针的指针