C++:派生类的构造函数和析构函数
4.2 派生类的构造函数和析构函数
4.2.1 派生类构造函数和析构函数的执行顺序
通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;
当撤销派生类对象时,则先执行派生类的派生类的析构函数,随后再执行基类的析构函数。
//例4.5 派生类的构造函数和析构函的执行顺序
#include<iostream> using namespace std; class Base{ //声明基类Base public: Base() { cout<<"Constructor Base Class.."<<endl; } ~Base() { cout<<"Destructor Base Class.."<<endl; } }; class Derived: public Base{ //声明基类Base的公有派生类Derived public: Derived() { cout<<"Constructor Derived Class.."<<endl; } ~Derived() { cout<<"Destructor Derived Class.."<<endl; } }; int main() { Derived obj; return 0; } /* 说明:从程序运行的结果可以看出:构造函数的调用严格按照先调用基类的构造函数,后调用派生类的 构造函数的顺序执行。析构函数的调用顺序与构造函数的调用顺序正好相反,先调用派生类的析 构函数,再调用基类的析构函数。 */
4.2.2 派生类构造函数和析构函数的构造规则
1. 简单的派生类的构造函数
当基类的构造函数没有参数,或没有显式定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。例4.5的程序就是由于基类的构造函数没有参数,所以派生类没有向基类传递参数。
派生类不能继承基类中构造函数和析构函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
在C++中,派生类构造函数的一般格式为:
派生类名(参数总表):基类名(参数表)
{
派生类新增数据成员的初始化语句
}
其中,基类构造函数的参数,通常来源于派生类构造函数的参数总表,也可以用常数值。
//例4.6 当基类含有带参数的构造函数,派生类构造函数的构造方法。
#include<iostream> #include<string> using namespace std; class Student{ //声明基类Student public: Student(int number1,string name1,float score1) //基类的构造函数 { number = number1; name = name1; score = score1; } void print() { cout<<"number:"<<number<<endl; cout<<"name:"<<name<<endl; cout<<"score:"<<score<<endl; } protected: int number; string name; float score; }; class UStudent:public Student{ //基类Student的公有派生类UStudent public: UStudent(int number1,string name1,float score1,string major1) :Student(22117,"张三",100/*number1,name1,score1*/) { //定义派生类的构造函数时,缀上要调用的基类的构造函数及其参数 major = major1; //参数可以是派生类构造函数总参数表中的参数,也可以是常量和全局变量。 } void print1() { print(); cout<<"major:"<<major<<endl; } private: string major; }; int main() { UStudent stu(22116,"张志",95,"信息安全"); //stu.print(); stu.print1(); return 0; }
注意事项:
请注意派生类构造函数首行的写法:
UStudent(int number1,string name1,float score1,string major1):Student(number1,name1,score1)
冒号前面的部分是派生类构造函数的主干,它和以前介绍过的构造函数的形式相同,但它的总参数表中包括基类构造函数所需要的参数和对派生类新增的数据成员初始化所需要的参数。冒号后面的部分是要调用的基类构造函数及其参数。
从上面列出的派生类UStudent构造函数首行中可以看到,派生类构造函数名(UStudent)后面的总参数表中包括参数的类型和参数名(如 int number1),而基类构造函数的参数表中只有参数名而不包括参数类型(如 number1),因为在这里不是再定义基类构造函数,而是调用基类构造函数,因此这些参数是实参而不是形参。它们可以是派生类构造函数总参数表中的参数,也可以是常量和全局变量。
说明:1、可以将派生类构造函数定义在类的外部,而在类体内只写该函数的声明。如在例4.6的派生类中可以只写构造函数的声明:
UStudent(int number1,string name1,float score1,string major1)
而在类外定义派生类的构造函数:
UStudent::UStudent(int number1,string name1,float score1,string major1):Student(number1,name1,score1)
{
major = major1;
}
请注意:在类中声明派生类构造函数时,不包括基类构造函数及其参数表(Student(number1,name1,score1),只在
类外定义构造函数时才将它列出。
2、若基类使用默认构造函数或不带参数的构造函数,则在派生类中定义构造函数时, "基类构造函数名(参数表)"可以省略。
如在例4.5的程序中,由于基类的构造函数没有参数,所以在派生类中定义构造函数时,不要缀上":Base()",也即
不必写成:
Deirved:Base()
{
cout<<"Constructor derived class"<<endl;
}
3、当基类构造函数不带参数时,派生类不一定需要定义构造函数,然而当基类的构造函数哪怕只带有一个参数,它所有的派生类必须定义构造函数,甚至所定义的派生类构造函数的函数体可能为空,仅仅起到参数的传递作用。
例如,在下面的程序中,派生类Deruved就不使用参数n,n只是被传递给基类构造函数Base。
class Base{ public: Base(int n) { i = n; } void show() { cout<<"i="<<i<<endl; } private: int i; }; class Derived public Base{ public: Derived(int n):Base(n) //构造函数参数表中只有一个参数, { } //传递给了要调用的基类构造函数Base }; //派生类构造函数体为空
2.派生类的析构函数
在派生类中可以根据需要定义自己的析构函数,用来对派生类中的所增加的成员进行清理工作。基类的清理工作仍然有基类的析构函数负责。由于析构函数是不带参数的,在派生类中是否要定义析构函数与它所属基类的析构函数无关。在执行派生类的析构函数时,系统会自动调用基类的析构函数,对基类的对象进行清理。析构函数的调用顺序与构造函数正好相反,先执行派生类的析构函数,再执行基类的析构函数。
//例4.7 简单派生类的构造函数和析构函数的执行顺序。
#include<iostream> using namespace std; class A{ //声明基类A public: A() { cout<<"Constuctor class A"<<endl; //基类的构造函数 } ~A() { cout<<"Destuctor class A"<<endl; //基类的构造函数 } }; class B:public A{ public: B() { cout<<"Constuctor class B"<<endl; //派生类的构造函数 } ~B() { cout<<"Destuctor class B"<<endl; //派生类的构造函数 } }; int main() { B b; return 0; } /* 运行结果如下: Constuctor class A Constuctor class B Destuctor class B Destuctor class A */
3.含有对象成员(子对象)的派生类的构造函数
当派生类中含有内嵌的对象成员(也称子对象)时,其构造函数的一般格式为:
派生类(参数总表):基类名(参数表 0):对象成员1(参数表 1),......,对象成员n(参数表 n)
{
派生类新增成员的初始化语句
}
在定义派生类对象时,构造函数的执行顺序如下:
(1)、调用基类的构造函数,对基类数据成员初始化;
(2)、调用内嵌对象成员的构造函数,对内嵌对象成员的数据成员进行初始化;
(3)、调用派生类的构造函数体,对派生类数据成员进行初始化。
撤销对象时,析构函数的调用顺序与构造函数的调用顺序相反,首先执行派生类的析构函数,
再执行内嵌对象成员的析构函数,最后执行基类的析构函数。
//例4.8 含有对象成员的派生类构造函数和析构函数的执行顺序
#include<iostream> using namespace std; class Base{ //声明基类Base public: Base(int i) //基类的构造函数 { x = i; cout<<"Constructor base class"<<endl; } ~Base() //基类的析构函数 { cout<<"Destructor base class"<<endl; } void show() { cout<<"x = "<<x<<endl; } private: int x; }; class Derived :public Base{ //声明基类Base的公有派生类Derived public: Derived(int i):Base(i):d(i) //派生类的构造函数,缀上要调用的基类构造函数和对象成员的构造函数 { cout<<"Constructor Derived class"<<endl; } ~Derived() { cout<<"Destructor Derived class"<<endl; } private: Base d; }; int main() { Derived obj(5); obj.show(); return 0; } /* 运行结果是:Constructor base class Constructor base class Constructor Derived class x = 5 Destructor Derived class Destructor base class Destructor base class */
说明:
(1)在派生类中含有多个内嵌对象成员时,调用内嵌对象成员的构造函数顺序由它们在类中声明的顺序确定。
(2)如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员。依次上溯。
//例4.9 含有多个对象成员的派生类构造函数的执行顺序。
#include<iostream> #include<string> using namespace std; class Student{ //声明基类Base public: Student(int number1,string name1,float score1) { number = number1; name = name1; score = score1; } void print() { cout<<"学号:"<<number<<endl; cout<<"姓名:"<<name<<endl; cout<<"成绩:"<<score<<endl; } protected: int number; string name; float score; }; class UStudent:public Student{ //声明基类Student的公有派生类UStudent public: UStudent(int number1,string name1,float score1,int number2,string name2,float score2, int number3,string name3,float score3,string major1):Student(number1,name1,score1): auditor2(number3,name3,score3),auditor1(number2,name2,score2) { major = major1; } void print() { cout<<"正式生是:"<<endl; Student::print(); cout<<"专业是:"<<major<<endl; } void print_auditor1() { cout<<"旁听生是:"<<endl; auditor1.print(); } void print_auditor2() { cout<<"旁听生是:"<<endl; auditor2.print(); } private: string major; //专业 Student auditor1; //定义对象成员1(旁听生) Student auditor2; //定义对象成员2(旁听生) }; int main() { UStudent stu(2001,"张志",95,3001,"王全安",66,3002,"李倩倩",50,"信息安全"); stu.print(); stu.print_auditor1(); stu.print_auditor2(); return 0; }