C++学习之路—继承与派生(二):派生类的构造函数与析构函数

(根据《C++程序设计》(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明)

由于基类的构造函数和析构函数是不能被继承的,所以在声明派生类时,需要我们自己定义派生类的构造函数和析构函数。

1  派生类的构造函数

在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还要考虑基类的数据成员的初始化。也就是说,

希望在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。

1.1  简单派生类的构造函数

简单的派生类只有一个基类,只有一级派生,且在派生类的数据成员中不包含基类的对象。通过具体的例子定义简单派生类的构造函数:

   1: class Student            //声明基类Student
   2: {
   3: public:
   4:     Student( int n , string nam , char s )        //定义基类的析构函数
   5:     {
   6:         num = n ;
   7:         name = nam ;
   8:         sex = s ;
   9:     }
  10:     ~Student(){}        //基类的析构函数
  11: protected:
  12:     int num ;
  13:     string name ;
  14:     char sex ;
  15: };
  16:  
  17: class Student1 : public Student            //声明公用派生类Student1
  18: {
  19: public:
  20:     Student1( int n , string nam , char s , int a , string ad ) : Student( n , nam ,s )
  21:     //定义派生类的构造函数
  22:      //在函数体内只对派生类新增的数据成员初始化
  23:     {
  24:         age = a ; 
  25:         addr = ad ;
  26:     }
  27:     ~Student1(){}        //派生类的析构函数
  28: private:
  29:     int age ;
  30:     string addr ; 
  31: };

请大家注意派生类构造函数首行的写法:

Student1( int n , string nam , char s , int a , string ad ) : Student( n , nam ,s )    
可以看出派生类构造函数一般形式为:
 
                      派生类构造函数名(总参数表) : 基类构造函数名(参数表)
                      {派生类中新增的数据成员初始化语句}
 
冒号":"前面的部分是派生类构造函数的主干,和普通类的构造函数相同,但是它的总参数表中包括基类构造函数所需的参数和对派生类新增的数据成员初始化所需的参数。冒号":"后面部分是要调用的基类构造函数及其参数。注意基类构造函数后面括号内的参数列表只有参数名而不包括参数类型(如 n , nam ,s ) ,因为这里不是定义基类的构造函数,而是调用基类的构造函数,因此,这些参数是实参而不是形参。若在main函数中定义stud1对象时指定了5个实参。它们按顺序传递给派生类构造函数Student1的形参( n , nam , s , a, d ) 。然后派生类的构造函数将前面3个( n , nam , s )传递给基类构造函数的形参。这样就同时完成了对从基类继承过来的数据成员和新增数据的初始化。
 
C++学习之路—继承与派生(二):派生类的构造函数与析构函数
   
  当然也可以将派生类的构造函数在类的外面定义,在类体中只写该函数的声明即可,这是声明不包括“基类构造函数名(参数表)”部分:
Student1( int n , string nam , char s , int a , string ad ) ; 
在类的外面定义构造函数:
Student1 :: Student1 (int n , string nam , char s , int a , string ad ): Student( n, nam ,s )
{
age = a ;
addr = ad ;
}

1.2   有子对象的派生类的构造函数

类中的数据成员除了标准型(int, char等)或系统提供的类型(如string)外,类中的数据成员还可以包括类的对象,例如以上例为基础,可以在声明一个类时包含这样的数据成员:

Student s1 ;         //Student是已声明的类名,s1是Student类的对象
   
其中s1是类中的内嵌对象,称为子对象,即对象中的对象。通过具体的例子来说明问题,以上例为基础,除了在派生类中增加数据成员age,addr外,还可以增加基类Student的对象monitor,也就是派生类中的子对象。程序在以上基础上略改如下:
 
   1: class Student            //声明基类,为简化,在上例的基础上去掉成员变量 char sex
   2: {
   3: public:
   4:     Student ( int n , string nam )
   5:     {
   6:         ...
   7:     }
   8:     ...
   9: };
  10:  
  11: class Student1 : public Student    //声明公用的派生类
  12: {
  13: public:
  14:     Student1 ( int n , string nam , int n1 , string nam1 , int a , string ad )
  15:         : Student ( n , nam ) , monitor ( n1 , nam1 ) //派生类的构造函数
  16:     {
  17:         age = a ;
  18:         addr = ad ;
  19:     }
  20: private:
  21:     Student monitor ;            //派生类中含有基类的对象
  22:     int age ; 
  23:     string addr ;
  24: };
 
赋值过程如下:
 
C++学习之路—继承与派生(二):派生类的构造函数与析构函数
 
由程序可以看出,子对象的初始化是在建立派生类时通过调用派生类的构造函数实现的。
归纳一下,定义派生类构造函数的一般形式为:
 
                            派生类构造函数名(总参数表):基类构造函数名(参数表),子对象名(参数表)
                                  {派生类中新增数据成员初始化语句}
 
     派生类构造函数的任务包括3个部分:
    (1)对基类数据成员初始化;
    (2)对子对象的数据成员进行初始化;
    (3)对派生类数据成员初始化。
     执行派生类构造函数的顺序是:
    (1)调用基类的构造函数,对基类数据成员初始化;
    (2)调用子对象构造函数,对子对象数据成员初始化;
    (3)再执行派生类构造函数本身,对派生数据成员初始化。
    注意:基类构造函数和子对象的次序可以是任意的,编译系统是根据相同的参数名来确定传递关系的。

1.3  多层派生时的构造函数

一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构。通过例子来说明:

   1: class Student                                  //声明基类
   2: {
   3: public:
   4:     Student ( int n , string nam )             //基类的构造函数
   5:         {
   6:             num = n ;
   7:             name = nam ;
   8:         }
   9: protected:                                     //基类的保护数据成员
  10:     int num ;
  11:     string name ;
  12: };
  13:  
  14: class Student1 : public Student                //声明公用派生类Student1
  15: {
  16:     Student1 ( int n , string nam , int a ): Student( n ,nam )     //派生类的构造函数
  17:     {
  18:     age = a ;                                  //函数体内只对新增的数据成员初始化
  19:     }
  20: private:                                      //派生类的私有数据成员
  21:     int age ;
  22: };
  23:  
  24: Student2 : public Student1                   //声明间接公用派生类Student2
  25: {
  26: public:
  27:     Student2(int n ,string nam , int a , int s ): Student1( n , nam ,a )     //间接派生类的构造函数
  28:     {
  29:         score = s ;                           //函数体内只对新增的数据成员初始化
  30:     }
  31: private:
  32:     int score ;                             //间接派生类新增数据成员
  33: };
注意派生类Student2构造函数的首部:
Student2( int n , string nam , int a , int s ):Student1( n , nam , a )
而不要写成:
Student2( int n , string nam , int a , int s ) : Student( n , nam ),Student1(n , nam , a )
 
不要列出每一层派生类的构造函数,只需列出其直接基类的构造函数。在声明Student2对象时,调用Student2的构造函数;再执行Student2构造函数时,先调用Student1的构造函数;再执行Student1的构造函数时,先调用基类的构造函数。

1.4  派生类构造函数的特殊形式

有两种特殊形式:

(1)当不需要对派生类新增的数据成员进行任何的初始化操作时,派生类构造函数的函数体可以为空。此时派生类构造函数的作用只是为了将参数传递给基类的构造函数和子对象,并在执行派生类的构造函数时调用基类的构造函数和子对象的构造函数。

(2)如果在基类中没有定义构造函数,或是定义了没有参数的构造函数,那么,在定义派生类的构造函数时可以不写基类的构造函数。在调用派生类的构造函数时,系统会自动首先调用基类的默认构造函数。

2    派生类的析构函数

析构函数的作用是在对象撤销之前,进行必要的清理工作。同样的,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。在派生类中可以根据需要定义自己的虚构函数,用来对派生类中所增加的数据成员进行清理工作。基类的清理工作仍然由基类的析构函数负责,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。

调用的顺序与构造函数相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。

上一篇:【C++学习之路】派生类的构造函数(二)


下一篇:C++学习笔记(6)----基类和派生类的构造函数和析构函数的执行顺序