例如,让我们考虑static
存储类说明符。以下是此存储类说明符的有效和不正确用法的几个示例:
static int a; // valid
int static b; // valid
static int* c; // valid
int static* d; // valid
int* static e; // ill-formed
static int const* f; // valid
int static const* g; // valid
int const static* h; // valid
int const* static i; // ill-formed
typedef int* pointer;
static pointer j; // valid
pointer static k; // valid
(Visual C ++ 2012,g ++ 4.7.2和Clang ++ 3.1接受了标记为“有效”的声明。所有这些编译器都拒绝了标记为“格式错误”的声明。)
这看起来很奇怪,因为存储类说明符适用于声明的变量。它是声明的变量static
,而不是声明的变量的类型。为什么e
和i
格式错误,但k
格式正确?
管理存储类说明符的有效放置的规则是什么?虽然我在此示例中使用了static
,但该问题适用于所有存储类说明符。最好,一个完整的答案应引用C ++ 11语言标准的相关部分并解释它们。
答案 0 :(得分:17)
总之,声明说明符中的任何位置(参见ISO / IEC 14882-2012中的第7.1节),即*
之前。 *
之后的限定符与指针声明符关联,而不是类型说明符,static
在指针声明符的上下文中没有意义。
考虑以下情况: 您可以在同一个声明列表中声明一个普通的int和一个指向int的指针,如下所示:
int a, *b;
这是因为类型说明符是int
,那么你有两个声明使用那个类型说明符int
,a
和一个声明指针的指针声明符*a
到int
。现在考虑:
int a, static b; // error
int a, *static b; // error
int a, static *b; // error
哪个看起来应该是错误的(因为它们是),原因(如第7.1和8.1节所定义)是因为C和C ++要求您的存储说明符与您的类型说明符一起使用,而不是在声明符中。 所以现在应该清楚的是,以下也是错误的,因为上述三个也是错误的:
int *static a; // error
你的最后一个例子,
typedef int* pointer;
static pointer j; // valid
pointer static k; // valid
都是有效的并且都是等效的,因为pointer
类型被定义为类型说明符,您可以按任何顺序放置类型说明符和存储specifeir。请注意,它们都是等效的,等同于说
static int *j;
static int *k;
或
int static *j;
int static *k;
答案 1 :(得分:5)
根据7.1,C ++声明的[简化]结构是
decl-specifier-seq init-declarator-list;
根据7.1 / 1,存储类说明符属于最初的“公共”部分decl-specifier-seq
。
Per 8/1,init-declarator-list
是一系列声明符。
每8/4,指针声明的*
部分是该序列中单个声明符的一部分。这立即意味着*
之后的所有内容都是该单个声明符的一部分。这就是您的某些存储类说明符展示位置无效的原因。声明符语法不允许包含存储类说明符。
理由很明显:由于存储类说明符应该适用于整个声明中的所有声明符,因此它们被放入声明的“公共”部分。
我会说,一个更有趣(并且有些相关)的情况发生在 decl-specifier-seq
和单个声明符中的说明符,例如const
说明符。例如,在以下声明中
int const *a, *b;
const
是否适用于所有声明者或仅适用于第一个声明者?语法规定了前一种解释:const
适用于所有声明者,即它是decl-specifier-seq
的一部分。
答案 2 :(得分:3)
如果你使用"Golden Rule"(它也不仅仅适用于指针),它自然而直观地遵循,并且在C / C ++中声明变量时避免使用a lot of mistakes和pitfalls 。不应该违反“黄金规则”(例如,const
应用于数组typedef,它将const
传播到基类型,以及C ++附带的引用。
K& R,附录A,第8.4节,声明者的含义:
每个声明符都被认为是一个断言,当一个构造与声明符相同的表单出现在表达式中时,它会产生一个指定类型和存储类的对象。
要在C / C ++中声明一个变量,你应该考虑应该应用它来获得基类型的表达式。
1)应该有一个变量名
2)然后表达式作为声明语句的有效*,应用于变量名
3)然后是基本类型和存储
等声明的剩余信息和属性存储不是您总能赋予表达式结果的特征,例如与constness相反。只有在声明时它才有意义。所以存储必须来自其他不在2的地方。
int * const *pp;
/*valid*/
int * static *pp;
/*invalid, this clearly shows how storage makes no sense for 2 and so breaks */
/*the golden rule. */
/*It's not a piece of information that goes well in the middle of a expression.*/
/*Neither it's a constraint the way const is, it just tells the storage of */
/*what's being declared. */
我认为K& R希望我们在声明变量时使用反向推理,这通常不是常见习惯。使用时,它避免了大多数复杂的声明错误和困难。
*有效并不是严格意义上的,因为会出现一些变化,如x [],x [size,not indexing],constness等...所以2是映射好的表达式(对于声明用法),“相同形式”,reflects variable's use,但not strictly。
#include <iostream>
int (&f())[3] {
static int m[3] = {1, 2, 3};
return m;
}
int main() {
for(int i = 0; i < sizeof(f()) / sizeof(f()[0]); ++i)
std::cout << f()[i] << std::endl;
return 0;
}
在声明的上下文中,&
不是获取地址的操作,它只是说明了什么是引用。
f()
:f
是功能 &
返回:返回是参考 [3]
:参考是 3个元素的数组 int
array [i] :元素是一个int 所以你有一个函数返回对3个整数数组的引用,并且因为我们有正确的数组大小的编译时间信息,我们可以随时sizeof
检查它=)
最后的黄金提示,对于任何可以放在类型之前的东西,在多个声明中,它一次应用于所有变量,因此不能单独应用。
此const
无法放在int
之前:
int * const p;
以下是有效的:
int * const p1, * const p2;
这个可以:
int const *p; // or const int *p;
所以以下内容无效:
int const *p1, const *p2;
可交换的const
适用于所有人:
int const *p1, *p2; // or const int *p1, *p2;
因此,我总是将不能的所有内容放在类型之前,更靠近变量(int *a
,int &b
),以及任何< strong>可以放在之前,我放在之前(volatile int c
)。
http://nosubstance.me/post/constant-bikeshedding/上有关于此主题的更多内容。