派生类通过公有继承、保护继承和私有继承控制对基类成员的访问属性在鸡啄米编程入门的前两节讲完了。今天讲讲派生类的构造函数。
鸡啄米前面说过,基类的构造函数和析构函数派生类是不能继承的。如果派生类需要对新成员初始化或者进行特定的清理工作,就需要自己定义构造函数和析构函数了。从基类继承的成员的初始化仍可通过基类的构造函数来完成。
我们使用派生类的对象以前需要对它的数据成员进行初始化赋值。派生类的数据成员包括从基类继承来的数据成员和派生类新增的数据成员,还可能包括其他类的对象作为其数据成员,包括其他类的对象时实际上还间接包括了这些对象的数据成员。那么我们对派生类初始化时就需要对基类的数据成员、派生类新增数据成员和内嵌的其他类对象的数据成员进行初始化。由于不能继承基类的构造函数,派生类就必须增加自己的构造函数。派生类的构造函数需要做的工作有,使用传递给派生类的参数,调用基类的构造函数和内嵌对象成员的构造函数来初始化它们的数据成员,再添加新语句初始化派生类新成员。派生类构造函数的语法形式为:
派生类名::派生类名(参数表):基类名1(参数表1),...基类名m(参数名m),
内嵌对象名(内嵌对象参数表1),...,内嵌对象名n(内嵌对象参数表n)
{
初始化派生类新成员的语句;
}
鸡啄米先讲下上面的语法形式吧。派生类的构造函数名同样也要与类名相同。构造函数参数表要给出初始化基类数据成员、新增数据成员和内嵌对象的数据成员的所有参数。在给出所有这些参数以后就要像上面那样指明所有要初始化的基类名及其参数表,还有内嵌对象名及其参数表。各个基类名和内嵌对象名可以以任何的顺序排列。
可能大家觉得此语法形式太过复杂,但其实构造函数也不是必须写这么复杂。假设一个类继承自多个基类,即为多继承时,对于那些构造函数有参数的基类就必须显式给出基类名及其参数表,而对于那些使用默认构造函数的基类没有必要给出基类名及其参数表,同理内嵌对象的构造函数若有参数也必须给出内嵌对象名及其参数表,若使用默认构造函数也没有必要给出内嵌对象名及其参数表。试想,如果派生类只有一个基类,而且有默认构造函数,没有内嵌对象或者可以使用其他公有函数成员初始化内嵌对象,那么后面的基类名和内嵌对象名就都不需要了,是不是形式就很简单了?其实我们最常用的比这种最简单的情况复杂不了多少。
鸡啄米再次强调,基类的构造函数若有参数,则派生类必须定义构造函数,将传入的参数再传递给基类的构造函数,对基类进行初始化。若基类没有定义构造函数,则派生类也可以不定义构造函数,都使用默认构造函数,对于派生类的新增数据成员可以通过其他的公有函数成员来初始化。而如果基类同时定义了默认构造函数和带参数的构造函数,那么在派生类的构造函数中可以给出基类名及其参数表,也可以不显式给出。
构造派生类的对象调用构造函数时的处理顺序是:1.首先调用基类的构造函数,若有多个基类,调用顺序按照它们在派生类声明时从左到右出现的顺序;2.如果有内嵌对象成员,则调用内嵌对象成员的构造函数,若为多个内嵌对象,则按照它们在派生类中声明的顺序调用,如果无内嵌对象则跳过这一步;3.调用派生类构造函数中的语句。
这里需要说明的是,基类和内嵌对象成员的构造函数的调用顺序和它们在派生类构造函数中出现的顺序无关。
鸡啄米这里给一个例子说明下派生类的构造函数。
派生类Child有三个基类Base1、Base2和Base3,继承方式都是公有继承,Base1只有一个默认构造函数,Base2类和Base3类都只有一个有参数的构造函数,Child类有三个内嵌对象成员,分别是Base1类、Base2类和Base3类的对象。
#include <iostream> using namespace std; class Base1 // 基类Base1,只有默认构造函数 { public: Base1() { cout<<"Base1 construct"<<endl; } }; class Base2 // 基类Base2,只有带参数的构造函数 { public: Base2(int x) { cout<<"Base2 construct "<<x<<endl; } }; class Base3 // 基类Base3,只有带参数的构造函数 { public: Base3(int y) { cout<<"Base3 construct "<<y<<endl; } }; class Child : public Base2, public Base1, public Base3 // 派生类Child { public: Child(int i,int j,int k,int m):Base2(i),b3(j),b2(k),Base3(m) { } private: // 派生类的内嵌对象成员 Base1 b1; Base2 b2; Base3 b3; }; int main() { Child child(3,4,5,6); return 0; }
由于上面的类Base2和类Base3都有带参数的构造函数,所以派生类Child必须定义带参数的构造函数。Child类构造函数的参数表给出了基类Base2和Base3、内嵌对象成员b2和b3需要的所有参数。因为基类Base1只有默认构造函数,没有参数,所以类Base1和Base1类的对象成员b1都不需要列在派生类的构造函数中。Child类的构造函数中基类Base2和Base3以及对象成员b2和b3可以按照任意的顺序排列。
主函数main中声明了派生类Child的对象child,构造对象child时会调用Child类的构造函数,执行时先调用基类的构造函数,调用顺序按照它们在派生类声明时从左到右出现的顺序,即按照先Base2再Base1后Base3的顺序,调用基类构造函数后就调用内嵌对象成员的构造函数,按照它们在派生类中声明的顺序调用,即按照先b1再b2后b3的顺序。基类Base1和Base1类的对象成员b1没有显式列出,执行调用Base1类的默认构造函数。
程序运行结果为:
Base2 construct 3
Base1 construct
Base3 construct 6
Base1 construct
Base2 construct 5
Base3 construct 4
此运行结果也完全验证了鸡啄米上面的分析。
关于派生类的构造函数鸡啄米就先讲到这了,下一节会讲派生类的析构函数。有问题欢迎到鸡啄米博客讨论。