c++学习[06] 类和对象 3 多态
多态
多态是指同一操作作用于不同的对象,可以有不同的解释和执行结果。
分为两类:
- 静态多态 : 函数重载、运算符重载,复用函数名 (让函数名、运算符符号 有多种形态表现出来)
- 动态多态 : 派生类和虚函数 实行运行时多态 (多种形态调用公共接口)
一句话概括静态多态:
同一段模板代码,在编译时根据传入的不同类型,生成不同的具体实现。
一句话概括动态多态:
用父类的指针或引用,调用子类重写的函数,实现"一个接口,多种实现"。
区别:
静态多态的函数地址早绑定,即编译阶段确定函数地址
动态多态的函数地址晚绑定,即运行阶段确定函数地址
1.动态多态(虚函数)
父类引用指向接收子类对象(c++中允许父子之间的类型转换)
![![[多态.png]]](https://i-blog.csdnimg.cn/direct/fc322a54660c400aa381ccb2ed018d83.png)
想让猫说话,但实际上是动物在说话
原因:该写法属于地址早绑定(speak函数)
若想让猫说话,利用动态多态(晚绑定)
virtual关键字 虚函数:
![![[虚函数.png]]](https://i-blog.csdnimg.cn/direct/29061cc6d8b5420897180980b79fe914.png)
(子类实现父类同名函数)
![![[同名函数.png]]](https://i-blog.csdnimg.cn/direct/75f931f1f05649a5a8e0067ee26aa8bc.png)
动态多态满足条件
- 有继承关系
- 子类重写父类中虚函数 ,virtual关键字可以不加(函数返回类型、函数名、参数完全相同,不是重载)
动态多态的使用
父类的指针或引用 指向子类对象
2.多态的原理剖析
复习:只有非静态成员变量存储于类,静态函数和成员,非静态函数都不存储于类
虚函数:
virtual 虚函数指针,指向虚函数表,表内部记录虚函数地址(成员函数的地址,加作用域 &取地址)
子类:
->不重写
继承父类——指针,指向子类虚函数表 ,记录虚函数
->重写
子类中虚函数表内部替换为子类的虚函数地址
当父类指针或引用指向子类对象,发生多态(从子类的虚函数表中找虚函数地址)
3.多态的优势
普通写法,扩展新功能时需要不断修改源码
开发提倡:开闭原则,即对扩展开放,对修改进行关闭
多态实现:
1.抽象类(多态写虚函数,子类中重写)
2.继承(vitual关键字可省略)抽象类
3.多态使用条件:[[#动态多态的使用|父类指针或引用指向子类对象]]
new一个对象,用指针接收、访问![![[多态案例.png]]](https://i-blog.csdnimg.cn/direct/3afd7106e91e4adfaff1a91e47430b47.png)
4.纯虚函数和抽象类
多态中,父类虚函数的实现无意义,主要调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
*删去了{}
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点
- 无法实例化对象(太抽象,不允许)
- 子类必须重写(完全一样)抽象类中的纯虚函数,否则也属于抽象类(无法实例化对象)
new一个普通类型和数组之前有讲过,
如何new一个对象?(类类型)
// 写法1:默认构造
Son* p1 = new Son; // 调用默认构造函数(不带括号)
// 写法2:默认构造(带括号,效果一样)
Son* p2 = new Son(); // 也调用默认构造函数
// 写法3:带参构造
Son* p3 = new Son(100); // 调用带参数的构造函数
// 写法4:数组
Son* p4 = new Son[5]; // 分配5个Son对象,每个都调用默认构造
区别:![![[new对象.png]]](https://i-blog.csdnimg.cn/direct/4104f898959d49e08d78739c631d6a93.png)
// 多态技术调用
Base * base = new Son;
多态让函数接口更通用化,通过一个指针,创建对象不同,调用不同形态的函数
案例2
父类指针:
普通函数——调用父类,虚函数——调用子类版本(多态)
堆区数据手动释放
abs指向 堆区数据
-
new Coffee→ 在堆区创建Coffee对象,返回地址(假设0x1000) -
调用
doWork(0x1000)→ 形参abs保存地址0x1000 -
delete abs→ 释放地址0x1000处的堆区对象![![[堆区数据释放.png]]](https://i-blog.csdnimg.cn/direct/4fbdd7f5a49747c4aa37fd5842ad0101.png)
扩展开放,修改关闭,增加新功能不需要修改,扩展即可
纯虚函数,父类不写实现,子类重写函数并且写具体实现
5.虚析构和纯虚析构
问题:
delete 父类指针 不会调子类的析构代码
解决:
将父类中的析构改成(纯)虚析构,释放子类对象
- 父类指针释放子类对象
子类中有数据开辟到堆区,需要调用子类的析构代码,但多态调不到![![[子类堆区属性释放.png]]](https://i-blog.csdnimg.cn/direct/e5c05e05e9af4cd9a4f585eb112da7ff.png)
纯虚析构
和虚析构都需要代码实现
父类中的堆区数据在父类的析构函数中释放,所以需要代码实现
纯虚析构的代码实现:
类内声明,类外实现(不用写virtual,加类的作用域)
有纯虚析构,这个类属于抽象类,无法实例化
总结
![![[虚析构和纯虚析构总结.png]]![[虚析构和纯虚析构总结2.png]]](https://i-blog.csdnimg.cn/direct/9906c957613e4a0eab2accf7705a38a9.png)
案例
抽象类 <-纯虚函数
电脑类,构造函数传入零件的指针;提供工作函数调用零件工作的接口
具体厂商的零件继承抽象类(重写纯虚函数,写实现)
测试阶段组装三台不同的电脑
更多推荐
所有评论(0)