2.7多态
多态
多态:多种形态。
1、多态的基本概念
多态分为两类
1.静态多态
函数重载和运算符重载属于静态多态,复用函数名
2.动态多态
派生类和虚函数实现运行时多态
静态多态和动态多态区别:
1.静态多态的函数地址是早绑定的。
早绑定:编译阶段确定函数地址
2.动态多态的函数地址是晚绑定的。
晚绑定:运行阶段确定函数地址
静态多态例子:
class Animal { public: //原因在这里! //现在是一个animal的对象,它调用speak函数,不管你传的是cat还是dog, //它都会走Animal类里边的speak函数。因为地址是已经确定好的。 void speak() { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; void doSpeak(Animal &animal) { animal.speak(); } int main() { Cat cat; doSpeak(cat); //输出的是“动物在说话”,而我们的本意是想输出“小猫在说话” //原因在于,现在的代码时是静态多态,地址早绑定,在编译阶段确定函数地址。 return 0; }
那么我们如何实现让“小猫”说话呢?——利用动态多态!让地址晚绑定
class Animal { public: virtual void speak() { cout << "动物在说话" << endl; } };
总结:
多态满足条件:
1.有继承关系
2.子类重写父类中的虚函数
多态使用条件:
父类指针 或引用 指向子类对象。
重写:函数返回值类型,函数名 参数列表,完全一致称为重写。
我们写虚函数的时候,类的内部发生了改变,多了一个叫vfptr的指针,即虚函数(表)指针。
虚函数指针指向的是一个虚函数表,虚函数表内部记录的是 虚函数的入口地址。
子类会继承父类的虚函数 和 虚函数指针。
当子类重写这个虚函数的时候,它会把自身的虚函数的入口地址替换掉,换成自己的虚函数入口地址。
之后,当你用父类的指针或引用指向子类对象的时候,由于本身还是个子类对象,当你在调用这个公共的接口时,它会从子类的虚函数表中找子类的虚函数入口地址。**
vfptr 虚函数(表)指针
v —— virtual
f —— function
ptr —— pointer
vftable 虚函数表
v —— virtual
f —— function
table —— table
2、多态案例——计算器类
案例描述:
分贝利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类。
多态的优点:
1.代码组织结构清晰
2.可读性强
3.利于前期和后期的扩展以及维护
总结:
C++开发提倡利用多态设计程序架构,因为多态优点很多。
3、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。
因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 ( 参数列表 ) = 0 ;
如:virtual void func() = 0
当类中有了纯虚函数,这个类也成为:抽象类
抽象类特点:
1.抽象类无法实例化对象!
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
写纯虚函数的目的:就是想写动态多态,让子类重写父类中的多态。
如果不写父类中的虚函数,你连创建对象的权限都没有。
(子类必须重写父类中的纯虚函数,否则无法实例化对象)
4、多态案例二——制作饮品
案例描述:
制作饮品的大致流程:煮水——冲泡——倒入杯中——加入辅料。
利用多态技术实现本例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。
//抽象制作饮品 class AbstractDrinking { public: //烧水 virtual void Boil() = 0; //冲泡 virtual void Brew() = 0; //倒入杯中 virtual void PourInCup() = 0; //加入辅料 virtual void PutSomething() = 0; //规定流程 void MakeDrink() { Boil(); Brew(); PourInCup(); PutSomething(); } }; //制作咖啡 class Coffee : public AbstractDrinking { public: //烧水 virtual void Boil() { cout << "煮农夫山泉!" << endl; } //冲泡 virtual void Brew() { cout << "冲泡咖啡!" << endl; } //倒入杯中 virtual void PourInCup() { cout << "将咖啡倒入杯中!" << endl; } //加入辅料 virtual void PutSomething() { cout << "加入牛奶!" << endl; } }; //制作茶水 class Tea : public AbstractDrinking { public: //烧水 virtual void Boil() { cout << "煮自来水!" << endl; } //冲泡 virtual void Brew() { cout << "冲泡茶叶!" << endl; } //倒入杯中 virtual void PourInCup() { cout << "将茶水倒入杯中!" << endl; } //加入辅料 virtual void PutSomething() { cout << "加入枸杞!" << endl; } }; //业务函数 void DoWork(AbstractDrinking* drink) { drink->MakeDrink(); delete drink; } void test01() { DoWork(new Coffee); cout << "--------------" << endl; DoWork(new Tea); } int main() { test01(); return 0; }
5、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为 虚析构 或者 纯虚析构
虚析构 和 纯虚析构 的共性:
1.可以解决父类指针释放子类对象。
2.都需要有具体的函数实现。
虚析构 和 纯虚析构 的区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名(){} = 0;
类名::~类名(){}
如:Animal::~Animal(){}
class Animal { public: Animal() //构造函数 { cout << "动物在说话" << endl; } //~Animal() //{ // cout << "Animal的虚析构函数调用" << endl; //} //纯虚函数,我们暂时用不到父类的函数,但是子类要继承,所以只能设置为纯虚函数 virtual void Speak() = 0; //利用虚析构可以解决 父类指针释放子类对象时不干净的问题。 //纯虚析构函数需要声明也需要实现! //有了纯虚析构 之后,这个类也属于抽象类,无法实例化对象! virtual ~Animal() = 0; }; //虚析构也好,纯虚析构也好,它必须得有一个函数的具体实现, //原因:假设,我们在父类中也有一些数据开辟到堆区了,那么父类的析构函数就有用了, //父类的堆区数据肯定是在父类的析构代码中释放的 //纯虚析构函数怎么写? //如下,这是一个Animal下的纯虚析构 Animal::~Animal() { cout << "Animal 的纯虚析构函数调用!" << endl; } class Cat : public Animal { public: //构造函数 Cat(string name) { m_Name = new string(name); //在堆区创建一个属性,用一个指针来维护他 } //父类的纯虚函数一定要重写,否则子类也属于一个抽象类,就无法实例化对象 virtual void Speak() { cout << *m_Name << "小猫在说话" << endl; } //我们在堆区创建了一个对象,就得写一个析构函数来释放它 ~Cat() { if (m_Name != NULL) { cout << "这是Cat的析构函数调用" << endl; delete m_Name; m_Name = NULL; } } public: string *m_Name; //在堆区创建一个属性,用一个指针来维护他 }; void test() { Animal* animal = new Cat("Tom"); //新建一个父类对象指针,指向新建的子类对象 animal->Speak(); //父类指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区属性,会出现内存泄露 //解决方法:将父类的析构函数改为虚析构函数 delete animal; //用完就释放 } int main() { test(); return 0; }
总结:
1.虚析构 或 纯虚析构就是用来解决通过父类指针释放子类对象。
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。
3.拥有纯虚析构函数的类也属于抽象类。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具