在C ++中声明
据我所知,C ++中的声明/初始化是带有“基本types”的语句,后面跟着逗号分隔的声明列表。
考虑下面的声明:
int i = 0, *const p = &i; // Legal, the so-called base type is 'int'. // i is an int while p is a const pointer to an int. int j = 0, const c = 2; // Error: C++ requires a type specifier for all declarations. // Intention was to declare j as an int and c an as const int. int *const p1 = nullptr, i1 = 0; // p1 is a const pointer to an int while i1 is just an int. int const j1 = 0, c1 = 2; // Both j1 and c1 are const int.
const int
是一个基types还是一个复合types?
从上面的第二个声明中的错误,它似乎是一个基本types。 如果是这样,那么第一个声明呢?
换句话说,如果第一个陈述是合法的,为什么不是第二个? 另外,为什么第三条和第四条陈述中的行为有所不同?
好问题,复杂的答案。 要真正掌握这一点,你需要非常彻底地理解C ++声明的内部结构。
(请注意,在这个答案中,我将完全省略属性的存在以防止过度复杂化)。
一个声明有两个组成部分:一个说明符序列,后面跟着一个以逗号分隔的init声明者列表。
说明符是这样的:
- 存储类说明符(如
static
,extern
) - 函数说明符(如
virtual
,inline
) -
friend
,typedef
,constexpr
- types说明符 ,其中包括:
- 简单types说明符(如
int
,short
) - cv-qualifiers (
const
,volatile
) - 其他东西(例如
decltype
)
- 简单types说明符(如
声明的第二部分是用逗号分隔的init-declarators。 每个init-declarator由一系列的声明符组成,可选的是一个初始化符。
什么声明是:
- 标识符(例如,
i
在int i;
) - 类指针运算符(
*
,&
,&&
,指向成员的语法) - 函数参数语法(例如
(int, char)
) - 数组语法(例如
[2][3]
) - cv-qualifiers ,如果这些遵循一个指针声明符。
请注意,声明的结构是严格的:第一个说明符,然后是init-声明符(每个声明符都可以跟随一个初始化符)。
规则是:指定符适用于整个声明,而声明符只适用于一个init声明器(指向逗号分隔列表的一个元素)。
另外请注意,cv-qualifier可以同时用作说明符和声明符。 作为一个声明符,语法限制它们只能在指针出现的时候使用。
所以,要处理你发布的四个声明:
1
int i = 0, *const p = &i;
说明符部分只包含一个说明符: int
。 这是所有申报人将适用的部分。
有两个init声明符: i = 0
和* const p = &i
。
第一个有一个声明, i
和一个初始化= 0
。 由于没有types修饰的声明符,所以i
的types由说明符给出,在这种情况下是int
。
第二个init声明符有三个声明符: *
, const
和p
。 还有一个初始者, = &i
。
声明符*
和const
将基types修改为“指向基types的常量指针”。 由说明符给出的基本types是int
,对于p
的types将是“指向int
常量指针”。
2
int j = 0, const c = 2;
同样,一个说明符: int
和两个init声明符: j = 0
和const c = 2
。
对于第二个init声明符,声明符是const
和c
。 正如我所提到的,如果有指针参与,语法只允许cv-qualifiers作为声明。 这不是这种情况,因此错误。
3
int *const p1 = nullptr, i1 = 0;
一个说明符: int
,两个init声明符: * const p1 = nullptr
, i1 = 0
。
对于第一个init-declarator,声明符是: *
, const
和p1
。 我们已经处理了这样的初始声明者(情况1中的第二个)。 它将“常量指针基types”添加到说明符定义的基types(仍然是int
)。
对于第二个init声明i1 = 0
,很明显。 没有types修改,按原样使用说明符。 所以i1
变成了一个int
。
4
int const j1 = 0, c1 = 2;
在这里,我们与前三者有着根本不同的情况。 我们有两个说明符: int
和const
。 然后是两个init-declarators, j1 = 0
和c1 = 2
。
这些init-declarators中都没有任何types修饰声明,所以它们都使用来自说明符的typesconst int
。
这在[dcl.dcl]和[dcl.decl]中作为simple-declaration
*的一部分进行了指定,归结为ptr-declarator
分支之间的差异:
声明-SEQ: 宣言 宣言: 块声明 块声明: 简单的声明 简单的声明: decl-specifier-seq opt init-declarator-list opt ; ---- DECL说明符-SEQ: decl-specifier decl-specifier-seq DECL说明符: types说明符← 在你的错误中提到 types说明符: 拖尾型说明符 拖尾型说明符: 简单型说明符 CV-预选赛 ---- 初始化声明符列表: 初始化声明符 init-declarator-list,init-declarator 初始化声明符: 声明器初始化器select 声明符: PTR声明符 ptr-declarator:← 这里是“开关” noptr声明符 ptr运算符ptr声明符 ptr-operator:← 允许const * cv-qualifier-seq select CV-预选赛: 常量 挥发物 noptr-declarator:← 不允许const 声明符-ID 声明符ID: ID-expression
规则中的重要分支是在ptr-declarator
:
ptr-declarator: noptr-declarator ptr-operator ptr-declarator
实际上,在你的上下文中, noptr-declarator
只是一个id-expression
。 它可能不包含任何cv-qualifier
,但是包含合格或不合格的id。 但是,一个ptr-operator
可能包含一个cv-qualifier
。
这表明你的第一个声明是完全有效的,因为你的第二个init-declarator
*const p = &i;
是ptr-operator ptr-declarator
的ptr-operator
ptr-operator ptr-declarator
ptr-operator
,在这种情况下ptr-operator
是* const
,而ptr-declarator
是一个不合格的标识符。
你的第二个陈述是不合法的,因为它不是一个有效的ptr-operator
:
const c = 2
一个ptr-operator
必须以*
, &
, &&
或者一个嵌套的名称说明符开头,后跟*
。 由于const c
并不以这两个标记开头,所以我们把const c
当作noptr-declarator
,这里不允许const
。
另外,为什么第三次和第四次陈述的行为有所不同?
由于int
是type-specifier
, *
是init-declarator
,
*const p1
声明一个常量指针。
但是,在int const
,我们有一个decl-specifier-seq
,它是两个decl-specifier
, int
(一个simple-type-specifier
)和const
(一个cv-qualifier
),参见trailing-type-specifier
。 因此两者都构成一个声明说明符。
*注意:我省略了所有不能应用的替代scheme,并简化了一些规则。 有关更多信息,请参阅C ++ 11( n3337 )的第7节“声明”和第8节“声明者”。