C++之 多态(Polymorphism)

news2024/11/14 14:00:18

目录

一、基本概念

多态的使用:

        案例一——计算机类

多态的优点:

二、纯虚函数与抽象类

特点:

①无法实例化对象

②子类必须重写父类中的纯虚函数,否则也属于抽象类

        案例二——制作饮品

三、虚析构与纯虚析构

因为父类指针在析构时,不会调用子类中的析构函数,从而导致堆区属性未释放 

解决:将父类虚构改为虚析构

案例三——电脑组装


一、基本概念

多态是C++三大特性之一

分为、作用与区别:

作用区别
静态多态函数重载 和 运算符重载,复用函数名函数地址早绑定,编译阶段确定函数地址
动态多态派生类 和 虚函数 实现运行时多态函数地址晚绑定,运行阶段确定函数地址

动态多态需要满足:

①有继承关系

②子类重写父类的虚函数

重写:子类的 返回值 函数名 形参列表 要与父类完全相同,但virtual可写可不写


多态的使用:

父类指针或引用子类对象 


例:

创建Animal与Cat与Dog类,其中每个都有dodif函数,最后单独实现dodif函数,创建测试函数并调用dodif函数

class Animal
{
public:
	void dodif()
	{
		cout << "动物" << endl;
	}
};
class Cat : public Animal
{
public:
	void dodif()
	{
		cout << "小猫" << endl;
	}
};
class Dog : public Animal
{
public:
	void dodif()
	{
		cout << "小狗" << endl;
	}
};
void dodif(Animal &animal)// Animal &animal = 子类传来的对象; 多态的使用
{
	animal.dodif();
}
void test01()
{
	Cat cat;
	dodif(cat);
    Cat dog;
	dodif(dog);
}

可以发现,无论我传参是什么,结果总是输出动物,也就是调用父类中的函数 

这是因为dodif函数是静态多态,地址早绑定,在编译阶段就确定了函数地址,因此后面我怎么传参都是访问已经确定的函数地址


因此需要改为动态多态:父类中,函数virtual,使其变成虚函数,地址晚绑定

class Animal
{
public:
	virtual void dodif() // 虚函数
	{
		cout << "动物" << endl;
	}
};


接下来深度分析一下上面代码的实现逻辑

首先,没有加virtual时,我们输出一下父类的大小

 结果是1,说明是个空对象

加上virtual,再次输出

变成8,其实是变成了一个指针,即 虚函数指针cfptr


接下来,画出Animal类的内部结构

使用开发人员命令提示符工具查看

然后是,继承的Cat类的内部结构,此时并无重写 

 虚函数表内仍然是&Animal::dodif

而当我们加上重写

 虚函数表内就改成了&Cat::dodif

 因为重写后,子类中的虚函数表会被替换成子类的虚函数地址


        案例一——计算机类

使用普通方法多态2种方法实现计算器类

普通方法

class caculator
{
public:
	int getResult()
	{
		cin >> num1 >> oper >> num2;
		if (oper == "+")
		{
			return num1 + num2;
		}
		else if (oper == "-")
		{
			return num1 - num2;
		}
		else if (oper == "*")
		{
			return num1 * num2;
		}
	}
	string oper;
	int num1;
	int num2;
};
void test01()
{
	caculator c;
	int ret = c.getResult();
	cout << ret << endl;
}

现在实现了加法、减法和乘法三种功能,但是如果想要增加功能,则需要修改源码

实际开发中提倡开闭原则:对扩展进行开放,对修改进行关闭

②多态的方法

class caculator_abstract // 计算器抽象类
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int num1;
	int num2;
};

class Add:public caculator_abstract  // 加法 Addition
{
	int getResult()
	{
		return num1 + num2;
	}
}; 
class Sub :public caculator_abstract // 减法 subtraction
{
	int getResult()
	{
		return num1 - num2;
	}
};
class Mul :public caculator_abstract // 乘法 multiplication
{
	int getResult()
	{
		return num1 * num2;
	}
};
class Div :public caculator_abstract // 除法 division
{
	int getResult()
	{
		return num1 / num2;
	}
};

调用

void test02()
{
	// 多态的使用方法:
	//父类指针或者引用子类对象
	caculator_abstract* c = new Add; // 加法调用
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl;
	delete c;// 堆区的数据,记得销毁

	c = new Sub;
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl; // 减法调用
	delete c;

	c = new Mul;
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl; // 乘法调用
	delete c;

	c = new Div;
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl; // 除法调用
	delete c;
}

多态的优点:

①组织结构清晰

②可读性强

③对于扩展以及维护能力高


二、纯虚函数与抽象类

在多态中,通常父类中虚函数的实现没有什么意义,主要是调用子类重写的内容

因此可将父类中的虚函数改为纯虚函数


纯虚函数:写一个虚函数,使其 = 0,即为纯虚函数

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

而当类中有了纯虚函数,这个类也称为抽象类


特点:

①无法实例化对象

class Base
{
public:
	virtual void func() = 0;
};

void test01()
{
	Base b;
	new Base;
}

 无论是栈上还是堆区都无法实例化对象


②子类必须重写父类中的纯虚函数,否则也属于抽象类

首先,不重写:

class Son :public Base
{
public:
	
};

void test01()
{
	Son s;
	new Son;
}

 一样的无法实例化对象

加上重写

class Son :public Base
{
public:
	virtual void func() {}; // 重写
};

即使重写是空实现,仍然可以实例化对象

简单修改一下

class Son :public Base
{
public:
	virtual void func() 
	{
		cout << "Son_func的调用" << endl;
	}; // 重写
};

void test01()
{
	Son s;
	s.func();
	 // 多态的方法:
	Base* base = new Son;
	base->func();
}

 


        案例二——制作饮品

简单制作一份饮品,咖啡or沏茶

咖啡:①煮水②冲泡咖啡③倒入杯中④加糖与牛奶

沏茶:①煮水②冲泡茶叶③倒入杯中④加柠檬

流程大致相同:①煮水②冲泡③倒入杯中④加佐料

class AbstactDrinking // 抽象类的饮品
{
public:
	virtual void Boil() = 0;
	virtual void Brew() = 0;
	virtual void Pour() = 0;
	virtual void Put() = 0;
	void makeDriking()
	{
		Boil();
		Brew();
		Pour();
		Put();
	}
};
class tea :public AbstactDrinking // 茶的具体实现
{
public:
	virtual void Boil()
	{
		cout << "煮水" << endl;
	}
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	virtual void Pour()
	{
		cout << "倒入杯中" << endl;
	}
	virtual void Put() 
	{
		cout << "加柠檬" << endl;
	}
};
class coffee :public AbstactDrinking // 咖啡的具体实现
{
public:
	virtual void Boil()
	{
		cout << "煮水" << endl;
	}
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}
	virtual void Pour()
	{
		cout << "倒入杯中" << endl;
	}
	virtual void Put()
	{
		cout << "加糖与牛奶" << endl;
	}
};
void doDrinking(AbstactDrinking* abd)
{
	abd->makeDriking();
	delete abd; // new出的堆区数据,记得销毁
}
void test01()
{
	doDrinking(new tea);
	cout << "-----------------" << endl;
	doDrinking(new coffee);
}

 没啥说的


三、虚析构与纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,导致子类堆区属性未被释放,引起内存泄漏

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


共同点不同点
虚析构可以解决父类指针释放子类对象\
纯虚析构都需要有具体的函数实现属于抽象类,无法实例化对象

语法:

虚析构virtual~类名() {};
纯虚析构virtual~类名() = 0 ; 声明
类名 : : 类名() {} ;实现

接下来创建父类Animal类与子类Cat类

class Animal
{
public:
	virtual void speak() = 0;
};
class Cat:public Animal
{
public:
	virtual void speak()
	{
		cout << "小猫" << endl;
	}
};
void test01()
{
	Animal* animal = new Cat;
	animal->speak();
	delete animal;
}

接下来在Cat类中增加string属性c_name; 

class Cat:public Animal
{
public:
	Cat(string name)
	{
		c_name = new string(name); // 堆区创建,应有释放
	}
	virtual void speak()
	{
		cout << *c_name << "小猫" << endl;
	}
	string *c_name;
};
void test01()
{
	Animal* animal = new Cat("yomi");
	animal->speak();
	delete animal;
}

 而c_name是堆区开辟的内存,应有释放,补全Cat的析构:

	~Cat()
	{
		if (c_name != NULL)
		{
			cout << "Cat的析构" << endl;
			delete c_name;
			c_name = NULL;
		}
	}

同时,我们把父类Animal的析构和构造以及子类Cat的构造也补全:

class Animal
{
public:
	Animal()
	{
		cout << "Animal的构造" << endl;
	}
	~Animal()
	{
		cout << "Animal的析构" << endl;
	}
	virtual void speak() = 0;
};
class Cat:public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat的构造" << endl;
		c_name = new string(name); // 堆区创建,应有释放
	}
	virtual void speak()
	{
		cout << *c_name << "小猫" << endl;
	}
	string *c_name;

	~Cat()
	{
		if (c_name != NULL)
		{
			cout << "Cat的析构" << endl;
			delete c_name;
			c_name = NULL;
		}
	}
};

运行可以发现,没有子类Cat的析构,说明堆区new出的c_name属性并未释放

因为父类指针在析构时,不会调用子类中的析构函数,从而导致堆区属性未释放 


解决:将父类虚构改为虚析构

	virtual ~Animal()
	{
		cout << "Animal的析构" << endl;
	}

就走了子类的析构 


接下来实现纯虚析构

class Animal
{
public:
	Animal()
	{
		cout << "Animal的构造" << endl;
	}

	virtual ~Animal() = 0; // 纯虚析构

	virtual void speak() = 0; // 虚函数
};

如果只在父类中写 virtual ~Animal() = 0;  运行将报错

 

因为这个相当于声明,没有实现

接下来在类外实现纯虚析构实现

Animal::~Animal() // 纯虚析构的实现
{
	cout << "Animal的纯虚析构" << endl;
}

因为如果父类有开辟堆区的属性,也需要释放,因此需要纯虚析构的实现

同时,当类中有了纯虚析构,这个类也属于抽象类,无法实例化对象


 总结:

①虚析构或纯虚析构是用来解决通过父类指针释放子类对象

②如果子类没有堆区数据,可以不写虚析构或纯虚析构

③纯虚析构的类也属于抽象类,无法实例化对象


案例三——电脑组装

电脑主要组成部件是处理器(计算)+显卡(显示)+内存条(存储)


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

class CPU
{
public:
	// 抽象计算函数
	virtual void calculate() = 0;
};
class GraphicsCard
{
public:
	// 抽象显示函数
	virtual void display() = 0;
};
class Memory
{
public:
	// 抽象存储函数
	virtual void storage() = 0;
};

具体厂商零件: 

// 具体零件厂商
// 1、Inter
class InterCPU :public CPU
{
	virtual void calculate()
	{
		cout << "Inter_CPU" << endl;
	}
};
class InterGPU :public GraphicsCard
{
	virtual void display()
	{
		cout << "Inter_GPU" << endl;
	}
};
class InterRAM :public Memory
{
	virtual void storage()
	{
		cout << "Inter_RAM" << endl;
	}
};
// 2、lenovo
class LenovoCPU :public CPU
{
	void calculate()
	{
		cout << "Lenovo_CPU" << endl;
	}
};
class LenovoGPU :public GraphicsCard
{
	void display()
	{
		cout << "Lenovo_GPU" << endl;
	}
};
class LenovoRAM :public Memory
{
	void storage()
	{
		cout << "Lenovo_RAM" << endl;
	}
};

 

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

// 电脑类的实现
class Computer
{
public:
	Computer(CPU* cpu, GraphicsCard* gpu, Memory* ram)
	{
		c_cpu = cpu;
		c_gpu = gpu;
		c_ram = ram;
	}
	void dowork() // 工作接口
	{
		c_cpu->calculate();
		c_gpu->display();
		c_ram->storage();
	}
private:
	CPU* c_cpu;
	GraphicsCard* c_gpu;
	Memory* c_ram;
};

测试时组装3种不同的电脑进行工作

void test01()
{
	// 创建第一台电脑的零件
	CPU* interCpu = new InterCPU;
	GraphicsCard* interGpu = new InterGPU;
	Memory* interRam = new InterRAM;
	// 创建第一台电脑
	Computer* computer1 = new Computer(interCpu, interGpu, interRam);
	computer1->dowork(); // 调用工作函数
	delete computer1;
}

在computer1工作完后,我们将其delete,但仍有一个问题:

上面new出的3个零件尚未delete,下面有2个方法

①在最后delete computer后,接着delete3个零件

②在电脑类中,提供析构函数,释放3个零件,下面实现方法②

	~Computer()
	{
		if (c_cpu != NULL) // 释放cpu
		{
			delete c_cpu;
			c_cpu = NULL;
		}
		if (c_gpu != NULL) // 释放gpu
		{
			delete c_gpu;
			c_gpu = NULL;
		}
		if (c_ram!= NULL) // 释放ram
		{
			delete c_ram;
			c_ram = NULL;
		}
	}

这样就解决了堆区的问题


创建另外2台电脑

2:

void test01()
{
		// 创建第二台电脑的零件
	CPU* lenovoCpu = new LenovoCPU;
	GraphicsCard* lenovoGpu = new LenovoGPU;
	Memory* lenovoRam = new LenovoRAM;
	// 创建第二台电脑
	Computer* computer2 = new Computer(lenovoCpu, lenovoGpu, lenovoRam);
	computer2->dowork(); // 调用工作函数
	delete computer2;
}

 3:

	// 创建第三台电脑的零件
	CPU* InterCpu = new InterCPU;
	GraphicsCard* InterGpu = new InterGPU;
	Memory* lenovoRam = new LenovoRAM;
	// 创建第三台电脑
	Computer* computer3 = new Computer(InterCpu, InterGpu, lenovoRam);
	computer3->dowork(); // 调用工作函数
	delete computer3;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】结构体嵌套结构体

目录 1、缘起 2、结构体嵌套结构体 3、总结 1、缘起 结构体嵌套结构体 是一种数据组织方式&#xff0c;就像 俄罗斯套娃 一样&#xff0c;一个数据结构可以包含另一个数据结构。这种嵌套结构使得程序可以更加灵活地处理数据&#xff0c;从而更好地满足复杂的需求。类比生活中…

Java之~ Aop自定义注解日志

大纲步骤&#xff1a; 一&#xff0c;创建需要记录的日志表&#xff0c;创建基础方法。&#xff08;省略&#xff09; 二&#xff0c;在需要加记录日志的方法上加Aop注解1&#xff0c;创建一个注解类&#xff0c;Aop中定义一个注解import java.lang.annotation.*; /*** http 请…

银行数字化转型导师坚鹏:商业银行零售业务数字化风控

商业银行零售业务数字化风控课程背景&#xff1a; 数字化背景下&#xff0c;很多银行存在以下问题&#xff1a; 不清楚商业银行数字化风控发展现状&#xff1f; 不清楚对公业务数字化风控工作如何开展&#xff1f; 不知道零售业务数字化风控工作如何开展&#xff1f; 课程特…

十三、RNN循环神经网络实战

因为我本人主要课题方向是处理图像的&#xff0c;RNN是基本的序列处理模型&#xff0c;主要应用于自然语言处理&#xff0c;故这里就简单的学习一下&#xff0c;了解为主 一、问题引入 已知以前的天气数据信息&#xff0c;进行预测当天(4-9)是否下雨 日期温度气压是否下雨4-…

拦截器 JWT SpringBoot 多环境开发 本地文件上传 阿里云OSS存储 异常处理

Springboot&#xff1a; 是&#xff1a;由pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化spring应用的初始搭建以及开发过程 作用&#xff1a;简化Spring的环境搭建和代码开发 使用原理&#xff1a;就是boot提前写好了一些maven的工程和jar包&#xff0c;程序员在…

Android屏幕适配dp、px两套解决办法

最新最全文章(2018-08-25)&#xff1a;Android dp方式的屏幕适配-原理(后期补充完整讲解)_手机dp输出是横屏还是竖屏_android阿杜的博客-CSDN博客 “又是屏幕适配&#xff0c;这类文章网上不是很多了吗&#xff1f;” 我也很遗憾&#xff0c;确实又是老问题。但本文重点对网上…

ChatGPT来势凶猛,公有云格局会不会大变?

【引言】&#xff1a; AI风暴来袭&#xff0c;全球无人幸免。 但公有云与ChatGPT到底啥关系&#xff1f; 1) 公有云与ChatGPT&#xff0c;到底谁会“吃”掉谁&#xff1f; 【科技明说 &#xff5c; 热点关注】在看到公有云厂商纷纷开始大模型发布&#xff0c;开始GPT的融入之…

虹科教您 | 基于Linux系统的虹科RELY-TSN-KIT套件操作指南(2)——操作演示

RELY-TSN-KIT是首款针对TSN的开箱即用的解决方案&#xff0c;它可以无缝实施确定性以太网网络&#xff0c;并从这些技术复杂性中抽象出用户设备和应用。该套件可评估基于IEEE 802.1AS同步的时间常识的重要性&#xff0c;并借助时间感知整形器来确定性地交付实时流量&#xff0c…

EJBCA搭建

EJBCA搭建 前言&#xff1a; 本次EJBCA搭建使用操作系统为centos 7&#xff0c;shell用户为root。 1.下载jdk环境&#xff1a; // 下载jdk yum install -y java-1.8.0-openjdk-devel // 查看java版本 java -version 本人jdk环境&#xff1a; openjdk version "1.8.…

计算机自动化有哪些SCI期刊推荐? - 易智编译EaseEditing

以下是计算机自动化领域的一些知名SCI期刊&#xff1a; IEEE Transactions on Automation Science and Engineering&#xff1a; 该期刊由IEEE&#xff08;电气和电子工程师协会&#xff09;出版&#xff0c;涵盖了自动化科学和工程领域的研究&#xff0c;包括自动控制、人工…

算法记录 | Day35 贪心算法

860.柠檬水找零 思路&#xff1a; 只需要维护三种金额的数量&#xff0c;5&#xff0c;10和20。 有如下三种情况&#xff1a; 情况一&#xff1a;账单是5&#xff0c;直接收下。情况二&#xff1a;账单是10&#xff0c;消耗一个5&#xff0c;增加一个10情况三&#xff1a;账…

九、Locust运行与配置

1. 配置 1.1 环境变量 也可以通过环境变量设置选项。它们通常与命令行参数相同&#xff0c;但大写并带有前缀LOCUST_&#xff1a; 在 Linux/macOS 上&#xff1a; $ LOCUST_LOCUSTFILEcustom_locustfile.py locust在 Windows 上&#xff1a; > set LOCUST_LOCUSTFILEcu…

MySQL的安装与卸载(Centos7.9环境下,全篇图文手把手安装教程)

前言 在安装MySQL之前&#xff0c;我们先来看看MySQL如何卸载。如果从未安装过MySQL的任何版本&#xff0c;可以直接跳过这部分。 如果已经安装过MySQL&#xff0c;一定要确保自己的环境中的MySQL相关文件删除干净&#xff0c;否则重新安装时可能会出现一些错误 tips&#xf…

问界M9全剧透:华为的「科技豪华」样板间

作者 | 德新 编辑 | 王博 今天在nova 11的发布会上&#xff0c;曝了一个意外——问界M9提前剧透。实际上这车要今年四季度才上市。「本来没打算这么早发出来&#xff0c;前阵子网上有一个很老的设计图在传&#xff08;实在太丑&#xff09;。没办法&#xff0c;干脆先讲讲」…

DriveGPT、车企订单背后,为什么毫末每年都能搞出新东西?

作者 | 祥威 编辑 | 德新 4月11日&#xff0c;毫末智行正式发布自动驾驶生成式大模型 DriveGPT&#xff0c;中文名 雪湖海若&#xff0c;可以提升自动驾驶认知能力&#xff0c;最终提升规控效率。 雪湖海若的核心&#xff0c;是将各种驾驶场景作为Token输入到模型中&…

《Netty》从零开始学netty源码(三十九)之PoolSubPage的内存分配

目录 PoolSubPage.allocategetNextAvail方法toHandle方法removeFromPool方法 PoolSubPage.allocate 上一篇我们介绍了PoolSubPage的简单知识&#xff0c;当我们需要PoolSubPage的内存时可调用allocate方法查找可分配二进制的位置&#xff0c;具体的源码过程如下&#xff1a; …

ctfshow web入门命令执行web74-118

1.web74 还是先扫目录 payload: c$anew DirectoryIterator(glob:///*);foreach($a as $f){echo($f->__toString()." ");}exit(0); #扫描根目录有什么文件 c$anew DirectoryIterator(glob:///*);foreach($a as $f){echo($f->getFilename()." ");} …

N32G430学习笔记20--- spi外设单工模式下spi1中断发送和spi2中断数据接收

基本知识 spi主模式下使用硬件NSS(3个条件缺一不可): 设置硬件NSS模式设置NSS电平为低电平使能NSS输出spi主模式下使用软件NSS: 设置软件NSS模式 使能NSS输出 SPI_SS_Output_Enable(SPI1);//NSS 使能 SPI接线方式(MISO 和MOSI反着接) SPI1->SLCK=PB3 <–> SPI2-…

在SPRO为定制表创建节点

确定透明表已创建&#xff0c;允许维护&#xff0c;并且生成维护视图。保证SM30可以正常维护。 2.在已有的节点增加还是新增&#xff0c;如果在以有的节点增加&#xff0c;需要查看已有节点的Enhancement ID 。 查看方法具体请点击。 举个例子&#xff1a;我们想在Z-business C…

每日学术速递4.17

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.DreamPose: Fashion Image-to-Video Synthesis via Stable Diffusion 标题&#xff1a;DreamPose&#xff1a;通过稳定扩散实现时尚图像到视频合成 作者&#xff1a;Johanna Karr…