探索C++中的多态性:理解虚函数和运行时多态

news2024/11/17 7:17:09

前言:

在现代软件开发中,面向对象编程(OOP)已经成为了主流。其中一个强大的概念就是多态性(Polymorphism),它不仅仅是一种技术,更是一种设计思想和实现方式,为软件开发带来了巨大的灵活性和可维护性。

多态性允许我们使用统一的接口来处理不同类型的对象,同时根据对象的实际类型来调用适当的方法。这种动态绑定的特性不仅提高了代码的复用性和可扩展性,还使得软件系统能够更好地适应变化和需求的增加。

本博客将深入探讨C++语言中多态性的各个方面:从基本概念到实际应用,从虚函数到虚函数表,从继承到接口隔离。我们将探索多态如何通过简单而强大的机制实现复杂的程序设计目标,以及它如何成为你面向对象编程旅程中的重要里程碑。

一、多态的概念

多态多态,简单地说,就是多种形态。具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

举个例子,同样是某个app的用户,一个是普通用户,一个是vip用户,当他们两个去点击操作同样的功能时,前者会提示你是普通用户,需要额外付费使用,后者则会提示您是vip用户,可直接使用。

更具体的来说,就是生活中去买票,军人去买票有着优先的特权,学生去买票可以优惠,而大多数普通人就只能全额购买。这三种不同的结果,就是我们说的多态。

同样是叫这个动作,狗叫是汪,猫叫是喵,羊叫是咩,这也是不同的结果。

二、多态的构成条件

多态是在继承的基础上出现的:不同继承关系的类对象,去调用了同一函数,却产生了不同的行为。

在此基础上,要构成多态还需要两个条件:

1、必须通过基类的指针或者引用调用虚函数。

2、被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

class Person
{
public:
	 void fun()
	{
		cout << "原价" << endl;
	}
};

class Student :public Person
{
public:
	 void fun()
	{
		cout << "半价" << endl;
	}
};

void Get_ticket()
{
	people.fun();
}


int main()
{
	Person a;
	Student b;

	Get_ticket(a);
	Get_ticket(b);

	return 0;
}
我们知道,在以上代码中,a是个Person类对象,b是个Student类对象,当我们让这两个对象做同一个动作(买票)时,得到的结果却都是原价。(Person & people引用派生类的做法在继承那一文章中讲过,详见 http://t.csdnimg.cn/jCGZZ)。
以上的两个类中的fun的关系是重定义(隐藏),而当我们是这把两个函数变为虚函数试试呢?这样关系就变为了重写(覆盖)
class Person
{
public:
	virtual void fun()
	{
		cout << "原价" << endl;
	}
};

class Student :public Person
{
public:
	virtual void fun()
	 {
		cout << "半价" << endl;
	}
};

结果就不在相同了,a运行的结果就是原价,b的结果则为半价。

因为这满足了多态运行的条件,也就是说,这就是多态的典型运行,明明调用的同一个函数Get_ticket,得到的结果却截然不同。

以上满足的三个条件:1、两个类是继承关系,在继承体系中。

2、两个fun都是虚函数,满足派生类对虚函数进行了重写。

3、通过基类的指针或者引用调用函数,也就是Person& people,我们把Student类型引用给了一个person类型,随后调用的就是Student重写后的fun函数。

注意:

当基类虚函数返回基类对象指针或者引用,派生类虚函数对象返回派生类指针或者引用时,也是特殊的重写关系,被称之为:协变。

另外,如果基类的析构函数是虚函数,那么此时的派生类虚函数只要定义,不管是否加了virtual关键字,都会与基类的虚函数构成重写,二者名字虽然不一样,但是编译器对析构函数的名称做了特殊处理,编译后的析构函数的名称统一处理为destructor。

三、override final

在某些疏忽的情况下,可能会导致某种差错,这种错误是在编译时不会报出的,因此,C++11补充了两个关键字, override final ,可以帮助我们检测是否构成了重写。

1、final:修饰虚函数表示该虚函数不能再被重写

class Person
{
public:
	virtual void fun ()final
	{
		cout << "原价" << endl;
	}
};

 2、override:检测派生类虚函数是否重写了基类的某个虚函数,若没有则编译报错。

class Student :public Person
{
public:
	virtual void fun()override
	{
		cout << "半价" << endl;
	}
};

四、纯虚函数与抽象类

在虚函数后面加上一个“=0”,那么这个虚函数就叫做纯虚函数,拥有纯虚函数的类叫做抽象类抽象类不能实例化出对象。

派生类继承抽象类后,也不能实例化出对象,只有对该纯虚函数进行重写后,才能实例化出对象。这样纯虚函数就规范了派生类必须进行重写。

class A
{
public:
	virtual void fun() = 0;//纯虚函数
};

class B :public A
{
public:
	int a;
};

int main()
{
	A a;//无法实例化,会编译报错
	B b;//无法实例化,会编译报错
	return 0;
}

以上A是一个抽象类,无法实例化,B继承了A,但没有对纯虚函数进行重写,所以仍然不能实例化,二者都会编译报错。

正确的解决方法是在B里对纯虚函数进行重写:

class A
{
public:
	virtual void fun() = 0;//纯虚函数
};

class B :public A
{
public:
	virtual void fun()
	{
		;
	}
};

int main()
{
	A a;//无法实例化,会编译报错
	B b;
	return 0;
}

这样B就能实例化出对象了。

五、多态的原理

1、虚函数表

class Base
{
public:
	virtual void fun()
	{
		cout << "Base" << endl;
	}
private:
	int a;
};

int main()
{
	cout << sizeof(Base) << endl;
	return 0;
}

对于以上代码,输出的结果是什么呢?

在32为环境下,打印出的结果为8,我们知道,int类型的a占据4个字节,那么还有四个字节是在哪来的呢?

这就是虚函数表的原因,因为我们类中存在虚函数,就会生成一个专门的虚函数表指针,这个指针就占据了四个字节大小(32位系统下)。

派生类对象中也会有一个虚表指针,这个对象由两部分组成,一部分是继承父类的的成员,另一部分是派生类自身定义的属性和方法。在继承的时候,派生类会复制一份父类的虚表,如果在派生类中对某个虚函数进行了重写,就会对复制下来的虚表中的相应的虚函数进行覆盖操作(这也是重写被叫做覆盖的原因)。虚函数表本质上就是存放虚函数指针的数组,通常情况下这个数组最后存放了一个nullptr。

注意,虚函数与普通函数一样,都存放在代码段,只是它的函数指针存放在了虚表中,而对象中存放的是虚表指针,通过虚表指针找到虚表,随后又找到对应的虚函数。

int main()
{
	int i = 0;
	static int j = 1;
	int* p1 = new int;
	const char* p2 = "xxxxxx";

	printf("栈:%p\n", &i);
	printf("静态区:%p\n", &j);
	printf("堆:%p\n", p1);
	printf("常量区:%p\n", p2);

	Base b;
	Core c;

	printf("Base虚表指针地址:%p\n", *((int*)&b));
	printf("Core虚表指针地址:%p\n", *((int*)&c));
	printf("Base对象地址:%p\n", &b);
	printf("Core对象地址:%p\n", &c);
	return 0;
}

由此可知对象在栈区,虚表在常量区代码段,虚函数表的指针在对象里,虚函数也在常量区代码段。

2、多态的原理

class Person
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-全价" << endl; 
	}
};

class Student : public Person 
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-半价" << endl;
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

当p指向的是mike对象时,p->BuyTicket在mike的虚表中找到的函数是:Person::BuyTicket。p指向johnson对象时,p->BuyTicket在johnson的虚表中找到的函数是:Student::BuyTicket。这样一来,就实现了不同对象去完成同一行为时,展现出不同的情况。

3、动态绑定与静态绑定

面向对象的多态性表现在两大类:静态与动态。

静态绑定又称前期绑定,在程序编译期间就确定了程序的行为,例如函数重载。

动态绑定又称后期绑定,是在程序运行期间,通过具体拿到的类型确定程序的具体行为,调用具体的函数,也就是本文所主要讲的多态。

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

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

相关文章

Python批量采集某东评论,实现可视化分析

女朋友没事就喜欢网购&#xff0c;买一大堆又不用&#xff0c;总说不合适&#xff0c;为了不让她花冤枉钱&#xff0c;于是我决定用Python写一个采集商品评论的脚本&#xff0c;然后对商品进行分析&#xff0c;这样就不怕踩到坑了&#xff01; 让我们直接开始本次操作 准备工作…

PPT模板背景音乐去除攻略:3个方法教你轻松删除自带背景乐!

PPT模板中自带的背景乐却找不到明显的播放图标&#xff0c;同时在幻灯片切换效果中也已选择“无声音”&#xff0c;但播放时仍有背景乐&#xff0c;这可能是由于音乐被嵌入到了幻灯片母版中。 针对这种情况&#xff0c;以下是一些去掉背景音乐的步骤&#xff1a; 方法一&…

【C++】C++11的新特性 --- lambda表达式 ,新的类功能,模块的可变参数 , emplace系列接口

如果你停止,就是谷底! 如果你还在继续,就是上坡! 这是我听过关于人生低谷最好的阐述。 -- 刘同 C11的新特性 1 lambda表达式1.1 基本用法1.2 细谈参数列表与捕捉列表 2 新的类功能2.1 移动构造与移动赋值2.2 default和delete 3 模块的可变参数4 emplace系列接口Thanks♪(&…

国防科技大学深圳地区新生欢送会圆满举行

2024年7月28日&#xff0c;第97个八一建军节来临之际&#xff0c;在这个充满希望的盛夏时节&#xff0c;深圳地区迎来了13名即将踏入国防科技大学的优秀学子。 为了庆祝这一荣耀时刻&#xff0c;并表达对新生的深切祝福&#xff0c;在国防科技大学深圳校友会黄丹会长的积极倡议…

小白也能读懂的ConvLSTM!(开源pytorch代码)

ConvLSTM 1. 算法简介与应用场景2. 算法原理2.1 LSTM基础2.2 ConvLSTM原理2.2.1 ConvLSTM的结构2.2.2 卷积操作的优点 2.3 LSTM与ConvLSTM的对比分析2.4 ConvLSTM的应用 3. PyTorch代码参考文献 仅需要网络源码的可以直接跳到末尾即可 1. 算法简介与应用场景 ConvLSTM&#x…

“手撕”MySQL的索引

目录 二、索引的作用 三、索引的缺点 四、如何使用索引 查看索引&#xff1a; 创建索引&#xff1a; ​编辑 删除索引&#xff1a; 五、索引的底层原理 那什么是B树&#xff0c;什么是B树呢&#xff1f; B树的好处&#xff1a; 总结&#xff1a; 一、什么是索引 索…

OpenCV 图像预处理—图像金字塔

文章目录 相关概念高斯金字塔拉普拉斯金字塔应用 构建高斯金字塔为什么要对当前层进行模糊&#xff1f;1. 平滑处理2. 减少混叠&#xff08;Aliasing&#xff09;3. 多尺度表示4. 图像降采样 举个栗子创建高斯金字塔和拉普拉斯金字塔&#xff0c;并用拉普拉斯金字塔恢复图像 相…

《汇编语言 基于x86处理器》- 读书笔记 - 第3章-汇编语言基础

《汇编语言 基于x86处理器》- 读书笔记 - 第3章-汇编语言基础 3.1 基本语言元素3.1.1 第一个汇编语言程序常见汇编语言调用规范 3.1.2 整数常量&#xff08;基数、字面量&#xff09;3.1.3 整型常量表达式3.1.4 实数常量十进制实数十六进制实数&#xff08;编码实数&#xff09…

使用git命令行的方式,将本地项目上传到远程仓库

在国内的开发环境中&#xff0c;git的使用是必不可少的。Git 是一款分布式版本控制系统&#xff0c;用于有效管理和追踪文件的变更历史及协作开发。本片文章就来介绍一下怎样使用git命令行的方式&#xff0c;将本地项目上传到远程仓库&#xff0c;虽然现在的IDE中基本都配置了g…

Ubuntu安装terminator教程

Terminator 是一个高级的终端仿真器,专为 Linux 和 Unix 系统设计。它的主要特点是提供了丰富的多窗口和多标签功能,使用户能够在一个窗口中管理多个终端会话。这对于系统管理员、开发人员以及需要同时运行多个命令行任务的用户来说,极为方便。 一、安装 1、更新包 sudo a…

使用Selenium爬虫批量下载AlphaFold数据库中的PDB文件

注意&#xff1a;本方法使用了python&#xff0c;下载速度一般&#xff0c;如果需要更快的大批量下载可以考虑使用其他方法&#xff0c;例如FTP Alphafold数据库其实提供了许多物种的蛋白质组&#xff1a; AlphaFold Protein Structure Database 但是如果你搜索的物种不在这个…

算法面试leadcode【经典150道】

88 合并两个有序数组 方法一 使用arraycopy排序 * 思路一&#xff1a;将nums2合并到nums1的尾部&#xff0c;再直接进行排序。* 使用arraycopy(int[]nums1,int m,int[] nums2,int n)* 方法来进行排序&#xff0c;* 从原数组的哪个位置&#xff0c;移动到原数组的哪个位置&#…

xxl-job适配达梦数据库并制作镜像、源码部署xxl-job

背景&#xff1a;因项目需要信创&#xff0c;需将原本的mysql数据库&#xff0c;改成达梦数据库 一、部署达梦数据库 1.1 部署达梦数据库服务 可参考&#xff1a;Docker安装达梦数据库_达梦数据库docker镜像-CSDN博客 PS&#xff1a;部署达梦数据库时&#xff0c;需加上大小…

Java | Leetcode Java题解之第300题最长递增子序列

题目&#xff1a; 题解&#xff1a; class Solution {public int lengthOfLIS(int[] nums) {int len 1, n nums.length;if (n 0) {return 0;}int[] d new int[n 1];d[len] nums[0];for (int i 1; i < n; i) {if (nums[i] > d[len]) {d[len] nums[i];} else {int…

19. Revit API: Parameter(参数)

一、前言 我们在前面或多或少提到也用到参数了&#xff0c;这篇便细讲一下。 首先&#xff0c;我们知道好多信息都藏在参数里&#xff0c;或者说可以从参数中获取。我们还能够通过调整参数的值&#xff0c;改变模型的形态&#xff0c;即族的参变。 其次&#xff0c;有时族上…

【CAN通讯系列4】CAN通讯如何传递信号?

在【CAN通讯系列3】如何学习CAN通讯&#xff1f;中举了一个例子&#xff1a;新能源汽车要实现驱动功能&#xff0c;先需要整车控制器VCU计算目标转速或扭矩请求等信号&#xff0c;再通过CAN通讯传递给电机控制器MCU&#xff0c;就这个例子继续探讨CAN通讯的基础问题。 1 CAN数据…

入门 PyQt6 看过来(案例)08~ 页面布局

主题&#xff1a;学习页面布局控件以及布局容器的使用&#xff08;理论知识&#xff09; 1 布局控件 PyQt6的布局方式包括绝对布局、水平布局、垂直布局、网格布局和表单布局。 绝对布局&#xff1a;直接设置控件对象在参考坐标中的位置水平布局&#xff1a;对加入的控件对象从…

引用的项目“xxxx/tsconfig.node.json”可能不会禁用发出。

vue3 报错&#xff1a; 引用的项目“xxxx/tsconfig.node.json”可能不会禁用发出。 解决&#xff1a; 进入对应的 json 文件&#xff1a; 修改&#xff1a; "noEmit": false 当 noEmit 设置为 false 时&#xff0c;TypeScript 编译器将根据项目配置生成相应的输出文…

【数据结构初阶】单链表经典算法题十道(详解+图例)—得道飞升(终篇)

hi &#xff01; 目录 9、 环形链表 || 10、随机链表的复制 终章 9、 环形链表 || 【图解】 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; struct ListNode *detectCy…