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、多态案例二——制作饮品

案例描述:
制作饮品的大致流程:煮水——冲泡——倒入杯中——加入辅料。
利用多态技术实现本例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。
image

//抽象制作饮品
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.拥有纯虚析构函数的类也属于抽象类。

6、多态案例三——电脑组装

posted @   蓝色的海嗷  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示