1.构造函数
定义了派生类之后,要使用派生类就需要声明该类的对象。对象在使用之前必须初始化。
派生类的成员对象是由所有基类的成员对象共同组成的。因此构造派生类函数的对象时,就要对基类的成员对象和新增的成员对象进行初始化。
基类的构造函数并没有继承下来,要完成这些工作,就必须给派生类添加新的构造函数。派生类对于基类的很多成员对象是不可以直接访问的,因此要完成对基类成员对象初始化工作,需要通过调用基类的构造函数。
派生类的构造函数需要以合适的初值作为参数,其中一些参数要传递给基类的构造函数,用于初始化相应的成员,另一些参数要用于对派生类新增的成员对象进行初始化。
在构造派生类对象时,会首先调用基类的构造函数,来初始化它们的数据成员,然后按照构造函数初始化列表中指定的方式初始化派生类新增的成员对象,最后才执行派生类构造函数的函数体。
(1)派生类构造函数的一般语法形式
派生类名::派生类名(参数表):基类名1(基类1初始化参数表),...,基类名n(基类n初始化参数表),成员对象名1(成员对象1初始化参数表),...,成员对象名m(成员对象m初始化参数表)
{
派生类构造函数的其他初始化操作;
}
这里派生类的构造函数名于派生类名相同。在构造函数的参数表中,需要给出初始化基类数据和新增成员对象所需要的参数。在参数表之后,列出需要使用参数进行初始化的基类名和成员对象名及各自的初始化参数表,各项之间用逗号隔开。
当一个类同时有多个基类时,对于所有需要给予参数进行初始化的基类,都要显式给出基类名和参数表。对于使用默认构造函数的基类,可以不给出类名。同样,对于成员对象,如果是使用默认构造函数,也不需要写出对象名和参数表。
(2)声明派生类构造函数
如果对基类初始化时,需要调用基类的带有形参表的构造函数时,派生类就必须声明构造函数,提供一个将参数传递给基类构造函数的途径,保证基类进行初始化时能获得必要的数据。
如果不需要调用基类的带参数的构造函数,也不需要调用新增的成员对象的带参的构造函数,派生类也可以不用声明构造函数,全部采用默认的构造函数,这时新增成员的初始化工作可以用其他公有成员函数来完成。当派生类没有显式构造函数时,系统会隐含生成一个默认构造函数,该函数会使用基类的默认构造函数对继承自基类的数据初始化,并且调用派生类的类类型的成员对象的默认构造函数,对这些成员对象初始化。
派生类构造函数执行的一般顺序如下:
①调用基类的构造函数,调用顺序按照它们被继承时声明的顺序(从左到右)。
②对派生类新增的成员对象初始化,初始化顺序按照它在类中声明的顺序。
③执行派生类的构造函数体中的内容。
【注意】构造函数初始化列表中基类名、对象名之间的次序无关紧要,它们各自出现的顺序可以是任意的,无论它们的顺序怎样安排,基类构造函数的调用和各个成员对象的初始化顺序都是确定的。
【例】派生类的构造函数举例(多继承,含有内嵌对象),有3个基类B1,B2和B3。其中B3只有一个默认构造函数,其余两个基类的成员只有一个带有参数的构造函数。类D由这三个基类,结果公有继承派生而来。派生类新增了3个私有对象成员,分别是B1,B2和B3类的对象。
#include<iostream>
using namespace std;
class B1//基类B1,构造函数有参数
{
public:
B1(int i)
{
cout << "构造B1对象" << i << endl;
}
};
class B2//基类B2,构造函数有参数
{
public:
B2(int j)
{
cout << "构造B2对象" << j << endl;
}
};
class B3//基类B3,构造函数无参数
{
public:
B3()
{
cout << "构造B3对象" << endl;
}
};
class D:public B2,public B1,public B3//派生类D,注意基类名的顺序
{
public://派生类的公有成员
D(int a,int b,int c,int d):B1(a),b2(d),b1(c),B2(b){}//注意类名的个数与顺序,注意成员对象名的个数与顺序
private://派生类的私有成员对象
B1 b1;
B2 b2;
B3 b3;
};
int main()
{
D d(1, 2, 3, 4);
return 0;
}
运行结果:
结果分析:
因为**基类和内嵌对象成员都具有默认构造函数,所以派生类中需要显式声明一个构造函数用来初始化基类及内嵌对象成员。**派生类的构造函数定义为:
D(int a,int b,int c,int d):B1(a),b2(d),b1(c),B2(b){}
构造函数的参数表中给出了基类及内嵌对象成员所需要的全部参数,在冒号之后,分别列出各个基类及内嵌对象名和各自的参数。需要注意两个问题:一是,这里并没有列出全部基类和成员对象,由于B3类只有默认构造函数,不需要给它传递参数,因此,基类B3以及B3类成员对象b3就不必列出。二是,在派生类的构造函数中基类名和成员对象名的顺序是随意的。 这个派生类构造函数的函数体为空,只起到了传递参数和调用基类及内嵌对象构造函数的作用。
程序中主函数中只声明了一个派生类D的对象d,生成对象d时调用了派生类的构造函数。D类派生类构造函数的执行情况,应该是先调用基类的构造函数,然后调用内嵌成员对象的构造函数。基类构造函数的调用顺序是按照派生类定义时的顺序,因此应该是先B2,再B1,最后B3;而内嵌对象的构造函数调用顺序应该是按照成员在类中的声明顺序,应该是先B1,再B2,最后B3。
派生类构造函数的定义中,并没有显式列出基类B3和B3类的对象b3,这时系统就会自动调用该类的默认构造函数。如果一个基类同时声明了默认构造函数和带参数的构造函数,那么在派生类构造函数声明中,既可以显式列出基类名和相应的参数,也可以不列出。
(3)派生类构造函数的特点
①基类和内嵌对象成员都具有非默认构造函数时,派生类中需要显式声明一个构造函数用来初始化基类及内嵌对象成员。
②如果一个基类中只有默认构造函数,那么在派生类的构造函数中不需要列出这个基类和这个基类类型的成员对象,不需要它们传参。系统就会自动调用该类的默认构造函数。
③在派生类的构造函数中基类名和成员对象名的顺序是随意的。
④如果派生类构造函数的函数体为空,那么该派生类的构造函数只起到了传递参数和调用基类及内嵌对象构造函数的作用。
⑤类派生类构造函数的执行情况,应该是先调用基类的构造函数,然后调用内嵌成员对象的构造函数。基类构造函数的调用顺序是按照派生类定义时的顺序,内嵌对象的构造函数调用顺序应该是按照成员在类中的声明顺序。
⑥如果一个基类同时声明了默认构造函数和带参数的构造函数,那么在派生类构造函数声明中,既可以显式列出基类名和相应的参数,也可以不列出。