C++ 中的声明

Declarations in C++

据我了解,C++ 中的 declarations/initializations 是带有 'base type' 后跟以逗号分隔的声明符列表的语句。

考虑以下声明:

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是基本类型还是复合类型?

从上面第二个声明的错误来看,好像是基类型。如果是这样,那么第一个声明呢?

换句话说,如果第一个声明是合法的,为什么第二个声明不合法?另外,为什么第三个和第四个语句的行为不同?

好问题,答案复杂。要真正掌握这一点,你需要相当透彻地理解C++声明的内部结构。

(请注意,在这个答案中,我将完全省略属性的存在以防止过于复杂)。

声明有两个组成部分:一系列 说明符, 后跟逗号分隔的 init-declarators.

说明符是这样的:

  • 存储 class 说明符(例如 staticextern
  • 函数说明符(例如virtualinline
  • friendtypedefconstexpr
  • 类型说明符,其中包括:
    • 简单类型说明符(例如 intshort
    • cv-限定符 (const, volatile)
    • 其他(例如decltype

声明的第二部分是逗号分隔的初始声明符。每个 init-declarator 由一系列 declarators, 可选地后跟一个 initialiser.

什么是声明符:

  • 标识符(例如 int i; 中的 i
  • 类似指针的运算符(*&&&、成员指针语法)
  • 函数参数语法(例如(int, char)
  • 数组语法(例如[2][3]
  • cv-qualifiers,如果它们跟在指针声明符之后。

请注意声明的结构是严格的:首先是说明符,然后是初始化声明符(每个都是声明符,后面可选地跟一个初始化程序)。

规则是:说明符适用于整个声明,而声明符仅适用于一个初始声明符(适用于逗号分隔列表的一个元素)。

另请注意,上面的 cv 限定符既可以用作说明符,也可以用作声明符。作为声明符,语法限制它们只能在指针存在的情况下使用。

因此,要处理您发布的四个声明:

1

int i = 0, *const p = &i;

说明符部分只包含一个说明符:int。这是所有声明符将适用的部分。

有两个初始声明符:i = 0* const p = &i

第一个有一个声明器 i 和一个初始化器 = 0。由于没有类型修改声明符,i 的类型由说明符给出,在本例中为 int

第二个 init-declarator 有三个声明符:*constp。还有一个初始化程序,= &i.

声明符 *const 将基类型修改为表示 "constant pointer to the base type." 说明符给出的基类型是 int,为 [= 的类型42=] 将是 "constant pointer to int."

2

int j = 0, const c = 2;

同样,一个说明符:int,以及两个初始化声明符:j = 0const c = 2.

对于第二个init-declarator,声明符是constc。正如我提到的,如果涉及指针,语法只允许 cv 限定符作为声明符。这里不是这种情况,因此出现错误。

3

int *const p1 = nullptr, i1 = 0;

一个说明符:int,两个初始化声明符:* const p1 = nullptri1 = 0.

对于第一个 init-declarator,声明符是:*constp1。我们已经处理过这样的 init-declarator(第二个 1)。它将 "constant pointer to base type" 添加到说明符定义的基类型(仍然是 int)。

对于第二个 init-declarator i1 = 0,这是显而易见的。没有类型修改,按原样使用说明符。所以 i1 变成 int.

4

int const j1 = 0, c1 = 2;

在这里,我们的情况与前三者根本不同。我们有两个说明符:intconst。然后是两个初始化声明符,j1 = 0c1 = 2.

None 这些初始声明符中有任何类型修改声明符,因此它们都使用说明符中的类型,即 const int.

这在 [dcl.dcl] 和 [dcl.decl] 中指定为 simple-declaration* 的一部分,归结为 ptr-declarator 中分支之间的差异:

declaration-seq:
    declaration

declaration:
    block-declaration

block-declaration:
    simple-declaration

simple-declaration:
    decl-specifier-seqopt init-declarator-listopt ;
----

decl-specifier-seq:
    decl-specifier decl-specifier-seq    

decl-specifier:    
    type-specifier                               ← mentioned in your error

type-specifier:
    trailing-type-specifier

trailing-type-specifier:
    simple-type-specifier
    cv-qualifier
----

init-declarator-list:
   init-declarator
   init-declarator-list , init-declarator

init-declarator:
   declarator initializeropt

declarator:
    ptr-declarator

ptr-declarator:                                 ← here is the "switch"
    noptr-declarator
    ptr-operator ptr-declarator

ptr-operator:                                   ← allows const
    *  cv-qualifier-seq opt

cv-qualifier:
    const
    volatile

noptr-declarator:                               ← does not allow const
    declarator-id

declarator-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-declarator,在这种情况下 ptr-operator* constptr-declarator 是不合格的标识符。

您的第二个陈述不合法,因为它不是有效的 ptr-operator:

 const c = 2

A ptr-operator 必须以 *&&& 或后跟 * 的嵌套名称说明符开头。由于 const c 不以这些标记中的任何一个开头,我们将 const c 视为 noptr-declarator,这不允许此处使用 const

Also, why the behaviour differs among 3rd and 4th statements?

因为inttype-specifier,而*init-declarator的一部分,

*const p1

声明一个常量指针。

然而,在 int const 中,我们有一个 decl-specifier-seq 两个 decl-specifierint(一个 simple-type-specifier)和 constcv-qualifier),参见 trailing-type-specifier。因此两者形成一个声明说明符。


* 注意:我省略了所有不能在这里应用的替代方案并简化了一些规则。有关详细信息,请参阅 C++11 (n3337) 的第 7 节 "Declarations" 和第 8 节 "Declarators"。