C++ 类继承:构造函数与析构函数调用顺序,派生类和基类之间的特殊关系,公有继承及其他
文章目录
- 一、派生类构造函数与基类构造函数
- 二、创建与销毁派生类对象时,构造函数和析构函数的调用
- 三、派生类和基类之间的特殊关系
- 四、公有继承
- (一)、何为公有继承
- (二)、多态公有继承
- (三)、虚函数的工作原理
- (四)、虚函数注意事项
- 五、访问控制:protected
- 六、抽象基类
一、派生类构造函数与基类构造函数
派生类的构造函数做了如下事情:
- 创建基类对象
- 派生类构造函数通过成员初始化列表将基类信息传递给基类构造函数
- 派生类构造函数初始化派生类新增的数据成员
二、创建与销毁派生类对象时,构造函数和析构函数的调用
创建派生类对象时
程序调用基类构造函数
初始化继承的数据成员
使用初始化器列表语法指明要使用的基类构造函数
Derived::Derived(type1 x, type2 y):Base(x,y) //初始化列表 {... }
若无初始化器列表语法,则使用默认的基类构造函数
程序调用派生类构造函数
- 初始化新增的数据成员
销毁派生类对象时
- 程序调用派生类的析构函数
- 程序调用基类的析构函数
三、派生类和基类之间的特殊关系
基类指针可在不进行显式转换的情况下指向派生类对象
基类引用可在不进行显式转换的情况下引用派生类对象
基类指针或引用只能调用基类方法,不能调用派生类方法
Derived derived; Base *basePointer = &derived; Base &baseReference = derived;
一般要求引用和指针类型与赋给的类型相匹配,这一规则对继承来说是例外。
然而此例外是单向的,即不可以将基类对象和地址赋给派生类引用和指针。
Base base; //Derived *derived = &base; Not Allowed //Derived &derived = base; Not Allowed
派生类对象可以使用基类的方法,条件是此方法不是私有的
class Base{private:int number;void privateMethod(){}public:void publicMethod(){} }class Derived: public Base{}Derived derived; //derived.privateMethod(); Not allowed derived.publicMehod(); //Allowed
四、公有继承
(一)、何为公有继承
公有继承为 is-a-kind-of
关系,通常使用术语 is-a
。如从 Fruit
类中派生出 Banana
类,即应采用公有派生,因为 Banana is a kind of fruit
但公有继承不建立 has-a
关系,不建立 is-like-a
关系,不建立 is-implemented-as-a
关系,不建立 uses-a
关系。因此以下例子都不应该采用公有继承。
Fruit->Lunch
Shark->Lawyer
Array->Stack
Computer->Printer
(二)、多态公有继承
实现多态公有继承有两种方法:
- 在派生类中重新定义基类的方法
- 使用虚函数
以下例子将说明这两种方法的使用,以及它们的不同之处:
class Base {private:int number1;public:Base(int number1 = 0);virtual void show1(){};void show2(){}virtual ~Base(){}
}class Derived : public Base
{private:int number2;public:Derived(const Base &b,int number2 = 0);Derived(int number1 = 0, int number2 = 0);virtual void show1(){}void show2(){}
}int main()
{Base base(0);Derived derived(0,0);//此处代码将演示第一种方法//在派生类中重新定义基类的方法,使用对象类型确认使用哪个版本//此处 base 和 derived 的类型不同base.show(); //use Base::show()derived.show(); //use Derived::show()//此处代码将演示第二种方法,并说明虚函数与第一种方法的区别//此处 base1 和 base2 的类型相同Base &base1 = base;Base &base2 = derived;//使用了virtual,程序将根据引用或指针 **指向的对象** 去选择相应的方法base1.show1(); //use Base::show1()base2.show1(); //use Derived::show1()//没有使用virtual,程序将根据 **引用类型** 或 **指针类型** 去选择相应的方法base1.show2(); //use Base::show2()base2.show2(); //use Base::show2()return 0;
}
另外,注意到了,上述程序使用了虚析构函数。
如果析构函数不为虚,则将只调用对应于指针类型的析构函数。这意味着对于 base1
和 base2
,二者均只有 Base
的析构函数被调用,即使base2
指针指向的为一个Derived
对象。
如果析构函数为虚,则将调用对应于指针所指对象类型的析构函数。这意味着对于 base1
和 base2
,base1
会调用Base
的析构函数,而base2
会先调用Derived
的析构函数,再调用Base
的析构函数。
(三)、虚函数的工作原理
调用虚函数时:
- 程序将查看存储在对象中的vtbl(vitual function table)地址。vtbl 为虚函数表,是对象的一个隐藏成员里保存着的指针。
- 程序转向相应的函数地址表。
- 程序将使用函数地址表里的地址,执行具有该地址的函数。
如下图所示。
(四)、虚函数注意事项
在基类方法声明中,使用
virtual
关键字,可使该方法在基类以及所有派生类中是虚的。包括从派生类里面派生出来的类。若使用指向对象的引用或指针来调用虚方法,则程序进行动态联编(亦称晚绑定),将使用为对象类型定义的方法,而不适用为指针或引用类型定义的方法。
通过晚绑定,基类指针或引用可以指向派生类对象。
如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。
以下内容一定要理解并记住
构造函数不可是虚函数
析构函数应当是虚函数,除非类不用做基类。但即使类不做基类,将类的析构函数声明为虚也并非错误,只不过可能牺牲效率
友元
friend
不能是虚函数。只有类成员才能是虚函数。而友元并不是类成员若派生类没有重新定义函数,则将使用该函数的基类版本
在派生类里重新定义函数,并不是重载。此举将隐藏方法。
class Base{private: int num;public: virtual void show(int i)const{} } class Derived: public Base{public: virtual void show()const{} } Derived derived; derived.show(); //valid //derived.show(2); invalid
五、访问控制:protected
派生类可直接访问基类的保护成员,但不能直接访问基类的私有成员。
在类外,只能用公有类成员去访问protected
的类成员。
六、抽象基类
- 抽象类只能用作其他类的基类,而不能定义抽象类的对象
- 抽象类不能用于参数类型、函数返回值或显示转换的类型
- 抽象类可以定义抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性
C++ 类继承:构造函数与析构函数调用顺序,派生类和基类之间的特殊关系,公有继承及其他相关推荐
- C++继承中构造函数、析构函数调用顺序及虚析构函数
首先说说构造函数,大家都知道构造函数里就可以调用成员变量,而继承中子类是把基类的成员变成自己的成员,那么也就是说子类在构造函数里就可以调用基类的成员了,这就说明创建子类的时候必须先调用基类的构造函数, ...
- C++构造函数和析构函数调用虚函数时都不会使用动态联编
先看一个例子: #include <iostream> using namespace std;class A{ public:A() {show();}virtual void show ...
- 多重继承的构造函数和析构函数的执行顺序(包含虚基类)
下面示例就是说明多重继承析构函数和构造函数的执行顺序: #include <iostream> using namespace std;class A {public:A(int i){c ...
- 不同派生方式下基类成员在派生类中的可访问范围属性
不同派生方式下基类成员在派生类中的可访问范围属性 派生方式 派生方式 派生方式 基类成员 公有派生 私有派生 保护派生 私有成员 不可访问 不可访问 不可访问 保护成员 保护 私有 保护 公有成员 公 ...
- 请确保此代码文件中定义的类与“inherits”属性匹配,并且该类扩展的基类(例如Page 或UserControl)是正确的。...
编译ASP.NET时,提示"请确保此代码文件中定义的类与"inherits"属性匹配,并且该类扩展的基类(例如Page 或UserControl)是正确的.", ...
- 分别声明Teacher(教师)类和Cadre(干部)类,采用多重继承方式由这两个类派生出新类Teacher_Cadre(教师兼干部)类。要求: (1)在两个基类中都包含姓名、年龄、性别、地址、电话等数
分别声明Teacher(教师)类和Cadre(干部)类,采用多重继承方式由这两个类派生出新类Teacher_Cadre(教师兼干部)类.要求: (1)在两个基类中都包含姓名.年龄.性别.地址.电话等数 ...
- 构造和析构函数调用顺序
一. 理论 1. 构造函数和析构函数 ①构造函数 构造函数不能有返回值 缺省构造函数时,系统将自动调用该缺省构造函数初始化对象,缺省构造函数会将所有数据成员都初始化为零或空 创建一个对象时,系统自动 ...
- C++析构函数调用顺序
文章目录 析构函数工作过程: 1.执行析构函数的函数体; 2.如果该类中拥有类对象成员,且类对象有析构函数,则以类对象成员声明次序的相反顺序调用其析构函数,销毁类对象成员; 3.按原来构造顺序的相反顶 ...
- C++中构造函数和析构函数调用的时机
今天看书忽然对这个地方有点模糊,尤其是析构函数在调用默认的析构函数和用户自己覆写的析构函数的时候有点意识模糊呢.写段代码总结下 [cpp] view plain copy #include < ...
最新文章
- HTTP的四种请求方法
- c语言infile和outfile用法,C语言文件读写基本操作DEMO
- 底部分页栏_2020年执业药师考试教材各科目增加页数!最多203页
- tp连接mysql mysql_thinkphp学习简易教程(二) thinkphp连接读取MySQL数据库
- java的归并排序算法_归并排序算法Java实现
- 1补码 2补码_8085微处理器中8位数字的1和2的补码
- Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析
- python两个集合相减_python集合的运算,两个集合相减是什么意思
- python编写加密程序_用Python实现一个简单的加密程序
- weblogic的输出打印日志设置
- 一个关于finally和return的面试题
- visio 2003密钥
- MarkDown安装后不能预览问题(awesomium_sdk的下载)
- 2021抖音数据报告(完整版)
- 嵌入式系统开发环境概述
- 谷歌,互联网界的“彩蛋狂魔”
- Spring中循环依赖的解决办法
- 升级iOS CocoaPods 版本
- 什么是驱动?驱动程序的工作原理?
- limit和offset用法