C++ 多态之继承4-派生类的构造函数以及初始化

来源:互联网 发布:linux 不保存退出vi 编辑:程序博客网 时间:2024/05/16 10:31

之前的两篇文章,我们已经了解了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)    {    }};

初始化非派生类时,构造函数只需要关心它自己的成员。例如,

int main(){    Base cBase(5); // use Base(int) constructor     return 0;}
当cBase被实例化时,发生如下事件:

1.为cBase预留内存

2.合适的Base构造函数被调用

3.初始化列表对变量进行初始化

4.执行构造函数的函数体

5.从调用出返回

这个过程非常的直观。但是换成初始化一个派生类时,事情变得稍微复杂点:

int main(){    Derived cDerived(1.3); // use Derived(double) constructor     return 0;}
当cDerived被实例化时,发生了如下事情:

1.为cDerived预留足够的内存(包括Base部分以及Derived部分)

2.合适的派生类构造函数被调用

3.Base对象被首先构造通过合适的Base构造函数

4.初始化列表对变量进行初始化

5.执行构造函数的函数体

6.从调用处返回


两者的唯一区别是实例化派生类时,基类的构造函数被先调用。Base构造函数建立对象的基类部分,然后返回到派生类的构造函数,接着派生类构造函数被允许执行它的工作。

初始化基类成员

当创建一个派生类对象时,我们该如何同时初始化m_dValue(派生类Derived的成员变量)和m_nValue(基类Base的成员变量)?

新程序员可能打算通过下列方法才解决问题:

class Derived: public Base{public:    double m_dValue;     Derived(double dValue=0.0, int nValue=0)        <strong>// does not work</strong>        : m_dValue(dValue), m_nValue(nValue)    {    }};
这是个好的设想,点子是对的。我们要明确的给构造函数加上一个参数,否则C++将无法知道该给m_nValue初始化为何值。

然而,C++不允许在构造函数的初始化列表中对基类成员进行初始化。换句话说,构造函数的初始化列表只能初始化属于自己的成员变量。

那C++为什么要这样做呢?答案和const以及reference变量有关。设想下,如果m_nValue是一个const变量,将发生什么。因为const变量必须被初始化当它被创建的时候,基类构造函数必须给它赋值。然而,当基类构造函数执行完后,派生类的构造函数初始化列表会被执行。如果这样的话,每一个派生类的都有可能初始化这个变量并可能改变这个值。

所以上述的例子无法工作,因为m_nValue来自Base基类,而只有非继承的变量才能在初始化列表中被改变。

有些新程序员可能会使用如下方法来初始化基类成员变量:

class Derived: public Base{public:    double m_dValue;     Derived(double dValue=0.0, int nValue=0)        : m_dValue(dValue)    {        m_nValue = nValue;    }};
当m_nValue是非const变量时,上面的办法可行,但当m_nValue为const变量事,则行不通。因为m_nValue被赋值了2次:1次实在Base基类的成员初始化列表中,1次是在派生类的构造函数体内。

庆幸的是,C++赋予我们选择执行哪个基类构造函数的的能力,通过在派生类初始化成员列表中调用指定基类构造函数来实现:

class Derived: public Base{public:    double m_dValue;     Derived(double dValue=0.0, int nValue=0)        : Base(nValue), // Call Base(int) constructor with value nValue!            m_dValue(dValue)    {    }};
测试代码如下:

int main(){    Derived cDerived(1.3, 5); // use Derived(double) constructor     return 0;}
基类构造函数Base(int)将被用来初始化m_nValue为5,派生类构造函数将m_dValue初始化为1.3.

更详细的,发生了如下事情:

1.分配内存给cDerived

2.Derived(double, int) 构造函数被调用,dValue=1.3, nValue=5

3.编译器会检查我们是否要求执行指定基类构造函数。我们指定了!所以将调用 Base(int)参数值为5

4.基类构造函数初始化列表给m_nValue赋值5

5.执行基类构造函数体

6.基类构造函数返回

7.派生类构造函数初始化列表给m_dValue赋值1.3

8.执行派生类构造函数体

9.派生类构造函数返回

上面列出了9条,看起来挺复杂的,其实很简单。发生的所有事情就是派生类构造函数调用了指定的基类构造函数来初始化派生类对象的基类部分。因为m_nValue存在于对象的基类部分,基类构造函数是唯一能对其进行初始化的。

来看看另外一个实例:

#include <string>class Person{public:    std::string m_strName;    int m_nAge;    bool m_bIsMale;     std::string GetName() { return m_strName; }    int GetAge() { return m_nAge; }    bool IsMale() { return m_bIsMale; }     Person(std::string strName = "", int nAge = 0, bool bIsMale = false)        : m_strName(strName), m_nAge(nAge), m_bIsMale(bIsMale)    {    }}; // BaseballPlayer publicly inheriting Personclass BaseballPlayer : public Person{public:    double m_dBattingAverage;    int m_nHomeRuns;     BaseballPlayer(double dBattingAverage = 0.0, int nHomeRuns = 0)       : m_dBattingAverage(dBattingAverage), m_nHomeRuns(nHomeRuns)    {    }};
BaseballPlayer类只初始化了自己的成员,并没有调用指定的Person构造函数,这意味着每个BaseballPlayer将使用默认Person构造函数,它初始化name为空,age为0。这并不符合我们的意图,修改后的BaseballPlayer类如下:

// BaseballPlayer publicly inheriting Personclass BaseballPlayer : public Person{public:    double m_dBattingAverage;    int m_nHomeRuns;     BaseballPlayer(std::string strName = "", int nAge = 0, bool bIsMale = false,        double dBattingAverage = 0.0, int nHomeRuns = 0)        : Person(strName, nAge, bIsMale), // call Person(std::string, int, bool) to initialize these fields            m_dBattingAverage(dBattingAverage), m_nHomeRuns(nHomeRuns)    {    }};
测试代码如下:

int main(){    BaseballPlayer cPlayer("Pedro Cerrano", 32, true, 0.342, 42);     return 0;}
更详细的测试代码如下:

int main(){    BaseballPlayer cPlayer("Pedro Cerrano", 32, true, 0.342, 42);     using namespace std;    cout << cPlayer.m_strName << endl;    cout << cPlayer.m_nAge << endl;    cout << cPlayer.m_nHomeRuns;     return 0;}
结果:

Pedro Cerrano3242

继承链

实例:

#include <iostream>using namespace std; class A{public:    A(int nValue)    {        cout << "A: " << nValue << endl;    }}; class B: public A{public:    B(int nValue, double dValue)    : A(nValue)    {        cout << "B: " << dValue << endl;    }}; class C: public B{public:    C(int nValue, double dValue, char chValue)    : B(nValue, dValue)    {        cout << "C: " << chValue << endl;    }}; int main(){    C cClass(5, 4.3, 'R');     return 0;}
结果:

A: 5B: 4.3C: R
析构函数

当一个派生类被销毁时,析构函数的调用次序与构造函数完全相反。在上面的例子中,如果cClass被销毁,C的析构函数被最先调用,然后是B的,最后是A的。你可以自己写测试程序来验证。

1 0
原创粉丝点击