Bootstrap

C++面向对象编程 基础篇(10)类和对象-多态

4.7.1 多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

  • 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class Animal {
public:

	//函数前面加virtual被称为虚函数
    //虚函数编译器在编译的时候就不能确定函数调用了
	void virtual speak() {
		cout << "动物再叫!" << endl;
	}
};

class Cat : public Animal {
public:
	void speak() {
		cout << "猫再叫!" << endl;
	}
};

class Dog : public Animal {
public:
	void speak() {
		cout << "狗再叫!" << endl;
	}
};

//执行说话函数
//地址如果早绑定 在编译阶段确定函数地址 执行Animal的speak方法
//如果想执行猫说话,那么地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态的两个条件
//1、必须有继承关系
//2、子类重写父类的虚函数(重写:函数返回值类型、函数名称、参数列表完全相同 虚函数在父类里的函数名前加virtual)

//动态多态使用
//父类的指针或引用 执行子类对象
void dospeak(Animal& animal) {
	animal.speak();
}

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

	Dog dog;
	dospeak(dog);
}

void main() {
	test();
}

总结:

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

动态多态的虚函数是把原本的函数替换成了一个虚函数指针(vfptr)指向虚函数地址,如果继承了就会继承这个虚函数地址,子类如果重写该虚函数,就会覆盖这个虚函数地址,成子类的虚函数地址。

请添加图片描述

4.7.2 多态案例一 计算机类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

示例

//普通实现
/*
class Calculator {
public:
	int num_1;
	int num_2;
	int jisuan(string fh) {
		if (fh == "+") {
			return num_1 + num_2;
		}
		else if(fh == "-") {
			return num_1 - num_2;
		}
		else if (fh == "*") {
			return num_1 * num_2;
		}
		else if (fh == "/" && num_2 != 0) {
			return num_1 / num_2;
		}
		else {
			cout << "不是正确的运算符!" << endl;
			return 0;
		}
	}
	//如果想扩展新的功能,需求修改代码
	//在真实的开发中,提倡 开闭原则
	//开闭原则: 对扩展进行开发,对修改进行关闭
};

void test() {
	Calculator c1;
	c1.num_1 = 8;
	c1.num_2 = 2;
	int num = c1.jisuan("-");
	cout << "结果为:" << num << endl;
}

void main() {
	test();
}

*/

//多态实现
/*
多态实现好处:
1、组织结构清晰,出错可以快速定位
2、可读性强
3、便于前后期扩展和维护
*/
class AbstractCalculator {
public:
	int num_1;
	int num_2;
	virtual int jisuan() {
		return 0;
	}
};

//加法计算器类
class Add :public AbstractCalculator {
public:
	virtual int jisuan() {
		return num_1 + num_2;
	}
};

//减法计算器类
class Sub :public AbstractCalculator {
public:
	virtual int jisuan() {
		return num_1 - num_2;
	}
};

//乘法计算器类
class Mul :public AbstractCalculator {
public:
	virtual int jisuan() {
		return num_1 * num_2;
	}
};

void test1() {
	//多态使用条件
	//父类指针或者引用指向子类对象

	//加法运算

	//通过父类指针指向新建的子类对象的方法进行运算
	AbstractCalculator* abs;//创建一个AbstractCalculator类指针
	abs = new Mul;
	abs->num_1 = 20;
	abs->num_2 = 10;
	int res = abs->jisuan();
	cout<<"加法结果为:"<< res << endl;
	//用完后需要销毁
	delete abs;

	//通过父类的引用指向子类对象的方法进行运算
	Sub sub;
	AbstractCalculator& abc=  sub;
	abc.num_1 = 20;
	abc.num_2 = 10;
	int ress = abc.jisuan();
	cout << "减法结果为:" << ress << endl;
}

void main() {
	test1();
}
new创建类对象与不new区别

下面是自己总结的一些关于new创建对象特点:

  • new创建对象需要指针接收,一处初始化,多处使用
  • new创建对象使用完需delete销毁
  • new创建对象直接使用堆空间,而局部不用new定义对象则使用栈空间
  • new对象指针用途广泛,比如作为函数返回值、函数参数等
  • 频繁调用场合并不适合new,就像new申请和释放内存一样
1、new创建对象例子:
CTest*  pTest = new  CTest();
delete pTest;
pTest用来接收对象指针。

2、不用new,直接使用类定义申明:
CTest  mTest;
此种创建方式,使用完后不需要手动释放,该类析构函数会自动执行。而new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。
CTest*  pTest = NULL;
但使用普通方式创建的类对象,在创建之初就已经分配了内存空间。而类指针,如果未经过对象初始化,则不需要delete释放。

4.7.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是在调用子类重写的内容

因此可以将虚函数改为纯虚函数

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

当类中有纯虚函数时,这个类也成为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

class AbstractCalculator {
public:
	int num_1;
	int num_2;
    //纯虚函数,只要有一个纯虚函数,这个类就被称为抽象类
	virtual int jisuan() = 0;
};
//抽象类子类,如果不重写父类中的纯虚函数,也会被归为抽象类
class vir : public AbstractCalculator{};

void test(){
    AbstractCalculator* abs = new Vir;//报错:不允许使用抽象类类型
    AbstractCalculator abs;//子类不重写父类纯虚函数,也被归为抽象类
}
void main(){
    test();
}
4.7.4 多态案例二-制作饮品

案例描述:

制作饮品大致流程: 煮水 、冲泡、倒入杯中、加入辅料

利用多态技术实现本案例,提供抽象类制作饮品基类,提供子类制作咖啡和茶

冲咖啡:煮水、冲泡咖啡、倒入杯中、加糖和牛奶

泡茶:煮水、冲泡茶叶、倒入杯中、加柠檬

#include <iostream>

using namespace std;

class AbstractDreak {
public:
	void water()//煮水
	{
		cout << "正在煮水" << endl;
	}
	virtual void chong() = 0;//冲泡
	void take() //倒入杯中
	{
		cout << "倒入杯中" << endl;
	}
	virtual void add() = 0;//加调料

	void makedreak() {
		water();
		chong();
		take();
		add();
	}
};

class Coffee : public AbstractDreak {
public:
	virtual void chong() {
		cout << "冲咖啡" << endl;
	}
	virtual void add() {
		cout<<"加入糖和牛奶" << endl;
	}
};

class Tea : public AbstractDreak {
public:
	virtual void chong() {
		cout << "泡茶" << endl;
	}
	virtual void add() {
		cout << "加入柠檬" << endl;
	}
};

void dreaking(AbstractDreak& dreak) {
	dreak.makedreak();
}

void test() {
	cout << "请问你想和coffee或者 tea" << endl;
	string dr;
	cin >> dr;
	if (dr == "coffee") {
		cout<< "正在为您冲泡咖啡" << endl;
		Coffee coffee;
		dreaking(coffee);
	}
	else if (dr == "tea") {
		cout << "正在为您冲泡茶" << endl;
		Tea tea;
		dreaking(tea);
	}
	else {
		cout << "输入错误!" << endl;
	}
}
void main() {
	test();
}
4.7.5 虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

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

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

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

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0

类名::~类名(){}

示例:

#include <iostream>

using namespace std;

class Animal {
public:
	Animal()
	{
		cout << "调用了Animal的构造函数" << endl;
	}
	virtual void speak() {}
	//虚析构函数 可以调用子类的虚析构函数,释放子类开辟的堆空间
	//virtual ~Animal()
	//{
	//	cout << "调用了Animal的虚析构函数" << endl;
	//}
	//纯虚析构函数
	// 有了纯虚析构函数,即使没有其他虚函数,这个类也属于抽象类,无法实例化对象
	//virtual ~Animal() = 0;
};
//Animal::~Animal()
//{
//	cout << "调用了Animal的纯虚析构函数" << endl;
//}

class Dog :public Animal {
public:
	Dog(string name)
	{
		dog_name = new string(name);
		cout << "调用了Dog的构造函数" << endl;
	}
	virtual void speak()
	{
		cout << *dog_name << "小狗在叫" << endl;
	}
	virtual ~Dog()
	{
		cout << "调用了Dog的析构函数" << endl;
		if (dog_name != NULL)
		{
			delete dog_name;
			dog_name = NULL;
		}
	}
	string* dog_name;
};


void test()
{
	//父类指针指向子类对象,父类指针在析构时不会调用子类的析构对象,导致如果子类有堆区属性,无法被释放,会存在内存泄露问题
	Animal* animal = new Dog("大黄");
	animal->speak();
	delete animal;
	Animal a1;
}

void main()
{
	test();
	system("pause");
}

总结:

  1. 虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类
4.7.6多态案例三-电脑组装

案例描述:

电脑主要组成部分为CPU、显卡、硬盘

将每个零件封装出抽象基类,并且提供补贴的厂商生产不同的零件

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

代码:

#include <iostream>
#include <random>
using namespace std;

class CPU {
public:
	virtual void calculation() = 0;
	
};

class Graphics {
public:
	virtual void rendering() = 0;
};

class Disk {
public:
	virtual void savefile() = 0;
	
};

class Intel :public CPU,Graphics,Disk {
public:
	void calculation() {
		cout << "Intel CPU在进行运算" << endl;
	}
	void rendering() {
		cout << "Intel 显卡在进行渲染" << endl;
	}
	void savefile() {
		cout << "Intel 硬盘在进行存储" << endl;
	}
};

class Nvidia :public Graphics {
public:
	void rendering() {
		cout << "Nvidia 显卡在进行渲染" << endl;
	}
};

class AMD :public CPU, Graphics {
public:
	void calculation() {
		cout << "AMD CPU在进行运算" << endl;
	}
	void rendering() {
		cout << "AMD 显卡在进行渲染" << endl;
	}
};

class Kingston :public Disk {
public:
	void savefile() {
		cout << "Kingston 硬盘在进行存储" << endl;
	}
};

class Computer {
public:
	Computer(CPU* cpu, Graphics* grap, Disk* disk) {
		cpu->calculation();
		grap->rendering();
		disk->savefile();
	}
private:
	CPU* c_cpu;
	Graphics* c_grap;
	Disk* c_disk;
};
void Assemble(int cpu,int grap,int disk)
{
	
	Intel intel;
	Nvidia nvidia;
	AMD amd;
	Kingston kingston;
	//使用电脑类进行组装
	//Computer computer(&amd, &nvidia, &kingston);
	if (cpu == 0) {
		intel.calculation();
	}
	else if (cpu == 1) {
		amd.calculation();
	}
	else {
		cout << "CPU品牌有误" << endl;
	}

	if (grap == 0) {
		intel.rendering();
	}
	else if (grap == 1) {
		amd.rendering();
	}
	else if (grap == 2) {
		nvidia.rendering();
	}
	else {
		cout << "显卡品牌有误" << endl;
	}

	if (disk == 0) {
		intel.savefile();
	}
	else if (disk == 1) {
		kingston.savefile();
	}
	else {
		cout << "硬盘品牌有误" << endl;
	}
	
	

	
}

void test() {
	int cpu, grap, disk,i=0;
	cout << "为您生成三套推荐配置:" << endl;
	while (i < 3) {
		cpu = rand() % 2;
		grap = rand() % 3;
		disk = rand() % 2;
		cout << "第"<<i+1<<"套推荐配置如下:" << endl;
		Assemble(cpu, grap, disk);
		i++;
	}
	
	
	
	cout << "您可自定义电脑配置:" << endl;
	cout << "请选择你想要的CPU品牌(Intel or AMD)输入0,1:" << endl;
	cin >> cpu;
	cout << "请选择你想要的显卡品牌(Intel or AMD or Nvidia)输入0,1,2:" << endl;
	cin >> grap;
	cout << "请选择你想要的硬盘品牌(Intel or Kingston)输入0,1:" << endl;
	cin >> disk;

	Assemble(cpu, grap, disk);
	
}

void main() {
	test();
}
;