C++:类和对象:多态

news2025/7/16 18:22:09

1:多态的基本概念

多态就是指多种状态,它是 C++面向对象三大特性之一。

多态分为两类

1:静态多态:函数重载和运算符重载。

2:动态多态:派生类和虚函数实现运行时多态。

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

1:静态多态的函数地址早绑定 -----编译阶段确定函数地址

2:动态多态的函数地址晚绑定 -----运行阶段确定函数地址

案例:假设我们有个动物类Animal ,还有个继承自动物类的 猫类  Cat。 动物类和猫类都有一个 speak成员函数,分别打印:动物在说话和 猫在说话。

现在如果我们有一个 doSpeak函数,函数的参数是动物类的引用 Animal& animal,现在我们创建一个 猫类对象 cat,然后以引用的方式传入 doSpeak函数。我们看看结果是调用 动物类还是猫类的 成员函数。

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

class Animal {
public:
	void speak() {
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal {
public:
	void speak() {
		cout << "小猫在说话" << endl;
	}
};

// 父类的引用指向一个 接收的子类对象
// 允许父类和子类之间的类别转换
// 地址是 早绑定,可以在编译阶段确定函数地址
void doSpeak(Animal& animal) {
	animal.speak();
}

void test() {
	Cat cat;
	doSpeak(cat);
}

int main() {
	test();
	return 0;
}

 

 打印结果表明:虽然传入的是猫类的引用,但是doSpeak函数是以动物类的引用 去接收 猫类对象,然后在调用 speak() ,所以speak函数的地址是早绑定的,不管传入什么类的对象都会调用动物类的 speak函数。

如何解决这个问题?

引入虚函数

在Animal 的speak成员函数前面添加 virtual ,使得Animal 的speak成员函数成为一个虚函数,然后在子类中重写这个 speak函数。

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

class Animal {
public:
	// 将speak函数声明为虚函数,那么编译器在编译的时候就不能确定函数调用地址。
	virtual void speak() {
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal {
public:
	// 重写父类的speak函数: 函数返回值,函数名,参数列表完全相同。
	// 子类重写:可以不加 virtual
	void speak() {
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal {
public:
	void speak() {
		cout << "小狗在说话" << endl;
	}
};

// 我们希望 它具备类型 java多态的功能
// 1: 传入什么对象,那么就调用什么对象的函数
// 2: 如果函数地址在编译阶段就能确定,那么就是 静态联编
// 3:如果函数地址在运行阶段才能确定,那么就是 动态联编
void doSpeak(Animal& animal) {
	animal.speak();
}

// 多态满足的条件
// 1: 有继承关系
// 2: 子类重写父类中的虚函数
// 多态使用 : 父类指针或引用指向子类对象
void test() {
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}

int main() {
	test();
	return 0;
}

 

运行结果可知

传入小猫对象,就可以打印小猫在说话

传入小狗对象,就可以打印小狗在说话

事项了运行时多态,即函数地址晚绑定。 

总结:

1:动态多态满足条件:有继承关系,子类重写父类中的虚函数。

2:动态多态使用条件:父类指针或引用指向子类对象。

3:重写:函数返回值,函数名,参数列表完全一直称为重写。 

2:多态原理剖析 

案例:我们分别创建两个类Animal1 和 Animal2 ,其中Animal1没有实现动态多态,Animal2实现了动态多态,然后看下它们的大小分别是多少。

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

class Animal1 {
public:
	void speak() {
		cout << "动物1在说话" << endl;
	}
};

class Animal2 {
public:
	virtual void speak() {
		cout << "动物2在说话" << endl;
	}
};

int main() {
	cout << "the size of 原始 = " << sizeof(Animal1) << endl;
	cout << "the size of 多态 = " << sizeof(Animal2) << endl;
	return 0;
}

 

从打印结果我们可以这样推测

1:由于成员函数和类时分开存储的,所以 Animal1 是一个空类,大小为1个字节,这个没什么疑问

2:而Animal2大小为 4个字节,也就是一个 int 指针大小,这个指针我们称为虚函数指针,指向虚函数表,而虚表里面保存的就是 Animal类的成员函数 speak地址。(下面我们通过VS开发人员工具看看结构)

Animal2 内存结构

 1:可以看到Animal2 大小为4字节:也就是一个 int 指针大小,这个指针我们称为虚函数指针,指向虚函数表,而虚表里面保存的就是 Animal类的成员函数 speak地址。

 2: 在看下 Animal3 的结构

可以看到:Animal3 大小也是4字节,继承了 Animal2的虚函数指针,指向了 Animal3类的虚函数表。Anmial3类的虚函数表中保存的也是 Animal2类成员函数speak地址。

下面我们通过一张图来深化总结一下。 

总结:

1:子类继承父类时,会将父类的虚函数表和虚函数指针都继承下来

2:如果子类重写父类的虚函数时,子类中的虚函数表中的内容就会替换成子类的虚函数地址,而父类中的虚函数表内容不变,这时如果父类的指针或者引用指向子类对象,就会发生多态。

4:纯虚函数和抽象类 

在多态中,通常父类中虚函数的实现时无意义的,因为实际场景都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。

纯虚函数语法:

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

当类中有了纯虚函数,那么这个类也被称为 抽象类。

抽象类特点:

1:无法实例化对象

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

案列: 

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

class Base {
public:
	// 纯虚函数
	// 类中只要有一个纯虚函数就是把这个类称为 抽象类
	// 抽象类无法实例化对象
	// 子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void speak() = 0;
};

class Son :public Base {
public:
	virtual void speak() {
		cout << "son speak 调用" << endl;
	}
};

int main() {
	Base* base = NULL;
	// base = new Base;  // error 抽象类无法实例化对象
	base = new Son;
	base->speak();
	delete base;
	return 0;
}

打印结果可知:

如果父类中包含纯虚函数,那么他就是个抽象类,子类重写父类的抽象函数,父类的指针指向子类时,如果调用父类的纯虚函数,这时会调用子类重写后的虚函数。 

6:虚析构和纯虚析构 

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

案例:创建一个父类 Animal ,添加纯虚成员函数 speak,在构造函数和析构函数中添加打印语句。创建一个子类 Cat, ,重写speak函数,在构造函数和析构函数中添加打印语句,创建一个父类指针指向子类对象,然后父类指针调用父类的speak函数,我们看下构造函数和析构函数情况。

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

class Animal {
public:
	// 纯虚函数
	// 类中只要有一个纯虚函数就是把这个类称为 抽象类
	// 抽象类无法实例化对象
	// 子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void speak() = 0;

	Animal() {
		cout << "Animal的构造函数调用" << endl;
	}

	~Animal() {
		cout << "Animal的析构函数调用" << endl;
	}
};

class Cat :public Animal {
public:
	string* m_Name;
	virtual void speak() {
		cout << "小猫在说话" << endl;
	}
	Cat(string name) {
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}
	~Cat() {
		if (m_Name)
		{
			cout << "Cat析构函数调用" << endl;
			delete(m_Name);
			m_Name = NULL;
		}
	}
};

int main() {
	Animal* animal = new Cat("Tom");
	animal->speak();
	delete animal;
	return 0;
}

运行结果可知:并没有调用子类的析构函数,导致子类堆区上的数据没有被释放,会造成内存泄漏。

原因就是:

 我们使用父类指针指向子类对象,父类指针在析构时,没有调用子类的析构代码,导致子类如果在堆区的数据会出现内存泄漏的情况。

解决方案:需要将父类中的析构函数修改为 虚析构或者纯虚析构。

1:将析构函数修改为虚析构函数 

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

class Animal {
public:
	// 纯虚函数
	// 类中只要有一个纯虚函数就是把这个类称为 抽象类
	// 抽象类无法实例化对象
	// 子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void speak() = 0;

	Animal() {
		cout << "Animal的构造函数调用" << endl;
	}

	virtual ~Animal() {
		cout << "Animal的析构函数调用" << endl;
	}
};

class Cat :public Animal {
public:
	string* m_Name;
	virtual void speak() {
		cout << "小猫在说话" << endl;
	}
	Cat(string name) {
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}
	~Cat() {
		if (m_Name)
		{
			cout << "Cat析构函数调用" << endl;
			delete(m_Name);
			m_Name = NULL;
		}
	}
};

int main() {
	Animal* animal = new Cat("Tom");
	animal->speak();
	delete animal;
	return 0;
}

 

打印结果:可以调用子类的析构函数。 

2:将析构函数修改为纯虚析构函数  

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

class Animal {
public:
	// 纯虚函数
	// 类中只要有一个纯虚函数就是把这个类称为 抽象类
	// 抽象类无法实例化对象
	// 子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void speak() = 0;

	Animal() {
		cout << "Animal的构造函数调用" << endl;
	}

	virtual ~Animal() = 0;
};

Animal::~Animal() {
	cout << "Animal的纯虚析构函数调用" << endl;
}

class Cat :public Animal {
public:
	string* m_Name;
	virtual void speak() {
		cout << "小猫在说话" << endl;
	}
	Cat(string name) {
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}
	~Cat() {
		if (m_Name)
		{
			cout << "Cat析构函数调用" << endl;
			delete(m_Name);
			m_Name = NULL;
		}
	}
};

int main() {
	Animal* animal = new Cat("Tom");
	animal->speak();
	delete animal;
	return 0;
}

运行可知:成功调用了Cat的析构函数。 

总结

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

-----都可以解决父类指针释放子类对象

------都需要有具体的函数实现

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

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

3:虚析构语法:

virtual ~类名() {}

4: 纯虚析构语法:

virtual ~类名() = 0;

类名:: ~类名() {} 

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

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

相关文章

set(关联性容器)

众所周知,不会set的人就不会c(你自己编的吧!),到底什么是set呢?我们今天就来了解一下set的奥秘. set是啥 set 作为一个容器也是用来存储同一数据类型的数据类型&#xff0c;并且能从一个数据集合中取出数据&#xff0c;在set 中每个元素的值都唯一&#xff0c;而且系统能根据…

Nacos学习笔记 (5)Nacos整合SpringBoot流程

前提&#xff0c;先下载Nacos并启动 Nacos Server。 1. Nacos 融合 Spring Boot 为注册配置中心 实现&#xff1a; 通过 Nacos Server 和 nacos-config-spring-boot-starter 实现配置的动态变更&#xff1b;通过 Nacos Server 和 nacos-discovery-spring-boot-starter 实现服…

16、Mysql高级之锁问题

16、Mysql高级之锁问题 文章目录16、Mysql高级之锁问题1、锁概述2、锁分类3、Mysql 锁4、MyISAM 表锁4.1 如何加表锁5.2.2 读锁案例4.3、写锁案例4.4、结论4.5、查看锁的争用情况5、InnoDB 行锁5.1、行锁介绍5.2、背景知识5.3、InnoDB 的行锁模式5.4、案例准备工作5.5、行锁基本…

机器学习模型-BUPA liver disorders-探索饮酒与肝炎关系(论文,科研,医疗信息化诊断系统用)

肝炎是由细菌、病毒、寄生虫、酒精、药物、化学物质、自身免疫等多种致病因素引起的肝脏炎症的统称。儿童及成年人均可患病&#xff0c;病毒感染导致的病毒性肝炎较为常见。 过渡饮酒是肝炎重要因素 过渡饮酒会引发下述血检指标异常&#xff0c;暗示肝炎发生。 酒精相关性肝病…

使用Nordic的nrf52840实现蓝牙DFU过程

需要用到的软件&#xff1a; 交叉编译环境&#xff1a;gcc-arm-none-eabi MinGW&#xff1a;下载 Python&#xff1a;下载 micro-ecc源码&#xff1a;下载 nRFUtil&#xff1a;下载或者直接使用python的pip来安装 手机app&#xff1a;nRF Toolbox或者nRF Connect 安装 gcc-…

【爬虫】JS调试解决反爬问题系列3—sign破解

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

LabVIEW如何减少下一代测试系统中的硬件过时 1

LabVIEW如何减少下一代测试系统中的硬件过时 1 许多测试系统的问题是&#xff0c;整个系统运行的时间必须超过单个系统组件的支持时间。有时被测试的设备有几十年的有效使用寿命&#xff0c;而许多测试仪器已经过时&#xff0c;在5年或更短的时间后就不再支持了。其他时候&…

技能梳理32@电源防反接电路+光耦隔离电路+串口磁耦隔离电路

电源防反接电路 CN2是个防反接插座&#xff1b; F1是个自恢复保险丝&#xff1b; MB6S是个整流桥电路&#xff0c;主要是这个模块实现的防反接效果&#xff1b; SPX3819是个电平转换模块&#xff0c;封装挺小的&#xff1b;LDO-EN是用来控制SPX3819是否使能的&#xff1b; …

等保2.0参与医院网络安全管理的重要性

随着现代医院 IT 技术架构的演变、新兴技术的引入&#xff0c;来自医院内外部的各种安全风险不断出现&#xff0c;对医院网络安全提出了更多挑战&#xff0c;医院网络安全在技术层面和管理层面都亟待完善。为此&#xff0c;借鉴相关法律法规、行业标准等&#xff0c;提出提升现…

Web前端开发神器WebStorm v2022.3发布——支持新的CSS功能

WebStorm是一款JavaScript 开发工具。被广大中国JS开发者誉为"Web前端开发神器""最强大的HTML5编辑器""最智能的JavaSscript IDE"等。与IntelliJ IDEA同源&#xff0c;继承了IntelliJ IDEA强大的JS部分的功能。 WebStorm v2022.3官方正式版下载…

百度工程师教你玩转设计模式(装饰器模式)

作者 | 北极星小组 想要写好代码&#xff0c;设计模式&#xff08;Design Pattern&#xff09;是必不可少的基本功&#xff0c;设计模式是对面向对象设计&#xff08;Object Oriented Design&#xff09;中反复出现的一类问题的一种解决方案&#xff0c;本篇介绍装饰器模式&am…

(第一章)OpGL超级宝典学习:配置和超级宝典相同的工作环境

目录前言配套资源配置解压文件夹复制资源HOWTOBUILD什么是CMake什么是GLFW安装CMake开始构建build glfw生成debug和release的lib库build sample推送结语前言 最近发现学习好像到了一定的瓶颈&#xff0c;马上要到2023年了&#xff0c;想要在新的一年开始后对自己有一定的突破&a…

Kubernetes 实现自动扩容和自愈应用实践

Kubernetes 自动扩容和自愈 tags: 实践 文章目录Kubernetes 自动扩容和自愈1. 背景2. 准备3. kind 部署 kubernetes4.实践4.1 部署 deployment4.2 创建 Service4.3 创建 Ingress4.4 部署 Ingress-nginx4.5 K8s 实现自愈4.6 k8s 实现自动扩容5. 其他1. 背景 在生产非 kubernet…

java进阶—List

上节回顾 List 是一个有序的&#xff0c;允许重复的Collection&#xff0c;其下的子类主要有 ArrayList LinkedList,Vector(这个官方不推荐使用) 那么同为list的子类&#xff0c;ArrayList 跟 LinkedList 有什么区别呢&#xff1f; 这里就涉及到了list的底层两种实现方式&…

《计算机体系结构量化研究方法》第2章-存储器层次结构设计 2.1 引言

一、概述 1、存储器层次结构&#xff1a;层次由速度和容量各不相同的存储器组成。 2、存储器层次结构被分为几个级别——离处理器越近&#xff0c;容量越小速度越快。 3、包含性质&#xff1a;大多数情况下&#xff0c;低层级存储器中的数据是上一级存储器中数据的超集。比如…

基于RSA的数字签名设计与实现

信息安全课程的第二个实验&#xff0c;主要是用java、js&#xff0c;前端页面用的html写的。 页面成果展示&#xff1a; 基本公私钥生成 实验环境为win10系统&#xff0c;使用git命令行工具——git bash生成公私钥。生成私钥&#xff0c;密钥长度为1024bit并从私钥中提取公钥 …

如何在 Spring Boot 项目中开启 “热部署“

目录 1. 添加热部署框架支持 2. 设置当前项目 Settings 和新项目 Settings 开启项目自动编译 3. 开启运行中热部署 4. 使用 Debug 启动项目 (社区版 IDEA) 1. 添加热部署框架支持 在 pom.xml 中添加如下框架引用: <dependency><groupId>org.springframework.b…

vue3中常用的三种组件传值方式

比较大型的项目中经常会涉及到多个组件之间进行传值&#xff0c;所以对之前用过的一些传值方法做个笔记&#xff0c;还有就是对不同的情况下需要使用哪一种传值方法更合适的思维进行一个总结 vue3中常用的三种组件传值方式mitt依赖注入pinia总结mitt 因为vue3去掉了全局事件总…

qt实现的pdf阅读器(二)--XpdfReader在linux下的编译

目录 1.简介 2.需求说明 3.编译 3.3. 下载源码 3.2. 移植安装包和依赖库 3.2.1 准备工作 3.3.2 了解 3.3.3 编译并安装zlib 3.3.4 编译并安装libpng 3.3.5 编译并安装lcms 3.3.6 编译并安装freetype 3.3.7 编译xpdf 3.3.8 查看运行编译好的xpdf 1.简介 Xpdf 是一个免…

我以为自己MySQL够牛逼了,直到看到了Alibaba的面试题

前言 众所周知&#xff0c;简历上“了解&#xff1d;听过名字&#xff1b;熟悉&#xff1d;知道是啥&#xff1b;熟练&#xff1d;用过&#xff1b;精通&#xff1d;做过东西”。 相信大家对于MySQL的索引都不陌生&#xff0c;索引(Index)是帮助MySQL高效获取数据的数据结构。…