什么是宣言和声明中使用,以及如何在它们的类型由标准的解释?(What are declaration

2019-06-21 21:40发布

究竟如何的标准定义,例如, float (*(*(&e)[10])())[5]声明类型的“参照10指针的阵列的变量的(功能)返回指针到阵列5 float “?

通过启发讨论与@DanNissenbaum

Answer 1:

我指的是C ++ 11的标准在这个岗位

声明

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

DECL说明符-SEQ 选择 的init声明符列表选择 ;
属性符-SEQ DECL说明符-SEQ 选择 的init声明符列表 ;

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

声明说明符

因此,我们的声明中,DECL说明符-SEQ,第一部分是由声明说明符的。 这些包括一些事情,我们可以忽略,如存储说明符( staticextern等),功能说明符( inline等)时, friend符,等等。 然而,我们感兴趣的一个声明说明符类型说明符 ,其中可能包括简单类型的关键字( charintunsigned等),用户自定义类型的名称,cv修饰符( constvolatile ),及其他我们不关心。

实施例 :因此,一个DECL说明符-SEQ这仅仅是类型说明符的一个序列的一个简单的例子是const int 。 另外一个可能是unsigned int volatile

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

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

  1. const int const有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?

    通过语义规则无效! const不能与自身相结合。

  2. unsigned const int一个有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?

    有效! 不要紧的const分离unsignedint

  3. auto const有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?

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

  4. int * const一个有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?

    通过语法规则无效! 虽然这很可能是丰满型的声明,只有int是声明说明序列。 该声明说明符仅提供基本类型,而不是化合物改性剂像指针,参考文献,阵列等

声明符

一个简单的声明的第二部分是init声明符列表 。 它是由逗号分隔的说明符的序列,每个具有可选初始化(§8)。 每个声明符引入一个单变量或函数到程序中。 声明符的最简单的形式就是你介绍名字- 声明符-ID。 声明int x, y = 5; 具有声明说明序列,这只是int ,后面两个说明符, xy ,其中第二个具有初始化。 但我们会忽略这个职位的其余部分初始化。

甲声明符可以具有特别复杂的语法,因为这是允许用户指定的变量是否是一个指针,参考,数组,函数指针等声明的部分请注意,这些是说明符的所有部分,而不是声明作为一个整体。 这正是为什么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; 是标识符的声明xint x ,和标识符的声明yint *y

类型标准为类似英文句子表达(如“指针为int”)。 声明在这个英语状式的解释两个部分进行。 首先,声明说明的类型被确定。 其次,递归程序适用的声明作为一个整体。

声明指定型

类型声明说明符序列的是由标准的表10来确定。 它列出了给定的类型,它们包含以任何顺序对应的说明符的序列。 因此,举例来说,它含有任何序列signedchar以任意顺序,包括char signed ,具有输入“符号字符”。 出现在声明说明序列的任何CV-限定符添加到该类型的前部。 所以char const signed的类型是“常量符号字符”。 这确保了无论你放什么样的顺序符,类型将是相同的。

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

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

    “常量unsigned long int类型”

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

    “挥发性炭”

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

    这取决于! auto将从初始推断。 如果它被推断为int ,例如,类型将是“const int的”。

声明类型

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

步骤1中的第一步骤是将声明分成形式TD其中T是声明说明序列和D是说明符。 所以,我们得到:

T = float const
D = (*(*(&e)[10])())[5]

的类型的T是,当然,“常量浮动”,就像我们在上一节中确定。 然后,我们寻找的§8.3的款是当前形式相匹配D 。 你会发现,这是§8.3.4阵列,因为它说,它适用于形式的声明TD ,其中D的形式为:

D1 [ 常数表达式选择 ] 属性说明符-SEQ 选择

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

现在想象声明T D1 (我们已经摆脱了的[5]

T D1 = const float (*(*(&e)[10])())

它的类型是“<一些东西> T ”。 本节指出,我们的标识符的类型, e ,是“<一些东西> 5的阵列T ”,其中<一些东西>是一样的,在假想声明的类型。 因此,要制定出类型的其余部分,我们需要解决的类型T D1

这是递归! 我们递归地制定出声明的内部的类型,在每一步剥离了一下它关闭。

第2步所以,像以前一样,我们分手了新的声明为形式的TD

T = const float
D = (*(*(&e)[10])())

这个匹配段§8.3/ 6,其中D是所述形式的( D1 ) 这种情况下是简单的类型, TD是简单的类型T D1

T D1 = const float *(*(&e)[10])()

第3步让我们把这个TD ,现在又拆起来:

T = const float
D = *(*(&e)[10])()

这个匹配§8.3.1指针,其中D是所述形式的* D1 。 如果T D1已经输入“<一些东西> T ”,则TD具有类型“<一些东西>指针T ”。 所以,现在我们需要的类型T D1

T D1 = const float (*(&e)[10])()

第4步 ,我们把它叫做TD和分裂它:

T = const float
D = (*(&e)[10])()

这个匹配§8.3.5函数,其中D是所述形式的D1 () 如果T D1已经输入“<一些东西> T ”,则TD具有类型“<一些东西>的功能()返回T ”。 所以,现在我们需要的类型T D1

T D1 = const float (*(&e)[10])

第5步 ,我们可以申请我们为第2步,在声明符只是parenthesised结束了同一条规则:

T D1 = const float *(&e)[10]

第6步 。当然,我们分手起来:

T = const float
D = *(&e)[10]

我们再次与匹配§8.3.1指针D形式的* D1 。 如果T D1已经输入“<一些东西> T ”,则TD具有类型“<一些东西>指针T ”。 所以,现在我们需要的类型T D1

T D1 = const float (&e)[10]

第7步拆分起来:

T = const float
D = (&e)[10]

我们再次匹配§8.3.4阵列,与D形式的D1 [10] 如果T D1已经输入“<一些东西> T ”,则TD具有类型“<一些东西>阵列10的T ”。 那么,什么是T D1的类型?

T D1 = const float (&e)

第8步重新应用括号步骤:

T D1 = const float &e

第9步拆分起来:

T = const float
D = &e

现在,我们匹配§8.3.2参考,其中D是形式的& D1 。 如果T D1已经输入“<一些东西> T ”,则TD具有类型“<一些东西>参照T ”。 那么什么是类型T D1

T D1 = const float e

第10步那么它只是“T”当然! 没有<一些东西>在这个水平。 这是由基础案例规则在§8.3/ 5给出。

我们就大功告成了!

所以,现在,如果我们看一下我们在每个步骤中确定,替换<一些东西>类型从下面的每个级别s,我们可以确定的类型efloat 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

太好了! 因此,显示了编译器如何推导出声明的类型。 请注意,如果有多个声明这被应用于标识符的每个声明。 尝试找出这些:

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

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

    “指针123的指针阵列的指针为bool”

  2. 有哪些类型的yz在声明int const signed *(*y)(int), &z = i;

    y是一个“指针(int)的返回指针的函数为const符号int”
    z是“引用为const符号整型”

如果有人有任何更正,请让我知道!



Answer 2:

这是我解析方式float const (*(*(&e)[10])())[5] 首先,识别符。 这里说明符float const 。 现在,让我们来看看的优先级。 [] = () > * 。 括号是用来消除优先级。 考虑到优先级,让我们找出变量ID,这是e 。 所以,e是一个数组的引用(因为[] > * 10个指针)的功能(自() > * ),其不采取任何参数和返回的指针和指向5浮子常量的阵列。 所以符是最后和休息根据优先级解析。



文章来源: What are declarations and declarators and how are their types interpreted by the standard?