学习:类和对象——多态和纯虚和抽象类等
多态的基本概念:
多态是C++面向对象三大特性之一
多态分为两类
1、静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名 ,那么这个我们之前都有用到过
2、动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
1、静态多态的函数地址早绑定 - 编译阶段确定函数地址
2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址
示例代码:
#include<iostream> #include<string> using namespace std; class Animal { public: virtual void speak() { // 进行virtual修饰成员函数,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。 cout << "动物在说话" << endl; } }; class Dog :public Animal{ public: void speak() { cout << "小狗在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; void aaa(Animal & animal) { animal.speak(); } void test01() { Cat c1; aaa(c1); Dog d1; aaa(d1); } int main() { test01(); system("pause"); return 0; }
总结:
多态满足的条件:
1、有继承关系
2、子类重写父类中的虚函数
多态使用条件:
1、父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态的原理:
不理解原理的话,真的很难受
我们先来了解下一个虚函数和虚拟指针和虚拟表的概念:
虚函数:当函数被virtual
修饰过后,这个函数也就会被称为虚函数
虚拟指针::当成为了虚函数,就会生成一个虚拟指针,这个指针保存的地址就是虚拟表的位置
我们通过之前的工具来查看下类的构造
正常的Cat类
那么继承的是来自父类中的一切
我们接着看下父类加了virtual
之后的Cat类
这时候的虚拟指针就指向虚拟表中的Cat类中speak函数的地址
纯虚函数和抽象类:
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,所以我们在父类中的虚函数中定义的内容都没啥用
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例代码:
#include<iostream> #include<string> using namespace std; class Base { public: virtual void to_take() = 0; //定义了一个纯虚函数to_take }; class Son :public Base { public: void to_take() { cout << "这是实现了父类的to_take"; } }; class Son2 :public Base { }; int main() { //Base b1; //直接实例化错误,因为Base为抽象类,为什么呢? 因为Base中用virtual定义了纯虚函数 //Son2 s2; //直接实例化错误,就算继承了Base但是没有实现to_take的纯虚函数,这个类还是一个抽象类,不是实体类 //以上的两个都是在栈区进行实例化的,那我们如果通过堆区实例化可以吗 //b1 = new Base; //答案是一样的,不可以 Base * base = new Son; //实例化成功,因为实现了父类中的纯虚函数 base->to_take(); //这里由于是指针,所以我们通过->来进行调用 system("pause"); return 0; }
虚析构和纯虚析构:
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1、可以解决父类指针释放子类对象
2、都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
示例代码:解决方法两种
#include<iostream> #include<string> using namespace std; class A { public: virtual void speak() = 0; //纯虚函数 A() { cout << "这是A的构造函数" << endl; } //virtual ~A() { //利用虚析构可以解决父类指针释放子类对象时候不干净的问题 // cout << "这是A的析构函数" << endl; //} virtual ~A() = 0; //利用纯虚析构来解决父类指针释放子类对象时候不干净的问题,在全局实现A的析构函数 }; A::~A() { cout << "这是A的析构函数" << endl; } class B:public A { public: B(int age) { cout << "这是B的构造函数" << endl; this->age = new int(age); } ~B() { if (age != NULL) { delete age; age = NULL; cout << "这是B的析构函数" << endl; } } virtual void speak() { cout << "年龄为"<< *this->age << "岁的B在说话" << endl; } public: int * age; //定义一个指针变量,来储存堆区的数据 }; void test01() { A * a = new B(100); a->speak(); delete a; //进行释放 调用相应的父类的析构函数 但是不会走子类的析构函数 } int main() { test01(); system("pause"); return 0; }
总结:
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY