上篇文章中我介绍了一些c++中继承的基本知识,本文我们将更近的观察下当派生类被实例化的时候发生的一些事。
首先,我们引用一个新的类来说明一些重要知识点。
class Base
{
public:
int m_nValue;
Base(int nValue=0)
: m_nValue(nValue)
{
}
};
class Derived: public Base
{
public:
double m_dValue;
Derived(double dValue=0.0)
: m_dValue(dValue)
{
}
};
在上诉例子中,Derived类继承于Base类。因为Derived类从Base类继承了函数和变量,所以很容易想到Derived类有两部分组成:Derived部分,Base部分。
我们都看过大量类(非派生类)的实例化:
int main()
{
Base cBase;
return 0;
}
Base类是一个非派生类,因为它没有继承于任何其他类。C++为Base分配内存,然后调用Base的默认构造函数来进行初始化工作。
现在,我们来看下当实例化一个派生类会发生什么
int main()
{
Derived cDerived;
return 0;
}
如果我们运行上述的代码,与实例化一个非派生类相比没有任何差别。但是在背后,事情还是稍微有点区别的。之前提到,派生类有两部分:基类部分和派生类部分。当C++构造一个派生对象时,它会安静的做如下工作:开始做基类的构造。一旦完成后,就开始构造每个派生类的自己的部分。
下面的实例更能阐明这个过程:
#include <iostream>
using namespace std;
class Base
{
public:
int m_nValue;
Base(int nValue=0)
: m_nValue(nValue)
{
cout << "Base" << endl;
}
};
class Derived: public Base
{
public:
double m_dValue;
Derived(double dValue=0.0)
: m_dValue(dValue)
{
cout << "Derived" << endl;
}
};
int main()
{
cout << "Instantiating Base" << endl;
Base cBase;
cout << "Instantiating Derived" << endl;
Derived cDerived;
return 0;
}
程序的执行结果为:
Instantiating Base
Base
Instantiating Derived
Base
Derived
你能看到,当构造一个派生类,派生类的基类部分将被首先构造,这传达一个理念:逻辑上,没有父母的话,孩子是不能存在的。这也是一种实现的安全方式:子类可以使用父类的变量和函数,但是父类不知道子类的任何信息。实例化父类确保了这些变量已经被初始化,随后创建的派生类就可以使用它们。
继承链的构造次序
下面是一个稍微复杂点的例子:
class A
{
public:
A()
{
cout << "A" << endl;
}
};
class B: public A
{
public:
B()
{
cout << "B" << endl;
}
};
class C: public B
{
public:
C()
{
cout << "C" << endl;
}
};
class D: public C
{
public:
D()
{
cout << "D" << endl;
}
};
需要记住重要的一点:C++总是首先构造“first”(第一个)或者”most base“(最底层的基类),它会按次序遍历继承树然后构造依次的派生类。
执行下面的验证程序后,你可以清晰的看到这点:
int main()
{
cout << "Constructing A: " << endl;
A cA;
cout << "Constructing B: " << endl;
B cB;
cout << "Constructing C: " << endl;
C cC;
cout << "Constructing D: " << endl;
D cD;
}
执行结果:
Constructing A:
A
Constructing B:
A
B
Constructing C:
A
B
C
Constructing D:
A
B
C
D
总结
也许你也注意到本文中的例子都使用的是默认构造函数,下篇文章我们将更深入的看看构造函数在构建派生类时所扮演的角色。