在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节“声明者”。