什么是声明和声明符以及它们的类型如何按标准解释?

Jos*_*eld 42 c++ standards types declaration c++11

标准究竟如何定义,例如,float (*(*(&e)[10])())[5]声明一个类型的变量"引用指向函数的10指针的数组()返回指向5 float" 数组的指针?

受到与@DanNissenbaum讨论的启发

Jos*_*eld 77

我在这篇文章中提到了C++ 11标准

声明

声明我们关心的被称为的类型的简单声明在C++中,这是通过下面两种形式(§7/ 1)之一的语法S:

decl-specifier-seq opt init-declarator-list opt ;
attribute-specifier-seq decl-specifier-seq opt init-declarator-list ;

属性说明符-SEQ是属性(的序列[[something]])和/或对准说明符(alignas(something)).由于这些不会影响声明的类型,我们可以忽略它们以及上述两种形式中的第二种.

声明说明符

因此,我们的声明的第一部分,即decl-specifier-seq,由声明说明符组成.这些包括一些事情,我们可以忽略,如存储符(static,extern,等),功能说明符(inline等)的friend说明符,等等.然而,我们感兴趣的一个声明说明是类型说明符,其中可能包括简单类型的关键字(char,int,unsigned等),用户自定义类型的名称,cv修饰符(constvolatile),以及其他我们不关心.

示例:这是一个简单的decl-specifier-seq示例,它只是一个类型说明符序列const int.另一个可能是unsigned int volatile.

你可能会想"哦,所以类似的东西const volatile int int float const也是一个decl-specifier-seq?" 你是对的,它符合语法规则,但语义规则不允许这样的decl-specifier-seq.只有一种类型说明符是允许的,事实上,除了某些组合(如unsignedintconst与除本身的任何东西)和至少一个需要非CV-限定符(§7.1.6/ 2-3).

快速测验(您可能需要参考标准)

  1. 是否const int const是有效的声明说明符序列?如果没有,它是否被句法或语义规则所禁止?

    语义规则无效!const不能与自己结合.

  2. 是否unsigned const int是有效的声明说明符序列?如果没有,它是否被句法或语义规则所禁止?

    有效!不要紧的const分隔unsigned距离int.

  3. 是否auto const是有效的声明说明符序列?如果没有,它是否被句法或语义规则所禁止?

    有效!auto是一个声明说明符,但在C++ 11中更改了类别.之前它是一个存储说明符(如static),但现在它是一个类型说明符.

  4. 是否int * const是有效的声明说明符序列?如果没有,它是否被句法或语义规则所禁止?

    语法规则无效!虽然这可能是声明的完整类型,但只有int声明说明符序列.声明说明符仅提供基本类型,而不是指针,引用,数组等复合修饰符.

声明符

简单声明的第二部分是init-declarator-list.它是一个由逗号分隔的声明符序列,每个声明符都有一个可选的初始值设定项(§8).每个声明者在程序中引入单个变量或函数.最简单的声明形式就是你要引入的名称 - declarator-id.声明int x, y = 5;具有声明说明序列,这只是int,后面是两个说明符,x以及y,其中所述第二具有一个初始化.但是,我们将忽略本文其余部分的初始化程序.

声明符可以具有特别复杂的语法,因为这是声明的一部分,允许您指定变量是指针,引用,数组,函数指针等.请注意,这些都是声明符的一部分而不是声明作为一个整体.这正是为什么int* x, y;不声明两个指针的原因- 星号*是声明者的x一部分而不是声明者的一部分y.一个重要的规则是每个声明者必须只有一个声明者id - 它声明的名称.关于有效声明符的其余规则在确定声明类型后强制执行(我们稍后会介绍).

示例:一个声明*const p符的简单示例,它声明了一个const指向... 的指针.它指向的类型由声明中的声明说明符给出.一个更可怕的例子是问题中给出的一个例子(*(*(&e)[10])())[5],它声明了对函数指针数组的引用,这些函数指针返回指针...再次,类型的最后部分实际上是由声明说明符给出的.

你不太可能遇到过如此可怕的宣告者,但有时会出现类似的声明.能够阅读问题中的声明并且是练习附带的技能是一项有用的技能.理解标准如何解释声明的类型是有帮助的.

快速测验(您可能需要参考标准)

  1. int const unsigned* const array[50];声明说明符和声明符的哪些部分?

    声明说明符:int const unsigned
    声明者:* const array[50]

  2. volatile char (*fp)(float const), &r = c;声明说明符和声明符的哪些部分?

    声明说明符:volatile char
    声明者#1:(*fp)(float const)
    声明者#2:&r

声明类型

现在我们知道声明由声明符说明符序列和声明符列表组成,我们可以开始考虑如何确定声明的类型.例如,可能很明显,int* p;定义p为"指向int的指针",但对于其他类型,它并不那么明显.

具有多个声明符的声明(例如2个声明符)被认为是特定标识符的两个声明.也就是说,int x, *y;是标识符的声明x,int x和标识符的声明y,int *y.

类型在标准中表示为类似英语的句子(例如"指向int的指针").这种英语形式的声明类型的解释分为两部分.首先,确定声明说明符的类型.其次,递归过程作为一个整体应用于声明.

声明说明符类型

声明说明符序列的类型由标准的表10确定.它列出了序列的类型,因为它们包含任何顺序的相应说明符.因此,例如,含有任何顺序signedchar以任何顺序,其中char signed,具有输入"符号字符".声明说明符序列中出现的任何cv限定符都将添加到该类型的前面.所以char const signed有类型"const signed char".这可以确保无论您放置说明符的顺序如何,类型都是相同的.

快速测验(您可能需要参考标准)

  1. 声明说明符序列的类型是什么int long const unsigned

    "const unsigned long int"

  2. 声明说明符序列的类型是什么char volatile

    "挥发性炭"

  3. 声明说明符序列的类型是什么auto const

    这取决于!auto将从初始化程序中推断出来.int例如,如果它被推断为类型将是"const int".

声明类型

现在我们有了声明说明符序列的类型,我们可以计算出标识符的整个声明的类型.这是通过应用§8.3中定义的递归过程来完成的.为了解释这个过程,我将使用一个运行的例子.我们将制定出的类型efloat const (*(*(&e)[10])())[5].

步骤1第一步是将声明拆分为声明说明符序列的形式T D,T并且是声明符D.所以我们得到:

T = float const
D = (*(*(&e)[10])())[5]
Run Code Online (Sandbox Code Playgroud)

T当然,类型是"const float",正如我们在上一节中所确定的那样.然后,我们寻找符合当前形式的§8.3的子部分D.你会发现,这是§8.3.4阵列,因为它说,它适用于形式的声明T D,其中D有以下形式:

D1 [ 常量表达式opt ] attribute-specifier-seq opt

我们D确实是一个形式,其中的D1(*(*(&e)[10])()).

现在想象一个宣言T D1(我们已经摆脱了[5]).

T D1 = const float (*(*(&e)[10])())
Run Code Online (Sandbox Code Playgroud)

它的类型是"<some stuff> T".本节说明我们的标识符的类型e是"<some stuff> 5 of array T",其中<some stuff>与虚构声明的类型相同.因此,为了计算出类型的其余部分,我们需要计算出类型T D1.

这是递归!我们以递归的方式计算出声明内部的类型,在每一步都去掉它.

步骤2因此,和以前一样,我们将新声明拆分为以下形式T D:

T = const float
D = (*(*(&e)[10])())
Run Code Online (Sandbox Code Playgroud)

这个匹配段§8.3/ 6,其中D的形式为( D1 ).这种情况很简单,类型T D只是类型T D1:

T D1 = const float *(*(&e)[10])()
Run Code Online (Sandbox Code Playgroud)

第3步T D现在让我们调用它并再次拆分:

T = const float
D = *(*(&e)[10])()
Run Code Online (Sandbox Code Playgroud)

这符合§8.3.1指针D的形式* D1.如果T D1有类型"<some stuff> T",则T D输入"<some stuff>指针T".所以现在我们需要以下类型T D1:

T D1 = const float (*(&e)[10])()
Run Code Online (Sandbox Code Playgroud)

第4步我们将其调用T D并拆分:

T = const float
D = (*(&e)[10])()
Run Code Online (Sandbox Code Playgroud)

这符合§8.3.5函数D的形式D1 ().如果T D1有类型"<some stuff> T",则T D输入"<some stuff>函数()返回T".所以现在我们需要以下类型T D1:

T D1 = const float (*(&e)[10])
Run Code Online (Sandbox Code Playgroud)

步骤5我们可以应用我们为第2步所做的相同规则,其中声明符只是用括号括起来以结束:

T D1 = const float *(&e)[10]
Run Code Online (Sandbox Code Playgroud)

第6步当然,我们把它分开了:

T = const float
D = *(&e)[10]
Run Code Online (Sandbox Code Playgroud)

我们再次使用D表格匹配§8.3.1指针* D1.如果T D1有类型"<some stuff> T",则T D输入"<some stuff>指针T".所以现在我们需要以下类型T D1:

T D1 = const float (&e)[10]
Run Code Online (Sandbox Code Playgroud)

第7步拆分:

T = const float
D = (&e)[10]
Run Code Online (Sandbox Code Playgroud)

我们再次使用D表格匹配§8.3.4数组D1 [10].如果T D1有类型"<some stuff> T",则T D输入"<some stuff> 10 of array T".什么是T D1类型?

T D1 = const float (&e)
Run Code Online (Sandbox Code Playgroud)

步骤8再次应用括号步骤:

T D1 = const float &e
Run Code Online (Sandbox Code Playgroud)

第9步拆分:

T = const float
D = &e
Run Code Online (Sandbox Code Playgroud)

现在我们匹配§8.3.2引用D的形式& D1.如果T D1有类型"<some stuff> T",则T D输入"<some stuff> reference to T".那是什么类型的T D1

T D1 = const float e
Run Code Online (Sandbox Code Playgroud)

第10步当然,它只是"T"!这个级别没有<some stuff>.这是由§8.3/ 5中的基本情况规则给出的.

我们完成了!

所以现在如果我们看一下我们在每一步确定的类型,从下面的每个级别替换<some stuff>,我们可以确定ein 的类型float 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
Run Code Online (Sandbox Code Playgroud)

如果我们将这些结合在一起,我们得到的是:

reference to array of 10 pointer to function of () returning pointer to array of 5 const float
Run Code Online (Sandbox Code Playgroud)

太好了!这样就说明了编译器如何推断出声明的类型.请记住,如果有多个声明符,则将其应用于标识符的每个声明.试着搞清楚这些:

快速测验(您可能需要参考标准)

  1. x声明中的类型是什么bool **(*x)[123];

    "指向指向bool的指针的123指针的指针"

  2. 宣言的类型y和内容zint const signed *(*y)(int), &z = i;什么?

    y是一个"指向(int)函数的指针返回指向const signed int的指针"
    z是"对const signed int的引用"

如果有人有任何更正,请告诉我!

  • @Shmiddty我花了最后几个小时写作! (7认同)
  • 哦,没有意识到这是你自己的问题.道歉! (4认同)