C++:派生类的构造函数和析构函数的调用顺序
一、派生类
在C++编程中,我们在编写一个基类的派生类时,大致可以分为四步:
• 吸收基类的成员:不论是数据成员还是函数成员,派生类吸收除基类的构造函数和析构函数之外的全部成员。
• 改造基类函数:在派生类中声明一个或多个与其(某个)基类中的成员函数同名的成员函数,并将它(们)根据新的需求进行重写
• 发展新的成员:在派生类中添加新的成员变量和成员函数,其中新添加的成员要求必须和基类中的成员不同名,并且应当保证新添加的成员会使派生类在功能上相比其基类有所发展
• 重写派生类的构造函数和析构函数
特别注意:
在重写派生类的构造函数时,通常将其基类们的构造函数变成新构造函数的一部分
语法:派生类名::派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),......,新增成员名1(参数1),新增成员名2(参数2){}
示例:
#include<iostream>
using namespace std;
3 class Father{ //基类
4 public:
5 Father()=default; //默认构造函数
6 Father(int value):father_value(value){} //带参数的构造函数
7 ~Father(){} //析构函数
8 void show(){
9 cout<<"这是基类Father"<<endl;
10 }
11 void father_func(){
12 cout<<"这是基类Father的方法father_func:"<<endl;
13 cout<<"\t成员father_value的值为:"<<father_value<<endl;
14 }
15 private:
16 int father_value;
17 };
19 class Son:public Father{ //Father类的派生类Son
20 public:
/*步骤一:吸收Father类中除构造函数和析构函数之外的所有成员
22 void show(){
23 cout<<"这是基类Father"<<endl;
24 }
25 void father_func(){
26 cout<<"这是基类Father的方法father_func:"<<endl;
27 cout<<"\t成员father_value的值为:"<<father_value<<endl;
28 }
29 */
30 //步骤二:改造基类Father中的成员
31 void show(){
32 cout<<"这是派生类Son"<<endl;
33 }
//步骤三:发展新的成员
35 void son_func(){
36 cout<<"这是派生类Son的方法son_func: "<<endl;
37 cout<<"\t成员son_value的值为:"<<son_value<<endl;
38 }
//步骤四:重写构造函数和析构函数
40 Son()=default;
41 Son(int value):Father(value),son_value(value){}
42 ~Son(){}
43 private:
44 /*步骤一:吸收Father类中除构造函数和析构函数之外的所有成员
int father_value;
*/
//步骤三:发展新的成员
48 int son_value;
49 };
int main(){
Father father();
father.show();
father.father_func();
cout<<"------------分界线--------------------"<<endl;
Son son();
son.show();
son.father_func();
son.son_func();
return ;
}

二、派生类的构造函数的调用顺序
我们先来看一个示例:
#include<iostream>
using namespace std;
class Father1{ //基类1
public:
Father1(){
cout<<"这是Father1类的构造函数"<<endl;
}
}; class Father2{ //基类2
public:
Father2(){
cout<<"这是Father2类的构造函数"<<endl;
}
}; class Father3{ //基类3
public:
Father3(){
cout<<"这是Father3类的构造函数"<<endl;
}
}; class Son:public Father1,public Father2,public Father3{ //派生类
public:
Son(){
cout<<"这是Son类的构造函数"<<endl;
}
}; int main(){
Son s;
return ;
}

由上面的例子可以看出,派生类在创建对象时会先调用其基类们的构造函数,然后才会调用自己的构造函数。下面是类Son的对象s在内存中的存放形式:

那么派生类调用基类的构造函数的顺序又是如何确定的呢?我们在来看一个例子:
#include<iostream>
using namespace std;
class Father1{ //基类1
public:
Father1(){
cout<<"这是Father1类的构造函数"<<endl;
}
}; class Father2{ //基类2
public:
Father2(){
cout<<"这是Father2类的构造函数"<<endl;
}
}; class Father3{ //基类3
public:
Father3(){
cout<<"这是Father3类的构造函数"<<endl;
}
}; class Son:public Father3,public Father1,public Father2{ //派生类
public:
Son(){
cout<<"这是Son类的构造函数"<<endl;
}
private:
30 Father1 father1;
31 Father2 father2;
32 Father3 father3;
}; int main(){
Son s;
return ;
}

由此可见,派生类在创建对象时其调用构造函数的顺序是:
• 先按照派生类对基类的继承顺序调用基类的构造函数。上例中由语句“class Son:public Father3,public Father1,public Father2”可知类Son先继承基类Father3,然后继承基类Father1,最后继承基类Father2,因此其调用基类的构造函数的顺序也是先Fathe3,再Father1,最后Father2。
•( 若派生类的成员变量中存在其基类的对象 )接着按照基类的对象在派生类定义中声明的先后顺序调用基类的构造函数。上例中按照成员对象的定义顺序依次调用Father1、Father2和Father3的构造函数。
• 最后调用派生类自己的构造函数
特别注意:
1.如果基类中没有定义默认构造函数或带有缺省值的构造函数而只有带参数的构造函数时,派生类的构造函数中必须显式的给出基类名和参数表,否则编译器将报错
#include<iostream>
using namespace std;
class Father1{ //基类1
public:
Father1(int v):value1(v){
cout<<"这是Father1类的构造函数"<<endl;
}
private:
int value1;
}; class Father2{ //基类2
public:
Father2(int v):value2(v){
cout<<"这是Father2类的构造函数"<<endl;
}
private:
int value2;
}; class Father3{ //基类3
public:
Father3(int v):value3(v){
cout<<"这是Father3类的构造函数"<<endl;
}
private:
int value3;
}; class Son:public Father1,public Father2,public Father3{ //派生类
public:
Son(int v):Father1(v),Father2(v),Father3(v),value4(v){ //派生类的构造函数中必须显式的给出基类名和参数表
cout<<"这是Son类的构造函数"<<endl;
}
private:
int value4;
}; int main(){
Son s();
return ;
}

2.如果基类中没有定义构造函数,这派生类也可以不定义构造函数,系统会自动在类中添加默认的构造函数的
3.如果基类中定义了带有参数表的构造函数时,派生类就应当定义相应的构造函数
QUESTION:基类在派生类的构造函数的初始化列表中的顺序是否会影响派生类的构造函数调用顺序?
ANSWER: 我们先来看一个示例:
#include<iostream>
using namespace std;
class Father1{ //基类1
public:
Father1(int v):value1(v){
cout<<"这是Father1类的构造函数"<<endl;
}
private:
int value1;
}; class Father2{ //基类2
public:
Father2(int v):value2(v){
cout<<"这是Father2类的构造函数"<<endl;
}
private:
int value2;
}; class Father3{ //基类3
public:
Father3(int v):value3(v){
cout<<"这是Father3类的构造函数"<<endl;
}
private:
int value3;
}; class Son:public Father3,public Father1,public Father2{ //派生类
public:
Son(int v):Father1(v),Father2(v),Father3(v),value4(v){
cout<<"这是Son类的构造函数"<<endl;
}
private:
int value4;
}; int main(){
Son s();
return ;
}

上面的例子中派生类Son是以顺序Father3、Father1、Father2来继承基类的,但在其构造函数中基类的顺序确实Father1、Father2、Father3。最终的结果表明类Son仍以其继承基类的顺序来调用基类的构造函数,而非基类在派生类构造函数中的顺序。因此可见基类在派生类的构造函数的初始化列表中的顺序不会影响派生类的构造函数调用顺序
三、派生类的析构函数的调用顺序
• 在派生类中,其析构函数只需要关心新增的一般成员的“善后工作”。而对于新增的成员对象和基类的“善后工作”,系统会自己调用成员对象和基类的析构函数来完成,而不需要用户来关心。
• 在派生类中,析构函数各部分的执行顺序与其构造函数的调用顺序刚好相反,即派生类的析构函数先对其新增的一般成员进行析构,然后对新增的成员对象进行析构,最后按照与其继承基类的相反顺序来调用基类的析构函数。
#include<iostream>
using namespace std;
class Father1{ //基类1
public:
Father1(){
cout<<"这是Father1类的构造函数"<<endl;
}
~Father1(){
cout<<"这是Father1类的析构函数"<<endl;
}
}; class Father2{ //基类2
public:
Father2(){
cout<<"这是Father2类的构造函数"<<endl;
}
~Father2(){
cout<<"这是Father2类的析构函数"<<endl;
}
}; class Father3{ //基类3
public:
Father3(){
cout<<"这是Father3类的构造函数"<<endl;
}
~Father3(){
cout<<"这是Father3类的析构函数"<<endl;
}
}; class Son:public Father3,public Father1,public Father2{ //派生类
public:
Son(){
cout<<"这是Son类的构造函数"<<endl;
}
~Son(){
cout<<"这是Son类的析构函数"<<endl;
}
private:
Father1 father1;
Father2 father2;
Father3 father3;
}; int main(){
Son s;
return ;
}

C++:派生类的构造函数和析构函数的调用顺序的更多相关文章
- c++学习笔记4,派生类的构造函数与析构函数的调用顺序(一)
測试源代码: //測试派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace ...
- C++学习笔记(6)----基类和派生类的构造函数和析构函数的执行顺序
基类和派生类:构造函数和析构函数的执行顺序 在Visual Studio中,新建控制台工程,构造类如下: #include<iostream> using namespace std; c ...
- C++:派生类的构造函数和析构函数
4.2 派生类的构造函数和析构函数4.2.1 派生类构造函数和析构函数的执行顺序 通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数:当撤销派生类对象时,则先执行派生类 ...
- 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)
[源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...
- C++学习之路—继承与派生(二):派生类的构造函数与析构函数
(根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 由于基类的构造函数和析构函数是不能被继承的,所以 ...
- cc28c_demo.cpp,派生类的构造函数和析构函数-代码示范3
cc28c_demo.cpp,派生类的构造函数和析构函数-代码示范3 //派生类的构造函数和析构函数//派生类的构造函数(执行步骤)//--执行基类的构造函数//--执行成员对象的构造函数//--执行 ...
- C++学习笔记(7)----类的数组中构造函数和析构函数的调用顺序
C++类的数组中构造函数和析构函数的调用顺序(2) 对于如下的代码: #include<iostream> using namespace std; class CBase { priva ...
- C++C++中构造函数与析构函数的调用顺序
http://blog.csdn.net/xw13106209/article/details/6899370 1.参考文献 参考1: C++继承中构造函数.析构函数调用顺序及虚函数的动态绑定 参考2 ...
- C++ 构造函数和析构函数的调用顺序、虚析构函数的作用
构造函数和析构函数的调用顺序 构造函数的调用顺序: 当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达最底层的目标派生类的构造函数为止. 析构函数的调用书序: ...
随机推荐
- [python]如何理解uiautomator里面的 right,left,up,down 及使用场景
关于Android自动化uiautomator 框架,前面有讲在有些场景下,比如需要在设置界面中将某些选项开关打开或者关闭(前提是这些选项和开关的控件(resourceId,className,tex ...
- js指定范围随机整数
js获取指定范围内随机整数,例如 6-10 (m-n) 计算公式: Math.floor(Math.random()*(n-m))+m // 6-10随机数,用循环得出一组测试随机数 var str ...
- css实现气泡框效果
前提:气泡框或者提示框是网页很常见的,实现它的方式有很多,我们以前最常用的就是切图片 然后通过 "定位" 方式 定位到相应的位置,但是用这种方式维护很麻烦,比如设计师想改成另外一种 ...
- 多Tomcat多JDK版本的Window服务添加配置方式
本文所讲的配置方式只适合Windows系统,所牵扯的软件2个解压的jdk,2个解压的tomcat. 1.环境初始化 将两个jdk和tomcat解压到我们指定的目录下.修改两个tomcat的端口:必须修 ...
- PAT B1015 德才论 (25 分)
宋代史学家司马光在<资治通鉴>中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人.凡取人之术,苟不得圣人,君子而与之,与其得小人,不若得愚人 ...
- Android SurfaceView概述
简介:SurfaceView继承自View,但它与View不同,View是在UI主线程中更新画面,而SurfaceView是在一个新线程中更新画面,View的特性决定了其不适合做动画,因为如果更新画面 ...
- Android使用AsyncTask设置请求超时的注意事项
备注:该篇文章为原创,转载请声明地址,谢谢! /** * AsyncTaskTools2集成了AsyncTask类 * 前三个参数为回到函数,最后一个为全局的Context */ final Asyn ...
- P1522 牛的旅行 Cow Tours
题目描述 农民 John的农场里有很多牧区.有的路径连接一些特定的牧区.一片所有连通的牧区称为一个牧场.但是就目前而言,你能看到至少有两个牧区通过任何路径都不连通.这样,Farmer John就有多个 ...
- kettle学习笔记(三)——kettle资源库、运行方式与日志
一.kettle资源库 资源库是用来保存转换任务的,用户通过图形界面创建的的转换任务可以保存在资源库中. 资源库可以使多用户共享转换任务,转换任务在资源库中是以文件夹形式分组管理的,用户可以自定义文件 ...
- 2017-2018-2 20155224『网络对抗技术』Exp5:MSF基础应用
基础问题回答 用自己的话解释什么是exploit,payload,encode? exploit就相当于是载具,将真正要负责攻击的代码传送到靶机中,我觉得老师上课举的火箭和卫星的例子非常形象,火箭只是 ...