最近要开始准备笔面试了,找几份笔试题,觉得挺吃惊的,本来觉得基础看的差不多了,这下又弄的我晕乎乎的。
笔面试出现的那些题目有的考的真细啊, 一些妖娆的写法要问运行结果,这些东西书上提的太少,若不是自己尝试过一时间还怎么不知道怎么下手了。
我是觉得没必要没完没了的浪费时间去琢磨这些,C++本来不好掌握,如果精力多的话,花时间来规范书写、放弃使用糟糕方法更好,而不是一个个去琢磨。
总之,人家非得这么考,那我也没办法了。这篇用来备忘。
------------------------------------------------
C++类单继承情况下构造和析构顺序是书上提及最多的,父类构造总是先于子类、子类析构总是先于父类。
例如:
class A
{
public:
A() {cout << "A" << endl;}
A(int a) {cout << "A" << a << endl;}
};
class B : public A
{
public:
B() {cout << "B" << endl;}
};
在实例化B b; 或者 new B;的时候,输出结果是A \n B \n
再定义C继承B,构造C的时候输出“C\n”,那么输出结果就是A\n B\n C\n
这个没什么特别要说的。
多继承的情况下,例如:
class D
{
public:
D() {cout << "D" << endl;}
D(int a) {cout << "D" << a << endl;}
};
class E : public D, public A
{
public:
E() {cout << "E" << endl;}
};
此时的构造顺序在满足单继承构造顺序要求的情况下,出现了两个同级父类的构造顺序问题。
修改继承循序,会发现结果亦随之改变。在有多重继承下,对于同级父类,构造顺序是根据继承列表从左往右的。
类中包含类对象的情况下的构造顺序,例如:
class C : public B
{
public:
D d;
A a;
};
构造顺序为先A,再B,然后对于C来说发生了变化。
这里需要先对类属性d、a进行构造,然后才能进行本身的构造。所以输出是A B D A C (换行符人工回收了..)
推测是因为构造C需要知道分配内存的大小,这又依赖于D和A的大小,所以需要先对这两个对象进行内存分配,然后才能确定C的大小(因为调用构造函数前需要先进行内存分配)
而对于这里D和A的构造顺序,则是由他们定义的顺序决定的。靠前的会先被构造(但我不确定C++规范是否明确定义了这点)。
现在扩展一下该问题:
class C : public B
{
public:
D d(5);
A a;
};
显然这种写法会带来一个编译错误,但大多数情况下我们面对的都不是一个只包含默认构造函数的类,甚至是没有默认构造函数的类,那么如此定义是否会造成编译错误以及如何进行初始化呢?
比如类D的定义为:
class D
{
public:
// D() {cout << "D" << endl;}
D(int a) {cout << "D" << a << endl;}
};
答案是类内声明D d; 并不会带来编译错误,而在函数中执行D d; 会产生编译错误。
虽然写法一样,但这应该是两个不同的概念,前者仅仅作为声明,并不执行具体的实例化操作,而后者将对其进行实例化。
对应的初始化方法我知道的只有通过构造函数初始化列表这一种。但需要注意的是,构造函数的初始化列表的顺序并不表示实际上初始化的顺序,这可能会在存在依赖的初始化行为里称为一个隐藏的较好的BUG,实际初始化顺序是由定义的顺序决定的,即:
class C : public B
{
public:
C():a(5),d(5) {cout << "C" << endl;}
D d;
A a;
};
这种情况下的会先构造D,后构造A。
多访问区段下的构造成员顺序:
class C : public B
{
public:
D d3;
public:
C():a(5),d(0),d1(1),d2(2),d3(3) {cout << "C" << endl;}
private:
A a;
D d;
public:
D d1;
D d2;
};
例如对于上述代码,不断交换区段位置和访问权限,始终都是按照从上到下的顺序进行构造。
存在静态类成员:
class D
{
public:
D(int a) {cout << "D" << a << endl;}
};
class C : public B
{
public:
C():a(5){cout << "C" << endl;}
static D d;
A a;
}; D C::d(5);
对于这样的情况,不在初始化列表里添加d(int) 编译是可以通过的(相对的,没有static的时候是不能通过编译的)。
推测应该是因为C++对静态成员的内存管理方式和普通成员不同,它并不计入所在对象所占空间(即sizeof得到的结果),而是分配在全局内存上。
初始化需要放在类声明之外进行,关于静态对象初始化就是一个比较麻烦的问题了。不同编译单元的静态成员初始化顺序未知,而本编译单元好像是随着全局变量一起初始化还是在第一次调用的时候初始化...这里搞不清了,日常很少这么用。
总之这里进行初始化的时候比所有的类都要早进行,应该是被放进全局初始化列表了。