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 说明符(例如
static
、extern
)
- 函数说明符(例如
virtual
、inline
)
friend
、typedef
、constexpr
- 类型说明符,其中包括:
- 简单类型说明符(例如
int
、short
)
- 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 有三个声明符:*
、const
和 p
。还有一个初始化程序,= &i
.
声明符 *
和 const
将基类型修改为表示 "constant pointer to the base type." 说明符给出的基类型是 int
,为 [= 的类型42=] 将是 "constant pointer to int
."
2
int j = 0, const c = 2;
同样,一个说明符:int
,以及两个初始化声明符:j = 0
和 const c = 2
.
对于第二个init-declarator,声明符是const
和c
。正如我提到的,如果涉及指针,语法只允许 cv 限定符作为声明符。这里不是这种情况,因此出现错误。
3
int *const p1 = nullptr, i1 = 0;
一个说明符:int
,两个初始化声明符:* const p1 = nullptr
和 i1 = 0
.
对于第一个 init-declarator,声明符是:*
、const
和 p1
。我们已经处理过这样的 init-declarator(第二个 1)。它将 "constant pointer to base type" 添加到说明符定义的基类型(仍然是 int
)。
对于第二个 init-declarator i1 = 0
,这是显而易见的。没有类型修改,按原样使用说明符。所以 i1
变成 int
.
4
int const j1 = 0, c1 = 2;
在这里,我们的情况与前三者根本不同。我们有两个说明符:int
和 const
。然后是两个初始化声明符,j1 = 0
和 c1 = 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
是 * const
,ptr-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?
因为int
是type-specifier
,而*
是init-declarator
的一部分,
*const p1
声明一个常量指针。
然而,在 int const
中,我们有一个 decl-specifier-seq
两个 decl-specifier
、int
(一个 simple-type-specifier
)和 const
( cv-qualifier
),参见 trailing-type-specifier
。因此两者形成一个声明说明符。
* 注意:我省略了所有不能在这里应用的替代方案并简化了一些规则。有关详细信息,请参阅 C++11 (n3337) 的第 7 节 "Declarations" 和第 8 节 "Declarators"。
据我了解,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 说明符(例如
static
、extern
) - 函数说明符(例如
virtual
、inline
) friend
、typedef
、constexpr
- 类型说明符,其中包括:
- 简单类型说明符(例如
int
、short
) - 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 有三个声明符:*
、const
和 p
。还有一个初始化程序,= &i
.
声明符 *
和 const
将基类型修改为表示 "constant pointer to the base type." 说明符给出的基类型是 int
,为 [= 的类型42=] 将是 "constant pointer to int
."
2
int j = 0, const c = 2;
同样,一个说明符:int
,以及两个初始化声明符:j = 0
和 const c = 2
.
对于第二个init-declarator,声明符是const
和c
。正如我提到的,如果涉及指针,语法只允许 cv 限定符作为声明符。这里不是这种情况,因此出现错误。
3
int *const p1 = nullptr, i1 = 0;
一个说明符:int
,两个初始化声明符:* const p1 = nullptr
和 i1 = 0
.
对于第一个 init-declarator,声明符是:*
、const
和 p1
。我们已经处理过这样的 init-declarator(第二个 1)。它将 "constant pointer to base type" 添加到说明符定义的基类型(仍然是 int
)。
对于第二个 init-declarator i1 = 0
,这是显而易见的。没有类型修改,按原样使用说明符。所以 i1
变成 int
.
4
int const j1 = 0, c1 = 2;
在这里,我们的情况与前三者根本不同。我们有两个说明符:int
和 const
。然后是两个初始化声明符,j1 = 0
和 c1 = 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
是 * const
,ptr-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?
因为int
是type-specifier
,而*
是init-declarator
的一部分,
*const p1
声明一个常量指针。
然而,在 int const
中,我们有一个 decl-specifier-seq
两个 decl-specifier
、int
(一个 simple-type-specifier
)和 const
( cv-qualifier
),参见 trailing-type-specifier
。因此两者形成一个声明说明符。
* 注意:我省略了所有不能在这里应用的替代方案并简化了一些规则。有关详细信息,请参阅 C++11 (n3337) 的第 7 节 "Declarations" 和第 8 节 "Declarators"。