C++面向对象的继承特性:派生类和基类的构造析构关系
1.派生类和基类的构造析构关系
派生类并不继承基类的构造和析构函数,只继承成员变量和普通成员方法。不继承意思是派生类中确实不包含基类的构造和析构函数,派生类自己有自己的构造和析构,规则和之前讲过的完全一样。研究构造和析构函数时,一定要注意默认规则。
尽管派生类不直接继承基类的构造和析构函数,有自己的构造和析构函数,但是派生类的构造函数一定会调用基类的某一个构造函数,析构函数也是一样。默认情况下派生类的默认构造函数调用的基类的默认构造函数,但是是派生类的任意构造函数也可以通过显式指定(语法是派生类构造函数:基类构造函数
)来调用基类的任意一个构造函数(通过参数匹配的方式,类似于函数重载)。这个可以通过在基类和派生类中都显式提供默认构造并添加打印信息,通过执行结果来验证,指定的语法是在基类的构造函数后面加上派生类的构造函数名及要传递的参数。
代码验证:
#include<iostream>
using namespace std;
class person
{
public:
string name;
int age;
//基类的默认构造函数
person(){
cout<<"person()."<<endl;};
//基类的自定义构造函数
person(string Name,int Age):name(Name),age(Age){
cout<<"person(string Name,int Age)."<<endl;};
//基类的默认析构函数
~person(){
cout<<"~person()."<<endl;};
};
class man:public person
{
public:
bool male;
//派生类的默认构造函数,并且指定调用基类的默认构造函数
man():person(){
cout<<"man():person()."<<endl;};
//派生类的自定义构造函数,并且指定调用基类的默认构造函数
man(bool male):person()
{
this->male=male;
cout<<"man(bool male):person()"<<endl;
};
//派生类的自定义构造函数,并且指定调用基类的自定义构造函数
man(string Name,int Age,bool Male):person(Name,Age)
{
this->male=Male;
cout<<"man(string name,int age,bool male):person(name,age)."<<endl;
};
~man(){
cout<<"~man()."<<endl;};
};
主程序1:
int main(int argc,char**argv)
{
man tom("tom",15,true);
return 0;
}
输出:
person(string Name,int Age).
man(string name,int age,bool male):person(name,age).
~man().
~person().
主程序2:
int main(int argc,char**argv)
{
man tom(true);
return 0;
}
输出:
person().
man(bool male):person()
~man().
~person().
通过代码执行结果看到的现象:派生类的构造函数执行之前,会先调用基类的构造函数,然后再调用自己的构造函数;而在派生类的析构函数之后,会先执行自己的析构函数,再执行基类的析构函数。
2.为什么派生类的构造(析构)必须调用基类的某个构造(析构)
首先要牢记构造函数的2大作用:初始化成员和分配动态内存。

派生类和基类各自有各自的构造函数和析构函数,这样可以各自管理各自的成员初始化,各自分配和释放各自所需的动态内存。对于man
这个类,它有3个成员变量,其中name
和age
是从person
继承而来的,male
是自己额外添加的。
继承的语言特性允许派生类调用基类的构造和析构函数,以管理派生类从基类继承而来的那些成员。派生类的构造和析构处理的永远是派生类自己的对象,只是派生类对象模板中有一部分是从基类继承而来的而已。
3.其他几个细节
①派生类构造函数可以直接全部写在派生类声明的
class
中,也可以只在class
中声明时只写派生类构造函数名和自己的参数列表,不写继承基类的构造函数名和参数列表,而在派生类的cpp
文件中再写满整个继承列表,这就是语法要求。
例如下面两种写法其实是等效的:
//写法1
//man.hpp
class man:public person
{
public:
bool male;
//派生类的自定义构造函数,并且指定调用派生类的自定义构造函数
man(string Name,int Age,bool Male):person(Name,Age)
{
this->male=Male;
};
};
//写法2:
//man.hpp
class man:public person
{
public:
bool male;
//声明时只写派生类构造函数名参数列表不写基类的构造函数名和参数列表
man(string Name,int Age,bool Male);
};
//man.cpp
//在派生类的cpp文件中实现该函数时再写满整个继承列表
man:man(string Name,int Age,bool Male):person(Name,Age)
{
this->male=Male;
};
②基类析构函数不用显式调用,直接写即可直接调用基类析构函数。
比如上面代码中man
的析构函数:
~man(){
};
//而不是~man():~person(){};
③构造函数的调用顺序是先基类再派生类,而析构函数是先派生类再基类,遵循栈规则。
④派生类的构造函数可以在调用基类构造函数同时,用逗号间隔同时调用初始化式来初始化派生类自己的成员。因此
:
后面跟着的有可能是基类的构造函数,也有可能是派生类自己的初始化式,这两个可以用逗号隔开。
例如下面两种写法其实是等效的:
//写法1
man(string Name,int Age,bool Male):person(Name,Age)
{
this->male=Male;
};
//写法2
man(string Name,int Age,bool Male):person(Name,Age),male(Male){
};
4.总结
派生类不直接继承基类的构造和析构函数,有自己的构造和析构函数,但是派生类的构造函数一定会调用基类的某一个构造函数。构造函数的调用顺序是先基类再派生类,而析构函数是先派生类再基类,遵循栈规则。
派生类做的三件事:
- 首先吸收基类成员:将基类中除构造和析构函数以外的所有成员全部吸收进入派生类中。
- 然后更改继承的成员:一是更改访问控制权限(根据继承类型还有成员在基类中的访问类型决定) ;二是同名覆盖(派生类中同名成员覆盖掉基类中)。
- 最后是添加派生类独有的成员。