第十五讲:派生类的构造函数和析构函数
* 理解:派生的构造函和析构函数
重点、难点:有子对象的派生类的构造函数;多重派生时的构造函数。
问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。
一、 简单的派生类的构造函数
一级派生类中定义构造函数一般形式为:
派生类构造函数名(总参数表列):基类构造函数名(参数表列)
{ 派生类中新增数据成员初始化语句 }
说明:总参数表列中包括基类构造函数所需的参数+对派生类新增的数据成员初始化所需的参数。
例5 简单的派生类的构造函数。
#include <iostream>
#include<string>
using namespace std;
class Student //声明基类
{ public: //公用部分
Student(int n,string nam,char s ) //基类构造函数
{ num=n;
name=nam;
sex=s; }
~Student( ) { }
protected: //保护部分
int num;
string name;
char sex ; };//基类析构函数
class Student1: public Student //声明公用派生类student
{ public:
Student1(int n,string nam,char s,int a,char ad ) : Student(n,nam,s) //派生类构造函数
{ age=a; //在函数体中只对派生类新增的数据成员初始化
addr=ad; }
void show( )
{ cout<<"num: "<<num<<endl;
cout<<"name: "<<name<<endl;
cout<<"sex: "<<sex<<endl;
cout<<"age: "<<age<<endl;
cout<<"address: "<<addr<<endl<<endl; }
~Student1( ) { } //派生类析构函数
private: //派生类的私有部分
int age;
string addr; };int main( )
{ Student1 stud1(10010,"Wang-li",'f',19,"115 Beijing Road,Shanghai");
Student1 stud2(10011,"Zhang-fun",'m',21,"213 Shanghai Road,Beijing");
stud1.show( ); //输出第一个学生的数据
stud2.show( ); //输出第二个学生的数据
return 0; }运行结果为
num:10010
name:Wang-li
Sex:f
address:115 BeijingRoad,Shanghai
num:10011
name:Zhang-fun
Sex:m
address:213 Shanghai Road,Beijing
注意:派生类构造函数首行的写法:
Studentl(int n.string nam,char s,int a,string ad):Student(n,nam,s)
然后,派生类构造函数将前面3个传递给基类构造函数的形参。见下图。
通过调用基类构造函数Student(n,nam,s)把3个值再传给基类构造函数的形参。
Student( n, nam. s )
↓ ↓ ↓
Student(int n,string nam,char s) //这是基类构造函数的首部
在上例中也可以将派生类构造函数在类外面定义,而在类体中只写该函数的声明:
Studentl(int n,string nam,char s,int a,string ad);
在类的外面定义派生类构造函数:
Studentl::Student1(int n,string nam,char s,int a,smng ad):Student(n,nam,s)
{age=a; addr=ad; }
说明:
1、在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数表列(即Student(n,nam,s))。只在定义函数时才将它列出。
2、调用基类构造函数时的实参是从派生类构造函数的总参数表中得到的,也可以不从派生类构造函数的总参数表中传递过来,而直接使用常量或全局变量。
例如:派生类构造函数5-行可以写成以下形式:
Student1( string nam,char s,int a,string ad):Student(10010,nam,s)
构造函数初始化表的例子:
Box::Box(int h,int w,int len):height(h),width(w),length(1en)
它也有一个冒号,在冒号后面的是对数据成员的初始化表。
实际上,派生类构造函数中对基类成员初始化,就是构造函数初始化表。不仅可以利用初始化表对构造函数的数据成员初始化,而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时实现这两种功能。
例如,
Studentl(int n,string nam,chars,int a,stung ad):Student(n,nam,s)
{ age=a; //在函数体中对派生类数据成员初始化
addr=ad;}
对age和addr的初始化也用初始化表处理,将构造函数改写为以下形式:
Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s),age(a),addr(ad){} 这样函数体为空,更显得简单和方便。
在建立一个对象时,执行构造函数的顺序是 :
①派生类构造函数先调用基类构造函数;
②再执行派生类构造函数本身(即派生类构造函数的函数体)。
二、有子对象的派生类的构造函数
类的数据成员中还可以包含类对象,这样类对象中内嵌子对象,我们称之为有子对象的类,下面我们讨论在派生类中含的对象的构造函数的构成。
定义派生类构造函数的一般形式为:
派生类构造函数名(总参数表列):基类构造函数名(参数表列),子对象名(参数表列)
{派生类中新增数据成员初始化语句}
执行派生类构造函数的顺序是:
①调用基类构造函数,对基类数据成员初始化;
②调用子对象构造函数,对子对象数据成员初始化;
③再执行派生类构造函数本身,对派生类数据成员初始化。
例6 包含子对象的派生类的构造函数。
为了简化程序以易于阅读,这里设基类Student的数据成员只有两个,即num和name
#include <iostream>
#include <string>
using namespace std;
class Student //声明基类
{ public: //公用部分
Student(int n,string nam) //基类构造函数
{ num=n;
name=nam; }
void display() //输出基类数据成员
{ cout<<"num:"<<num<<endl<<"name:"<<name<<endl;}
protected: //保护部分
int num;
string name; };
class Student1: public Student //用public继承方式声明派生类student
{ public:
Student1(int n,string nam,int n1,string nam1,int a,string ad) :Student(n,nam),monitor(n1,nam1) //派生类构造函数
{ age=a; //在此处只对派生类新增的数据成员初始化
addr=ad; }
void show( )
{ cout<<"This student is:"<<endl;
display(); //输出num和name
cout<<"age: "<<age<<endl;
cout<<"address: "<<addr<<endl<<endl; }
void show_monitor() //输出子对象的数据成员
{ cout<<endl<<"Class monitor is:"<<endl;
monitor.display(); }//调用基类成员函数
private: //派生类的私有数据
Student monitor; //定义子对象(班长)
int age;
string addr; };
int main( )
{ Student1 stud1(10010,"Wang-li",10001,"Li-sun",19,"115 Beijing Road,Shanghai");
stud1.show( ); //输出第一个学生的数据
stud1.show_monitor(); //输出子对象的数据
return 0; }运行时的输出如下:
This student is:
num:10010
name:Wang-li
age:19
address:115 BeringRoad,Shanghai
Class momtor is:
num:10001
name:Li-sun
派生类构造函数的任务应该包括3个部分:
(1)对基类数据成员初始化;
(2)对子对象数据成员初始化;
(3)对派生类数据成员初始化。
程序中派生类构造函数首部如下:
Student1(int n,string nam,int nl,stnng naml,int a,string ad):
Student(n,nam),monitor(n1,nam1)
三、多层派生时的构造函数
一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构。在上面叙述的基础上,不难写出在多级派生情况下派生类的构造函数。
基类和两个派生类的构造函数的写法:
基类的构造函数首部:
Student(int n,string nam)
派生类Studentl的构造函数首部:
Studentl(int n,string nam,int a):Student(n,nam.)
派生类Student,?,的构造函数首部:
Student2(int n,string nam,int 9,int s):Studentl(n,nam,a)
注意不要写成:
Student2(int n,string nam,int a,int s):Student(n,nam),Studentl(n,nanl,s)不要列出每一层派生类的构造函数,只须写出其上一层派生类(即它的直接基类)的构造函数即可。在声明Student2类对象时,调用Student2构造函数;在执行Student2构造函数时,先调用Studentl构造函数;在执行Studentl构造函数时,先调用基类Student构造函数。初始化的顺序是:
①先初始化基类的数据成员Bum和name。
②再初始化Student1的数据成员age。
③最后再初始化Studen2的数据成员score。
例7 多级派生情况下派生类的构造函数。
#include <iostream>
#include<string>
using namespace std;
class Student //声明基类
{ public: //公用部分
Student(int n, string nam ) //基类构造函数
{ num=n; name=nam; }
void display() //输出基类数据成员
{ cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl; }
protected: //保护部分
int num; //基类有两个数据成员
string name; };class Student1: public Student //声明公用派生类Student1
{ public:
Student1(int n,char nam[10],int a):Student(n,nam) //派生类构造函数
{age=a; } //在此处只对派生类新增的数据成员初始化
void show( ) //输出num,name和age
{ display(); //输出num和name
cout<<"age: "<<age<<endl; }
private: //派生类的私有数据
int age; };//增加一个数据成员
class Student2:public Student1 //声明间接公用派生类student2
{ public:
//下面是间接派生类构造函数
Student2(int n, string nam,int a,int s):Student1(n,nam,a)
{ score=s; }
void show_all() //输出全部数据成员
{ show(); //输出num和name
cout<<"score:"<<score<<endl; }//输出age
private:
int score; };//增加一个数据成员
int main( )
{ Student2 stud(10010,"Li",17,89);
stud.show_all( ); //输出学生的全部数据
return 0; }运行时的输出如下:
num:10010
name:Li
age:17
score:89
其派生关系如图13所示。
四、派生类构造函数的特殊形式
在使用派生类构造函数时,有以下特殊的形式:
1、当不需要对派生类新增的成员进行任何初始化操作时,派生类构造函数的函数体可以为空,即构造函数是空函数。
如例:6程序中派生类Studentl构造函数可以改写为
Studentl(int n,strin nam,int nl,strin naml):Student(n,nam),monitor(n1,naml){}
2、如果在基类中没有定义构造函数,或定义了没有参数的构造函数,那么,在定义派生类构造函数时可以不写基类构造函数。因为此时派生类构造函数没有向基类构造函数传递参数的任务。在调用派生类构造函数时,系统会自动首先调用基类的默认构造函数。
3、 如果在基类和子对象类型的声明中都没有定义带参数的构造函数,而且也不需对派生类自己的数据成员初始化,则可以不必显式地定义派生类构造函数。
4、 如果在基类或子对象类型的声明中定义了带参数的构造函数,那么就必须显式地定义派生类构造函数,并在派生类构造函数中写出基类或子对象类型的构造函数及其参数表。
5、如果在基类中既定义无参的构造函数,又定义了有参的构造函数(构造函数重载),则在定义派生类构造函数时,既可以包含基类构造函数及其参数,也可以不包含基类构造函数。在调用派生类构造函数时,根据构造函数的内容决定调用基类的有参的构造函数还是无参的构造函数。编程者可以根据派生类的需要决定采用哪一种方式。
五、派生类的析构函数
析构函数的作用是在对象撤销之前,进行必要的清理工作。当对象被删除时,系统会自动调用析构函数。析构函数比构造函数简单,没有类型,也没有参数。
在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。在派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
调用的顺序与构造函数正好相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。