什么是声明和声明,它们的types是如何被标准解释的?
例如float (*(*(&e)[10])())[5]
声明一个types为“指向10的指针的数组的指针”的variables返回指向数组的指针5 float
“?
受@DanNissenbaum的讨论启发
我在这篇文章中提到了C ++ 11标准
声明
我们所关心的types的声明在C ++语法中被称为简单声明 ,它是以下两种forms之一(§7/ 1):
decl-specifier-seq opt init-declarator-list opt ;
attribute-specifier-seq decl-specifier-seq opt init-declarator-list ;
attribute-specifier-seq是一系列属性( [[something]]
和/或alignment说明符( alignas(something)
)。 既然这些不影响声明的types,我们可以忽略它们和上述两种forms中的第二种。
声明说明符
所以我们声明的第一部分decl-specifier-seq由声明说明符组成。 这些包括一些我们可以忽略的东西,如存储说明符( static
, extern
等),函数说明符( inline
等), friend
说明符等等。 然而,我们感兴趣的一个声明说明符是types说明符 ,它可能包括简单types关键字( char
, int
, unsigned
等),用户定义types的名称,cv-qualifiers( const
或volatile
)等我们不关心。
例子 :所以一个decl-specifier-seq的简单例子就是const int
types的说明符序列。 另一个可能是unsigned int volatile
。
你可能会想“哦,所以像const volatile int int float const
这样的东西也是一个decl-specifier-seq ? 你说得对,它符合语法的规则,但语义规则不允许这样的decl-specifier-seq 。 事实上,除了某些组合(例如unsigned
int
或者const
除了它本身以外的任何东西)以及至less需要一个非cv-qualifier(§7.1.6/ 2-3)之外,只允许使用一个types说明符。
快速测验 (你可能需要参考标准)
-
const int const
是一个有效的声明说明符序列吗? 如果不是,是不是被句法或语义规则所禁止?无效的语义规则!
const
不能与自身结合。 -
是
unsigned const int
是否有效的声明说明符序列? 如果不是,是不是被句法或语义规则所禁止?有效!
const
无关紧要,从int
分离unsigned
。 -
auto const
是否是有效的声明说明符序列? 如果不是,是不是被句法或语义规则所禁止?有效!
auto
是一个声明说明符,但在C ++ 11中改变了类别。 之前它是一个存储说明符(如static
),但现在它是一个types说明符。 -
是
int * const
是否有效的声明说明符序列? 如果不是,是不是被句法或语义规则所禁止?语法规则无效! 虽然这可能是完整types的声明,但只有
int
是声明说明符序列。 声明指定符只提供基本types,而不是像指针,引用,数组等的复合修饰符。
声明符
简单声明的第二部分是init-declarator-list 。 它是由逗号分隔的一系列声明符,每个都带有一个可选的初始化符(§8)。 每个声明符都将一个variables或函数引入到程序中。 声明符最简单的forms就是您要介绍的名称 – 声明符ID 。 声明int x, y = 5;
有一个只是int
的声明说明符序列,后面跟着两个声明符x
和y
,其中第二个声明符有一个初始化符。 然而,我们将会忽略这篇文章的其余部分的初始化。
一个声明符可以有一个特别复杂的语法,因为这是声明的一部分,它允许你指定variables是否是指针,引用,数组,函数指针等等。注意,这些都是声明的一部分,而不是声明作为一个整体。 这正是int* x, y;
的原因int* x, y;
不会声明两个指针 – 星号*
是x
的声明符的一部分,而不是y
的声明符的一部分。 一个重要的规则就是每个声明符都必须有一个声明符id – 声明的名字。 关于有效申报人的规则的其余部分,一旦确定了申报types,我们会强制执行(稍后会介绍)。
例子 :一个简单的声明的例子是*const p
,它声明了一个const
指针… …。 它指向的types由声明中的声明说明符给出。 (*(*(&e)[10])())[5]
是一个更可怕的例子,它声明了一个返回指针的函数指针数组的引用,types的最后一部分实际上是由声明说明符给出的。
你不可能遇到这样可怕的宣言,但有时会出现类似的宣言。 能够阅读像问题中的那样的声明是一种有用的技能,并且是一种随练习而来的技能。 了解标准如何解释声明的types是有帮助的。
快速测验 (你可能需要参考标准)
-
int const unsigned* const array[50];
哪个部分int const unsigned* const array[50];
声明说明符和声明符是什么?声明说明符:
int const unsigned
声明者:* const array[50]
-
volatile char (*fp)(float const), &r = c;
声明说明符和声明符是什么?声明说明符:
volatile char
声明符#1:(*fp)(float const)
申报者#2:&r
声明types
现在我们知道一个声明由一个声明符指定符序列和一个声明符列表组成,我们可以开始思考如何确定一个声明的types。 例如,可能很明显, int* p;
将p
定义为“指向int的指针”,但是对于其他types,它并不那么明显。
包含多个声明符的声明,比如2个声明符,被认为是特定标识符的两个声明。 也就是说, int x, *y;
是标识符x
, int x
和声明标识符y
, int *y
的声明。
types在标准中表示为类似英语的句子(比如“int指针”)。 这种英文forms的声明types的解释分为两部分。 首先确定声明说明符的types。 其次,recursion程序适用于整个声明。
声明说明符types
声明说明符序列的types由标准的表10确定。 它列出了序列的types,因为它们以任何顺序包含相应的说明符。 因此,例如,包含signed
和char
的任何顺序的任何序列都具有“signed char”types。 出现在声明说明符序列中的任何cv限定符都会添加到types的前面。 所以char const signed
为“const signed char”。 这样可以确保不pipe按照什么顺序放置说明符,types都是一样的。
快速测验 (你可能需要参考标准)
-
什么是声明说明符序列的types
int long const unsigned
?“const unsigned long int”
-
什么是声明说明符序列
char volatile
?“挥发性字符”
-
声明说明符序列
auto const
的types是什么?这取决于!
auto
会从初始化器中推导出来。 如果推断为int
,则types将是“const int”。
声明types
现在我们有了声明说明符序列的types,我们可以计算出一个标识符的整个声明的types。 这是通过应用§8.3中定义的recursion过程来完成的。 为了解释这个过程,我将使用一个运行的例子。 我们将在float const (*(*(&e)[10])())[5]
出e
的types。
步骤1第一步是将声明拆分为TD
forms,其中T
是声明说明符序列, D
是声明符。 所以我们得到:
T = float const D = (*(*(&e)[10])())[5]
T
的types当然是“const float”,正如我们在上一节中所确定的那样。 然后,我们查找与当前D
forms相匹配的§8.3的小节。 你会发现这是§8.3.4数组,因为它声明它适用于表单TD
声明,其中D
的forms如下:
D1 [
常量expression式opt]
属性 – 说明符 – select
我们的D
确实是这样的forms,其中D1
是(*(*(&e)[10])())
。
现在想象一个声明T D1
(我们已经摆脱了[5]
)。
T D1 = const float (*(*(&e)[10])())
它的types是“<some stuff> T
”。 本节指出,我们的标识符e
的types是“<some stuff> 5 T
数组”,其中<some stuff>与虚构声明的types相同。 所以要计算出其余的types,我们需要计算出T D1
的types。
这是recursion! 我们recursion地计算出声明的内部部分的types,在每一步中都剥离了它的一部分。
步骤2和以前一样,我们把新的声明分成TD
forms:
T = const float D = (*(*(&e)[10])())
这符合段落§8.3/ 6,其中D
是( D1 )
的forms。 这种情况很简单, TD
的types简单就是T D1
的typesT D1
:
T D1 = const float *(*(&e)[10])()
第3步现在我们打电话给这个道明,再把它分开:
T = const float D = *(*(&e)[10])()
这符合§8.3.1指针,其中D
的forms是* D1
。 如果T D1
types是“<some stuff> T
”,那么TD
input“<some stuff> T
指针”。 所以现在我们需要T D1
的types:
T D1 = const float (*(&e)[10])()
第四步我们称之为TD
并分解:
T = const float D = (*(&e)[10])()
这符合§8.3.5的function,其中D
是D1 ()
的forms。 如果T D1
有types“<some stuff> T
”,那么TD
有types“<some stuff>()返回T
函数”。 所以现在我们需要T D1
的types:
T D1 = const float (*(&e)[10])
第5步我们可以应用与第2步相同的规则,其中声明符只是简单的括起来,最后是:
T D1 = const float *(&e)[10]
第6步当然,我们把它分开:
T = const float D = *(&e)[10]
我们再次将§8.3.1指针与forms* D1
D
相匹配。 如果T D1
types是“<some stuff> T
”,那么TD
input“<some stuff> T
指针”。 所以现在我们需要T D1
的types:
T D1 = const float (&e)[10]
第7步拆分:
T = const float D = (&e)[10]
我们再次将§8.3.4数组与D
的formsD1 [10]
匹配。 如果T D1
types是“<some stuff> T
”,那么TD
types是“<some stuff> 10 T
”。 那么T D1
的types是什么?
T D1 = const float (&e)
第8步再次应用括号步骤:
T D1 = const float &e
第9步拆分:
T = const float D = &e
现在我们匹配§8.3.2引用,其中D
的forms是& D1
。 如果T D1
types是“<some stuff> T
”,那么TD
input“<some stuff> T
”。 那么T D1
的types是什么?
T D1 = const float e
第10步当然这只是“T”! 这个级别没有<some stuff>。 这是由§8.3/ 5中的基本情况规则给出的。
我们完成了!
因此,现在,如果我们看看每一步所确定的types,用下面的每一层代替<some stuff>,我们可以e
float const (*(*(&e)[10])())[5]
来确定e
的typesfloat const (*(*(&e)[10])())[5]
:
<some stuff> array of 5 T │ └──────────┐ <some stuff> pointer to T │ └────────────────────────┐ <some stuff> function of () returning T | └──────────┐ <some stuff> pointer to T | └───────────┐ <some stuff> array of 10 T | └────────────┐ <some stuff> reference to T | | <some stuff> T
如果我们把这一切结合起来,我们得到的是:
reference to array of 10 pointer to function of () returning pointer to array of 5 const float
太好了! 这样就显示了编译器如何推导出声明的types。 请记住,如果有多个声明符,它将应用于每个标识符的声明。 尝试搞清楚这些:
快速测验 (你可能需要参考标准)
-
声明中
x
的types是什么bool **(*x)[123];
?“指向指向bool的指针的123指针数组的指针”
-
在声明中,
y
和z
是什么types?int const signed *(*y)(int), &z = i;
?y
是一个“指向函数的指针(int)返回指向const的int int”
z
是“对const signed int的引用”
如果有人有任何更正,请让我知道!
这是我parsingfloat const (*(*(&e)[10])())[5]
。 首先,确定说明符。 这里的说明符是float const
。 现在,我们来看看优先级。 [] = () > *
。 括号用于区分优先级。 优先考虑,让我们确定variablesID,即e
。 所以,e是一个对函数( () > *
)的10个指针的数组引用(因为[] > *
),它们没有参数和返回值,而是一个指向5个常量的数组的指针。 所以说明符是最后一个,其余的根据优先级进行parsing。