例如,让我们考虑存储类说明符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语言标准的相关部分并加以解释。
总之,声明说明符中的任何位置(参见 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
类型被定义为类型说明符,您可以按任意顺序放置类型说明符和存储说明符。请注意,它们都是等效的,等同于说
static int *j;
static int *k;
或
int static *j;
int static *k;
根据 7.1,C++声明的 [简化] 结构为
decl-specifier-seq init-declarator-list;
根据 7.1/1,存储类说明符属于初始"公共"部分decl-specifier-seq
。
根据 8/1,init-declarator-list
是声明符序列。
根据 8/4,指针声明的*
部分是该序列中单个声明符的一部分。这立即意味着*
后面的所有内容都是该单个声明符的一部分。这就是某些存储类说明符放置无效的原因。声明符语法不允许包含存储类说明符。
基本原理相当明显:由于存储类说明符应该应用于整个声明中的所有声明符,因此它们被放置在声明的"公共"部分。
我想说的是,一个更有趣(并且有点相关)的情况发生在可以存在于decl-specifier-seq
和单个声明符中的说明符,例如const
说明符。例如,在以下声明中
int const *a, *b;
const
适用于所有声明者还是仅适用于第一个声明者?语法决定了前一种解释:const
适用于所有声明者,即它是decl-specifier-seq
的一部分。
如果你使用"黄金法则"(也不仅适用于指针),它会自然、直观地遵循,并且在 C/C++ 中声明变量时避免了很多错误和陷阱。不应违反"黄金法则"(也有极少数例外,例如应用于数组 typedef 的const
例外情况,const
传播到基类型,以及随C++附带的引用)。
K&R,附录A,第8.4节,声明符的含义指出:
每个声明符都被视为一个断言,即当表达式中出现与声明符形式相同的构造时,它会生成指定类型和存储类的对象。
要在 C/C++ 中声明一个变量,你应该真正考虑你应该应用于它的表达式来获取基类型。
1)应该有一个变量名
2)然后从声明语句中得到有效*的表达式,应用于变量名
3)然后是声明的其余信息和属性,如基本类型和存储
存储不是您总是可以赋予表达式结果的特征,例如与恒常性相反。只有在声明时才有意义。因此,存储必须位于不在 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,而不是索引]、恒常性等......所以 2 是一个映射良好的表达式(对于声明用法),"相同形式",一个反映变量用法的表达式,但不严格。
外行的黄金法则奖金
#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
数组[i]:元素是整数
所以你有一个函数,它返回对 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
),任何可以放在前面的东西,我都放在前面(volatile int c
)。
http://nosubstance.me/post/constant-bikeshedding/上还有更多关于这个主题的内容。