C++继承机制下的构造函数

在前一章节中,我们介绍了构造函数的功能和用法,派生类同样有构造函数。当我们创建一个派生类对象的时候,基类构造函数将会被自动调用,用于初始化派生类从基类中继承过来的成员变量。而派生类中新增的成员变量则需要重新定义构造函数用于初始化了。

【例 1】
#include<iostream>
using namespace std;

class book
{
public:
    book();
    book(char* a, double p = 5.0);
    void setprice(double a);
    double getprice()const;
    void settitle(char* a);
    char * gettitle()const;
    void display();
private:
    double price;
    char * title;
};

class book_derived :public book
{
public:
    void display();
};

book::book(char* a, double p) 
{
    title = a;
    price = p;
}

book::book()
{
    title = "NoTitle";
    price = 0.0;
}

void book::setprice(double a)
{
    price = a;
}

double book::getprice()const
{
    return price;
}

void book::settitle(char* a)
{
    title = a;
}

char * book::gettitle()const
{
    return title;
}

void book::display()
{
    cout<<"The price of "<<title<<" is $"<<price<<endl;
}

void book_derived::display()
{
    cout<<"The price of "<<gettitle()<<" is $"<<getprice()<<endl;
}

int main()
{
    book_derived b;
    b.display();
    return 0;
}
在本例中定义了 book_derived 类,该类没有自身的成员变量,类中所有成员变量都继承自 book 类,类中成员函数仅有一个 display() 函数,该函数遮蔽了基类 book 中的 display() 函数。在主函数中定义派生类的对象 b,之后调用派生类的 display() 函数,程序运行结果为The price of NoTitle is $0

从例 1 中我们不难看出,派生类在创建对象时会自动调用基类构造函数。如果像例 1 这种情况,派生类中没有新增成员变量,基类的构造函数功能已经满足派生类创建对象初始化需要,则派生类则无需重新自定义一个构造函数,直接调用基类构造函数即可。如果派生类中新增了成员变量,这时如果需要在创建对象时就进行初始化则需要自己设计一个构造函数,具体见例 2。

【例 2】
#include<iostream>
using namespace std;

enum language{none, cpp, java, python, javascript, php, ruby};

class book
{
public:
    book();
    book(char* a, double p = 5.0);
    void setprice(double a);
    double getprice()const;
    void settitle(char* a);
    char * gettitle()const;
    void display();
private:
    double price;
    char * title;
};

class codingbook: public book
{
public :
    codingbook():book(){lang = none;}
    codingbook(language lang, char * t, double p);
    void setlang(language lang);
    language getlang(){return lang;}
    void display();
private:
    language lang;
};

book::book(char* a, double p) 
{
    title = a;
    price = p;
}

book::book()
{
    title = "NoTitle";
    price = 0.0;
}

void book::setprice(double a)
{
    price = a;
}

double book::getprice()const
{
    return price;
}

void book::settitle(char* a)
{
    title = a;
}

char * book::gettitle()const
{
    return title;
}

void book::display()
{
    cout<<"The price of "<<title<<" is $"<<price<<endl;
}

void codingbook::setlang(language lang)
{
    this->lang = lang;
}

codingbook::codingbook(language lang, char * t, double p):book(t,p)
{
    this->lang = lang;
}

void codingbook::display()
{
    book::display();
    cout<<"The language is "<<lang<<endl;   
}

int main()
{
    codingbook cpp;
    cpp.display();
    codingbook java(java, "Thinking in Java", 59.9);
    java.display();
    return 0;
}
codingbook 类是 book 类的派生类。类中新增了一个 language 成员变量,为此必须重新设计新的构造函数。本例中,book 类中有一个默认构造函数和一个带参数的构造函数,codingbook 类中同样声明了两个构造函数,一个默认构造函数和一个带参数的构造函数,默认构造函数显式调用基类的默认构造函数,带参构造函数显式调用基类的带参构造函数。

主函数中定义了 codingbook 类的对象 cpp,该对象调用 codingbook 类的默认构造函数,默认构造函数先会调用基类的默认构造函数将 title 和 price 进行初始化,之后才会执行自身函数体中的内容。

之后又定义了 codingbook 类对象 java,该对象在定义时后面接有三个参数,很明显是需要调用 codingbook 类的带参构造函数,其中 java 参数用于初始化 lang 成员变量,而后两个参数则用于初始化从基类继承过来的 title 和 price 两个成员变量,当然初始化顺序依然是先调用基类的带参构造函数初始化 title 和 price,然后再执行自身函数体中的初始化代码初始化 lang 成员变量。

最后程序运行结果如下:

The price of NoTitle is $0
The language is 0
The price of Thinking in Java is $59.9
The language is 2

在这个例子中,language 没有显示为 java 或者 cpp,只显示为 0 和 2,这个熟悉枚举类型的应该都清楚,枚举类型在本例中其实就是从 0 开始的 int 类型。

从例 2 中,我们可以很清楚的看到,创建派生类对象时,先由派生类构造函数调用基类构造函数,然后再执行派生类构造函数函数体中的内容,也就是说先执行基类构造函数,然后再去执行派生类构造函数。

如果继承关系有好几层的话,例如 A 类派生出 B 类,B 类派生出 C 类,则创建 C 类对象时,构造函数的执行顺序则为 A 的构造函数,其次是 B 的构造函数,最后是 C 类的构造函数。构造函数的调用顺序是按照继承的层次,自顶向下,从基类再到派生类的。

【例 3】
#include<iostream>
using namespace std;

class base
{
public:
    base(){x = 0; y = 0; cout<<"base default constructor"<<endl;}
    base(int a, int b){x = a; y = b; cout<<"base constructor"<<endl;}
private:
    int x;
    int y;
};

class derived: public base
{
public:
    derived():base(){z = 0; cout<<"derived default constructor"<<endl;}
    derived(int a, int b, int c):base(a,b){z = c; cout<<"derived constructor"<<endl;}
private:
    int z;
};

int main()
{
    derived A;
    derived B(1,2,3);
    return 0;
}
本例中定义了两个类,基类 base 中定义了一个默认构造函数和一个带参数的构造函数。派生类 derived 中同样定义了两个构造函数,这两个构造函数一个为默认构造函数,一个为带参构造函数。派生类中的默认构造函数显式调用基类默认构造函数,带参构造函数显式调用基类的带参构造函数。我们在主函数中定义了派生类的两个对象,这两个对象一个是调用派生类的默认构造函数,另一个调用派生类的带参构造函数。

这个程序运行结果如下:

base default constructor
derived default constructor
base constructor
derived constructor

从运行结果可以看出,创建对象时先是执行基类的构造函数,然后再是执行派生类的构造函数。构造函数执行顺序是按照继承顺序自顶向下执行。