在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声明者列表。

说明符是这样的:

  • 存储类说明符(如staticextern
  • 函数说明符(如virtualinline
  • friendtypedefconstexpr
  • types说明符 ,其中包括:
    • 简单types说明符(如intshort
    • cv-qualifiersconstvolatile
    • 其他东西(例如decltype

声明的第二部分是用逗号分隔的init-declarators。 每个init-declarator由一系列的声明符组成可选的是一个初始化符。

什么声明是:

  • 标识符(例如, iint 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声明符有三个声明符: *constp 。 还有一个初始者, = &i

声明符*const将基types修改为“指向基types的常量指针”。 由说明符给出的基本types是int ,对于p的types将是“指向int常量指针”。

2

 int j = 0, const c = 2; 

同样,一个说明符: int和两个init声明符: j = 0const c = 2

对于第二个init声明符,声明符是constc 。 正如我所提到的,如果有指针参与,语法只允许cv-qualifiers作为声明。 这不是这种情况,因此错误。

3

 int *const p1 = nullptr, i1 = 0; 

一个说明符: int ,两个init声明符: * const p1 = nullptri1 = 0

对于第一个init-declarator,声明符是: *constp1 。 我们已经处理了这样的初始声明者(情况1中的第二个)。 它将“常量指针基types”添加到说明符定义的基types(仍然是int )。

对于第二个init声明i1 = 0 ,很明显。 没有types修改,按原样使用说明符。 所以i1变成了一个int

4

 int const j1 = 0, c1 = 2; 

在这里,我们与前三者有着根本不同的情况。 我们有两个说明符: intconst 。 然后是两个init-declarators, j1 = 0c1 = 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-declaratorptr-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

另外,为什么第三次和第四次陈述的行为有所不同?

由于inttype-specifier*init-declarator

 *const p1 

声明一个常量指针。

但是,在int const ,我们有一个decl-specifier-seq ,它是两个decl-specifierint (一个simple-type-specifier )和const (一个cv-qualifier ),参见trailing-type-specifier 。 因此两者都构成一个声明说明符。


*注意:我省略了所有不能应用的替代scheme,并简化了一些规则。 有关更多信息,请参阅C ++ 11( n3337 )的第7节“声明”和第8节“声明者”。