引入继承的目的:①派生类继承了基类的成员,实现了原有代码的重用。②实现代码的扩充,只有在派生类中通过添加新的成员,加入新的功能,类的派生才有实际意义。
基类的构造函数和析构函数不能被继承,在派生类中,如果对派生类新增的成员进行初始化,就需要加入派生类的构造函数,同时,对所有从基类继承下来的成员的初始化工作,还是由基类的构造函数完成
1、派生类构造函数和析构函数的执行顺序
通常,当创建派生类对象时,首先执行基类的构造函数,随后执行派生类的构造函数;当撤销派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。析构函数的调用顺序与构造函数的调用顺序正好相反。
2、派生类构造函数和析构函数的构造规则
(1)简单的派生类的构造函数
在C++中,派生类构造函数的一般格式为:
派生类名(参数总表):基类名(参数表){
派生类新增数据成员的初始化语句
}
- 其中,基类构造函数的参数,通常来源于派生类构造函数的参数总表,也可以用常数值,因为这里是调用基类构造函数,所以这些参数是实参不是形参,它们可以是派生类构造函数总参数表中的参数,也可以是常量和全局变量。
- 当基类的构造函数没有参数,或没有显式定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。
- 派生类不能继承基类中的构造函数和析构函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
例 1:当基类含有带参数的构造函数,派生类构造函数的构造方法。
#include<iostream>
#include<string>
using namespace std;
class Student{ //声明类 Student
protected:
int number; //学号
string name; //姓名
float score; //成绩
public:
Student(int num1,string name1,float score1){ // 基类构造函数
number=num1;
name=name1;
score=score1;
}
void print(){
cout<<"number:"<<number<<endl;
cout<<"name:"<<name<<endl;
cout<<"score:"<<score<<endl;
}
};
class Ustudent:public Student{ //声明公有派生类 Student
private:
string major;
public:
Ustudent(int num1,string name1,float score1,string major1):Student(num1,name1,score1){
major=major1;
}
void print1(){
print();
cout<<"major:"<<major<<endl;
}
};
int main(){
Ustudent stu(12345,"叮叮",100,"数学");
stu.print1();
return 0;
}
说明:
① 在类的外部定义派生类的构造函数,在类体内只写构造函数的声明。
如,例 1 可以写成:
Ustudent(int num1,string name1,float score1,string major1);
而在类的外部定义派生类的构造函数:
Ustudent(int num1,string name1,float score1,string major1):Student(num1,name1,score1){
major=major1;
}
② 若基类使用默认构造函数或不带参数的构造函数,则在派生类中定义构造函数时可略去“:基类构造函数名(参数表)”,此时若派生类不需要构造函数,则可不定义派生类构造函数。
③ 当基类构造函数不带参数时,派生类不一定需要定义构造函数,而当基类的构造函数哪怕只带有一个参数,它所有的派生类都必须定义构造函数,甚至所定义的派生类构造函数的函数体可能为空,仅仅起参数的传递作用。
(2) 派生类的析构函数
在派生类中可以根据需要定义自己的析构函数,用来对派生类中的所增加的成员进行清理工作,基类的清理工作仍然由基类的析构函数负责。
在执行派生类的析构函数时,系统会自动调用基类的析构函数,对基类的对象进行清理。析构函数的调用顺序与构造函数正好相反,先执行派生类的析构函数,再执行基类的析构函数。
(3)含有对象成员(子对象)的派生类的构造函数
当派生类中含有内嵌的对象成员(也称子对象时),其构造函数的一般形式为:
派生类名(参数总表):基类名(参数表 0), 对象成员名 1(参数表 1), ···, 对象成员名 n(参数表 n)
{
派生类新增成员的初始化语句
}
可参考类的组合:https://blog.csdn.net/aaqian1/article/details/84494415
在定义派生类对象时,构造函数的执行顺序如下:
① 调用基类的构造函数,对基类数据成员初始化;
② 调用内嵌对象成员的构造函数,对内嵌对象成员的数据成员初始化;
③ 执行派生类的构造函数体,对派生类数据成员初始化。
- 撤销对象时,析构函数的调用顺序与构造函数的调用顺序正好相反。
- 当在派生类中含有多个内嵌对象成员时,调用内嵌对象成员的构造函数顺序由它们在类中声明的顺序确定。
例 2:含有对象成员的派生类构造函数和析构函数的执行顺序
#include<iostream>
using namespace std;
class Base{ //声明基类 Base
private:
int x;
public:
Base(int i){ //基类的构造函数
x=i;
cout<<"Constructing base class"<<endl;
}
~Base(){ //基类的析构函数
cout<<"Destructing base class"<<endl;
}
void show(){
cout<<"x="<<x<<endl;
}
};
class Derived:public Base{ //声明公有派生类 Derived
private:
Base d; //d 为基类对象,作为派生类的内嵌对象成员
public:
Derived(int i):Base(i),d(i){ //派生类的构造函数,缀上要调用的基类构造函数和对象成员构造函数
cout<<"Constructing derived class"<<endl;
}
~Derived(){
cout<<"Destructing derived class"<<endl;
}
};
int main(){
Derived obj(5);
obj.show();
return 0;
}
执行结果:
例 3:含有多个对象成员的派生类构造函数的执行顺序
#include<iostream>
#include<string>
using namespace std;
class Student{
protected:
int number; //学号
string name; //姓名
float score; //成绩
public:
Student(int num1,string name1,float score1){ // 基类构造函数
number=num1;
name=name1;
score=score1;
}
void print(){
cout<<"number:"<<number<<endl;
cout<<"name:"<<name<<endl;
cout<<"score:"<<score<<endl;
}
};
class Ustudent:public Student{ //声明公有派生类 Ustudent
private:
string major; //专业
Student auditor1; //定义对象成员 1(旁听生 )
Student auditor2; //定义对象成员 2(旁听生 )
public:
Ustudent(int num1,string name1,float score1,int num2,string name2,
float score2,int num3,string name3,float score3,string major1):Student(num1,name1,score1),
auditor2(num2,name2,score2),auditor1(num3,name3,score3){
major=major1;
}
void print(){
cout<<"正式生是:"<<endl;
Student::print();
cout<<"major:"<<major<<endl<<endl;
}
void print_auditor1(){
cout<<"旁听生是:"<<endl;
auditor1.print();
}
void print_auditor2(){
auditor2.print();
}
};
int main(){
Ustudent stu(1,"小明",92,2,"小红",99,3,"小兰",63,"英语");
stu.print();
stu.print_auditor1();
stu.print_auditor2();
return 0;
}
执行结果:
调用内嵌对象成员构造函数的顺序由它们在类中声明的顺序确定。 在本例中有两个内嵌对象成员,虽然在派生类构造函数首部,内嵌对象成员 auditor2 的构造函数写在 auditor1 的前面,但是调用顺序还是先执行 auditor1 的构造函数。
如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化,依次上溯。