C++多态

多态

1.多态基本概念
  • 多态是c++面向对象三大特性,封装、继承、多态
  • 多态可以分成两类:
    • 静态多态:就是函数重载、操作符重载。多态就是多种形态,函数重载就是让相同函数名有多种形态表现出来,参数数量,类型不同。
    • 动态多态:c++中多态一般指的是动态多态,派生类和虚函数实现运行时多态。
  • 静态多态和动态多态的区别
    • 静态多态的函数地址早绑定-编译阶段确定函数地址
    • 动态多态的函数地址晚绑定-运行阶段确定函数地址
使用动态多态方法
  • 父类的指针或引用,指向子类的对象
/*
创建一个动物类含有一个说话函数输出动物说话,
然后创建一个子类猫类也有一个说话函数输出小猫在说话。
创建一个执行说话的函数,函数参数为父类Animal,调用speak函数。
在test函数中创建猫类对象,然后对象传入dospeak()中,父类引用指向子类对象。
那输出到底是父类的speak还是子类的speak呢?
*/
#include "stdafx.h"
#include <iostream>

using namespace std;
//动物类
class Animal {
public:	
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};
//猫类
class Cat : public Animal {
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
//执行说话的函数
void dospeak(Animal &animal) //Animal &animal=cat;父类的引用指向子类对象
{
	animal.speak();
}

void test01()
{
	Cat cat;
	dospeak(cat);//传猫自然本意是想让猫说话
}

int main()
{
	test01();
	cout << "hello" << endl;
	system("pause");
    return 0;
}
/*但最后输出动物,是因为地址早在编译阶段就绑定了,
如果想执行让猫说话,这个函数的地址就要晚绑定,动态多态。
只需要在父类成员函数前添加virtual关键字即可
*/

添加狗类测试输入不同子类

#include "stdafx.h"
#include <iostream>

using namespace std;
//动物类
class Animal {
public:	
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
//猫类
class Cat : public Animal {
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
//狗类
class Dog :public Animal {
public:
	void speak()
	{
		cout << "狗在说话" << endl;
	}
};
//执行说话的函数
void dospeak(Animal &animal) //Animal &animal=cat;父类的引用指向子类对象
{
	animal.speak();
}

void test01()
{
	Cat cat;
	dospeak(cat);

	Dog dog;
	dospeak(dog);
}

int main()
{
	test01();
	cout << "hello" << endl;
	system("pause");
    return 0;
}
总结

动态多态满足条件:
1、有继承关系
2、子类要重写父类的虚函数,子类中也有父类的成员函数,它与函数重载不一样,重载的参数不一样,但重写的函数返回值类型,函数名,参数列表要完全相同。
3、子类重写时virtual关键字可有可无,但父类必须要有。

动态多态如何使用:
1、父类的指针或引用,指向子类的对象,这样当你运行那个虚函数时,就晚绑定地址。

2.多态原理剖析:内存角度

还是上一个程序,先去掉父类成员函数的virtual,然后新建test02()函数输出sizeof(Animal),此时它是一个空类,大小为1;再把virtual加上,再编译,此时sizeof(Animal)= 4/8看x86或x64

  • Animal类内部结构
  1. 如果没有virtual
    Animal类内为空,只有一个字节
    在这里插入图片描述
  2. 加上virtual
    vfptr :virtual function pointer虚函数指针
    指针指向一个虚函数表,这个表是vftable,它是什么结构呢,vftable内 存的就是虚函数表,表的内部会记录虚函数的地址,&Animal::speak
    在这里插入图片描述
  • Cat类内部结构
  1. 如果没有发生重写的情况,那就是全部继承了
    也有一个vfptr指向子类的vftable,这个表的内部记录的继承了父类还是父类虚函数地址&Animal::speak,有4/8个字节
    在这里插入图片描述
  2. 如果发生了重写,那么子类中的虚函数表内部会替换成子类的虚函数地址
    &Cat::speak,有4/8个字节
    在这里插入图片描述
    当父类的指针或引用指向子类的时候,发生多态
    Animal &animal=cat
    animal.speak();
3.为什么要用多态,多态有什么优点
  • 多态的优点:
    • 代码组织结构清晰
    • 可读性强
    • 利于前期和后期的扩展和维护

分别用普通写法和多态写法实现两个操作数进行运算的计算器类

//普通写法
class Calculator {
public:
	int getResult(std::string oper)
	{
		if (oper == "+")
			return m_Num1 + m_Num2;
		else if (oper == "-")
			return m_Num1 - m_Num2;
		else if (oper=="*")		
			return m_Num1*m_Num2;
		//如果想扩展新的功能,需要修改源码
		//在真实的开发中,提倡开闭原则
		//对扩展进行开放,对修改进行关闭
	}	
	int m_Num1;
	int m_Num2;
};
//利用多态实现计算器
//先实现计算器抽象类,方便后续扩展
class AbstractCalsulator {
public:
	virtual int getResult();

	int m_Num1;
	int m_Num2;
};
//加法计算器类
class AddCaculator :public AbstractCalsulator {
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};
//减法计算器类
class SubCaculator :public AbstractCalsulator {
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
//乘法计算器类
class MulCaculator :public AbstractCalsulator {
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};
//测试案例
void test201()
{
	//创建计算器对象
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 20;
	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
}

void test202()
{
	//多态使用条件
	//父类指针或者引用指向子类对象
	AbstractCalsulator*abc = new AddCaculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	//减法运算
	abc = new SubCaculator;//只是销毁堆区的数据,但类型没变
	abc->m_Num1 = 100;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	//乘法运算
	abc = new MulCaculator;//只是销毁堆区的数据,但类型没变
	abc->m_Num1 = 100;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}
总结

使用多态虽然代码量多了,但是组织结构更清晰,而且下次如果想要添加新的功能,只需要创建新的子类即可,不用在像普通法一样在类中大段代码中找;如果想要修改某个功能,也只需要修改某个子类;对源码避免改动。
组织架构:

抽象基类,基本的函数名和成员变量
子类重写函数功能
测试用例父类指针或引用指向子类对象
创建实现函数参数为父类引用
实现函数传参为子类对象
4.纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义,我们一般都是调用子类实现,父类的函数即使有功能也用不上,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0;

当类中有纯虚函数,也被成为抽象类

  • 抽象类的特点:
    • 无法实例化对象
    • 子类必须要重写抽象类中的纯虚函数,否则子类也属于抽象类
//纯虚函数和抽象类
class Base {
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类称为抽象类
	//抽象类特点
	//1、无法实例化对象
	//2、抽象类的子类必须要重写父类的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
};
class Son :public Base {
public:
	virtual void func() {
		cout << "func函数调用" << endl;
	}
};

void test301() {
	//Son s;//不重写子类也无法实例化

	//子类使用父类的指针调用
	Base *base = new Son;
	base->func();

}
5.多态案例:制作饮品

制作饮品无论咖啡茶叶对象,大致流程一样:煮水-冲泡-导入杯中-加入辅料

利用多态实现,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

  1. 创建父类,使用纯虚函数,先把框架搭好,makeDrink()将各种功能整合,但是实现仍然是靠子类实现
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();
	}
};
  1. 在子类中将纯虚函数重写
//咖啡子类
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;
	}
};
  1. 实现函数参数为父类的指针,在函数内部实现,最后释放内存,避免泄露。
void doWork(AbstractDrinking* abs) {
	abs->makeDrink();
	delete abs;//记得要释放
}
  1. 测试用例传入子类对象,在主函数调用即可
void test401() {
	
	doWork(new Coffee);
	cout << "---------------------" << endl;
	doWork(new Tea);
}
6.虚析构和纯虚析构

问题:多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构的共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构的区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法
virtual ~类名(){}
纯虚析构语法
virtual ~类名()=0
类名::~类名(){}

  1. 创建Animal父类
class Animal {
public:
	Animal() {
		cout << "Animal 构造函数" << endl;
	}
	~Animal() {
		cout << "Animal 虚析构函数" << endl;
	}
	virtual void speak() = 0;
};
  1. 创建猫子类,在构造函数时在堆区存属性,析构函数将其析构
class Cat :public Animal {
public:
	Cat(string name) {
		cout << "Cat构造函数" << endl;
		m_Name = new string(name);
		cout << *m_Name << endl;
	}
	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;
};
  1. 测试用例过程:理论上先调用父类构造,再调用子类构造,再调用子类重写虚函数,再析构子类,最后析构父类。但实际上并没有析构子类,是因为析构父类时无法调用子类的析构函数。
void test501() {
	Animal * animal=new Cat("Tom");
	animal->speak();
	//父类的指针在析构的时候不会调用子类的析构函数,导致子类如果有堆区属性,会造成内存泄露
	delete animal;
}
  1. 虚析构和纯虚析构就是为了解决这个问题,修改父类,在析构函数前加virtual即可,如果使用纯虚析构,会报错,是因为纯虚析构也要有实现,在类外实现它。
class Animal {
public:
	Animal() {
		cout << "Animal 构造函数" << endl;
	}

	//利用虚析构可以解决父类指针释放子类对象时不干净的问题
	//virtual ~Animal() {
		//cout << "Animal 虚析构函数" << endl;
	//}
	//纯虚析构 需要声明也需要有实现的
	//有了纯虚析构,这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 0;
	//纯虚函数
	virtual void speak() = 0;
};
Animal::~Animal() {
	cout << "Animal 纯虚析构函数" << endl;
}
总结
  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类没有堆区数据,可以不写虚析构或纯虚析构
  3. 拥有纯虚析构的类也属于抽象类
7.多态案例:电脑组装

电脑主要组成部件为CPU,显卡,内存条
将每个零件抽象成基类,并且提供不同的厂商的Intel,Lenovo的零件
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作

test
compute1
compute2
compute3
IntelCPU.calculate
IntelVideoCard.display
LenovoMemory.storage

抽象出每个零件的类
class CPU 抽象类
{
//抽象计算函数
virtual void calculate()=0;
}
class VideoCard 抽象类
{
//抽象显示函数
virtual void display()=0;
}
class Memory 抽象类
{
//抽象存储函数
virtual void storage()=0;
}
电脑类
class Computer
{
构造函数中传入三个零件
提供工作的函数
{
调用每个零件工作的接口
}
}
具体零件厂商
Inter 厂商
class IntelCpu:public CPU
{
void calculate()
{
cout<<"Intel的CPU开始计算了”
}
}
诸如此类

  1. 创建三种零件基类:三大零件的功能做纯虚函数,让不同厂商的不同零件重写
class CPU
{
public:
	virtual void calculate() = 0;
};
class VideoCard
{
public:
	virtual void display() = 0;
};
class Memory
{
public:
	virtual void storage() = 0;
};
  1. 创建电脑组装类:组装三台零件作为成员指针;构造函数将传入的零件赋给该电脑对象的成员;电脑工作函数将调用三大零件的功能函数;最后需要析构三种零件。
class Compute
{
public:
	Compute(CPU* cpu, VideoCard* videocard, Memory* memory)
	{
		cout << "构造电脑" << endl;
		m_Cpu = cpu;
		m_Videocard = videocard;
		m_Memory = memory;
	}
	void dowork()
	{
		cout << "电脑工作了" << endl;;
		m_Cpu->calculate();
		m_Videocard->display();
		m_Memory->storage();
	}
	~Compute()
	{
		if (m_Cpu != NULL || m_Videocard != NULL || m_Memory != NULL)
		{
			cout << "Compute 析构cpu,videocard,memory" << endl;
			delete m_Cpu, m_Videocard, m_Memory;
			m_Cpu = NULL;
			m_Videocard = NULL;
			m_Memory = NULL;
		}
	}
private:
	CPU* m_Cpu;
	VideoCard* m_Videocard;
	Memory* m_Memory;
};
  1. 创建零件厂商类:Intel和Levono其实差不多,重写虚函数
//Intel厂商
class IntelCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Intel CPU start calculate" << endl;
	}
};
class IntelVideoCard:public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel VideoCard start display" << endl;
	}
};
class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel Memory start storage" << endl;
	}
};
//Lenovo厂商
class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo CPU start calculate" << endl;
	}
};
class LenovoVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Lenovo VideoCard start display" << endl;
	}
};
class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo Memory start storage" << endl;
	}
};
  1. 测试用例:创建电脑对象new一个对象传入三种零件,所以需要new三种厂商零件,然后电脑对象调用工作函数,最后释放内存。
void test601()
{
	cout << "test601()" << endl;
	//第一台电脑零件,电脑的零件并没有随电脑释放而释放
	CPU *intelcpu = new IntelCPU;
	VideoCard *intelcard = new IntelVideoCard;
	Memory *intelmemory = new IntelMemory;
	Compute *compute1=new Compute(intelcpu,intelcard,intelmemory);
	compute1->dowork();
	delete compute1;
	//第二台电脑
	Compute *compute2 = new Compute(new IntelCPU, new IntelVideoCard, new IntelMemory);
	compute2->dowork();
	delete compute2;	
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值