所以我在学习课程时偶然发现了一些我觉得很尴尬的事情。
class Nebla
{
public:
int test()
{
printout();
return x;
}
void printout()
{
printout2();
}
private:
int x,y;
void printout2()
{
cout<<"Testing my class";
}
};
我发现在类中,我可以在声明函数(原型)之前使用它们
您可以看到我在分离前使用了
printout()
、printout2()
。
在声明变量为之前,我也可以使用变量
你可以看到我做了
return x
;在解密x.之前
为什么我可以在声明前在类中使用函数和变量,但在类外使用?如果这样做,我会出错?
感谢
好问题;多年来,我一直依赖这个功能而没有想过它。我查阅了几本C++书籍来找到答案,包括Stroustrup的《C++编程语言》和《注释C++参考手册》,但都没有承认或解释其中的区别。但是,我想我可以推理。
我相信,您的示例之所以有效,是因为test
和printout
的主体并不是它们在文件中真正出现的位置。代码
class MyClass {
void someFun() {
x = 5;
}
int x;
};
这似乎违反了在使用变量之前必须声明变量的规则,实际上相当于:
class MyClass {
void someFun();
int x;
};
void MyClass::someFun() {
x = 5;
}
一旦我们这样重写它,很明显,MyClass
定义中的内容实际上是声明的列表。这些可以按任何顺序排列。在声明x
之前,您不会依赖它。我知道这是真的,因为如果你像这样重写这个例子,
void MyClass::someFun() {
x = 5;
}
class MyClass {
void someFun();
int x;
};
它将不再编译!因此,类定义是第一位的(带有完整的成员列表),然后您的方法可以使用任何成员,而不考虑它们在类中声明的顺序。
最后一个难题是C++禁止在类定义之外声明任何类成员,因此一旦编译器处理了类定义,它就会知道类成员的完整列表。这一点在Stroustrup的注释C++参考手册的第170页中有说明:">成员列表定义了类的全套成员。不能在其他地方添加任何成员。">
谢谢你让我调查此事;我今天学到了一些新东西
为了清楚起见,这是C++标准所要求的,而不仅仅是几个编译器处理类定义的方式。
N3242 3.3.7:
类中声明的名称的潜在作用域不仅包括名称声明点后面的声明性区域,还包括所有函数体、非静态数据成员的大括号或相等初始值设定项,以及该类中的默认参数(包括嵌套类中的此类内容)。
除了Philip的良好回应外,Stroustrup在C++的设计和进化中对名称查找规则进行了很好的解释。这在"6.3澄清"中有描述。在6.3.1.1中,"ARM名称查找规则">,他提到了ARM中定义的两个规则:
[1] 类型重新定义规则:类型名称在类中使用后,不能在类中重新定义。
[2] 重写规则:对内联定义的成员函数进行分析,就好像它们是在类声明结束后立即定义的一样。
因此,在您的情况下,它将应用重写规则(正如Philip所推断的),这就是为什么您可以转发引用这些类成员。
这本书可能主要是出于历史兴趣(它写于94年),但我认为这些规则今天也以同样的方式适用。
之所以能够这样做,是因为当您调用test
、printout
或printout2
时,它们已经创建。如果在实现之前在任意函数的外部调用函数,则会出现错误。
将类成员函数视为与类的其他部分的求值流异步。这不适用于独立函数,但您可以访问尚未实例化的数据成员。我不完全确定为什么我们能够做到这一点,但我认为这与类对象的实例化有关。
为什么在声明之前可以在类中使用函数和变量
这是因为成员函数的主体是类的完整类上下文,如下面引用的语句所述:
来自class.mem.general#6:
6.类的完整类上下文是一个:
功能体([dcl.fct.def.general]),
默认参数,
noexcept说明符(〔except.spec〕)或
默认成员初始值设定项
在类的成员规范中。
这意味着在这里允许在成员函数test
内部使用printout
,在成员函数printout
内部使用printout2
,即使这些成员稍后在编写类定义时出现。