究竟如何的标准定义,例如, float (*(*(&e)[10])())[5]
声明类型的“参照10指针的阵列的变量的(功能)返回指针到阵列5 float
“?
通过启发讨论与@DanNissenbaum
究竟如何的标准定义,例如, float (*(*(&e)[10])())[5]
声明类型的“参照10指针的阵列的变量的(功能)返回指针到阵列5 float
“?
通过启发讨论与@DanNissenbaum
我指的是C ++ 11的标准在这个岗位
我们关心的是在C ++中,这是通过下面两种形式(§7/ 1)之一的语法被称为 S上的类型的声明 :
DECL说明符-SEQ 选择 的init声明符列表选择 ;
属性符-SEQ DECL说明符-SEQ 选择 的init声明符列表 ;
属性说明符-SEQ是属性(的序列[[something]]
和/或对准说明符( alignas(something)
)。 由于这些不影响声明的类型,我们可以忽略他们的以上两种模式的第二位。
因此,我们的声明中,DECL说明符-SEQ,第一部分是由声明说明符的。 这些包括一些事情,我们可以忽略,如存储说明符( static
, extern
等),功能说明符( inline
等)时, friend
符,等等。 然而,我们感兴趣的一个声明说明符类型说明符 ,其中可能包括简单类型的关键字( char
, int
, unsigned
等),用户自定义类型的名称,cv修饰符( const
或volatile
),及其他我们不关心。
实施例 :因此,一个DECL说明符-SEQ这仅仅是类型说明符的一个序列的一个简单的例子是const int
。 另外一个可能是unsigned int volatile
。
你可能会想:“哦,所以像const volatile int int float const
也是DECL说明符-SEQ?” 你是对的,它符合语法规则,但语义规则不允许这样的DECL说明符-SEQ。 只有一种类型说明符是允许的,事实上,除了某些组合(如unsigned
与int
或const
与除了本身的任何东西)和至少一个需要非CV-限定符(§7.1.6/ 2-3)。
快速测验 (您可能需要参考标准)
是const int const
有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?
通过语义规则无效!
const
不能与自身相结合。
是unsigned const int
一个有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?
有效! 不要紧的
const
分离unsigned
的int
。
是auto const
有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?
有效!
auto
是一个声明说明,但在C ++ 11类改变。 之前,它是一个存储说明符(如static
),但现在它是一个类型说明符。
是int * const
一个有效的声明说明顺序或不? 如果不是,它是通过句法和语义规则,不允许?
通过语法规则无效! 虽然这很可能是丰满型的声明,只有
int
是声明说明序列。 该声明说明符仅提供基本类型,而不是化合物改性剂像指针,参考文献,阵列等
一个简单的声明的第二部分是init声明符列表 。 它是由逗号分隔的说明符的序列,每个具有可选初始化(§8)。 每个声明符引入一个单变量或函数到程序中。 声明符的最简单的形式就是你介绍名字- 声明符-ID。 声明int x, y = 5;
具有声明说明序列,这只是int
,后面两个说明符, x
和y
,其中第二个具有初始化。 但我们会忽略这个职位的其余部分初始化。
甲声明符可以具有特别复杂的语法,因为这是允许用户指定的变量是否是一个指针,参考,数组,函数指针等声明的部分请注意,这些是说明符的所有部分,而不是声明作为一个整体。 这正是为什么int* x, y;
不申报两个指针-星号*
是的说明符的部分x
的声明符,而不是一部分y
。 一个重要的规则是,每个说明符必须有一个确切的说明符-ID -它声明名称。 一旦声明的类型决定的有关有效的声明符其余规则强制执行(以后我们会来的话)。
例如 :一个说明符的一个简单的例子是*const p
,其声明了一个const
指针...的东西。 它指向的类型是通过在其声明中的声明说明符给出。 更可怕的例子是一个在给定的问题, (*(*(&e)[10])())[5]
这再次声明了一个参考函数指针返回指针的阵列...,所述该类型的最后一部分实际上是由该声明符给出。
你不太可能曾经遇到过这样可怕的声明中使用,但有时相似的人真的出现。 这是一个有用的技能,以便能够读取像在问题的声明,并随实践的技能。 它有助于了解标准如何解释的声明的类型。
快速测验 (您可能需要参考标准)
其中的部分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
现在我们知道,声明是由一个声明符符序列和声明符列表中,我们可以开始思考如何声明的类型决定的。 例如,它可能是显而易见的, int* p;
定义p
为“指向int的指针”,但对于其他类型的它并不那么明显。
与多个声明的声明,比方说2说明符,被认为是特定标识的两个声明。 也就是说, int x, *y;
是标识符的声明x
, int x
,和标识符的声明y
, int *y
。
类型标准为类似英文句子表达(如“指针为int”)。 声明在这个英语状式的解释两个部分进行。 首先,声明说明的类型被确定。 其次,递归程序适用的声明作为一个整体。
类型声明说明符序列的是由标准的表10来确定。 它列出了给定的类型,它们包含以任何顺序对应的说明符的序列。 因此,举例来说,它含有任何序列signed
和char
以任意顺序,包括char signed
,具有输入“符号字符”。 出现在声明说明序列的任何CV-限定符添加到该类型的前部。 所以char const signed
的类型是“常量符号字符”。 这确保了无论你放什么样的顺序符,类型将是相同的。
快速测验 (您可能需要参考标准)
什么是声明说明序列类型int long const unsigned
?
“常量unsigned long int类型”
什么是声明说明序列类型char volatile
?
“挥发性炭”
什么是声明说明序列类型auto const
?
这取决于!
auto
将从初始推断。 如果它被推断为int
,例如,类型将是“const int的”。
现在,我们已经声明符序列的类型,我们可以制定出一个标识符的整个声明的类型。 这是通过在§8.3定义递归过程来完成。 为了解释这个过程中,我将使用一个运行实例。 我们将制定出的类型e
在float 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,我们可以确定的类型e
在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
如果我们结合这一切都在一起,我们得到的是:
reference to array of 10 pointer to function of () returning pointer to array of 5 const float
太好了! 因此,显示了编译器如何推导出声明的类型。 请注意,如果有多个声明这被应用于标识符的每个声明。 尝试找出这些:
快速测验 (您可能需要参考标准)
是什么的类型x
在声明bool **(*x)[123];
?
“指针123的指针阵列的指针为bool”
有哪些类型的y
和z
在声明int const signed *(*y)(int), &z = i;
?
y
是一个“指针(int)的返回指针的函数为const符号int”
z
是“引用为const符号整型”
如果有人有任何更正,请让我知道!
这是我解析方式float const (*(*(&e)[10])())[5]
首先,识别符。 这里说明符float const
。 现在,让我们来看看的优先级。 [] = () > *
。 括号是用来消除优先级。 考虑到优先级,让我们找出变量ID,这是e
。 所以,e是一个数组的引用(因为[] > *
10个指针)的功能(自() > *
),其不采取任何参数和返回的指针和指向5浮子常量的阵列。 所以符是最后和休息根据优先级解析。