进阶了解C++(4)——多态

news2024/11/15 23:25:27

       在上篇文章中,简单的介绍了多态中的概念以及其相关原理。本文将针对多态中其他的概念进一步进行介绍,并且更加深入的介绍关于多态的相关原理。

目录

1. 抽象类:

2. 再谈虚表:

3. 多继承中的虚函数表:


1. 抽象类:

       在上篇文章中提到了,如果使用关键字virtual修饰一个成员函数,则这个成员函数被称为虚函数。此处,针对虚函数进行扩展,如果在虚函数的声明后面加上=0,则这个函数被称为纯虚函数。包含纯虚函数的类又叫抽象类,其特点是不能初始化出对象。即使是子类继承这个类,同样也不能初始化出对象。只有认为对纯虚函数进行重写,才能初始化出一个对象。

    给定一个抽象类及其子类如下:

//抽象类
class Person
{
public:
	virtual void func() = 0
	{
		cout << "Person-func()";
	}
};

class Teacher : public Person
{
public:

};

class Student : public Person
{
public:

};

如果向初始化出这三个类的对象,即:
 

int main()
{
	Person p;
	Student s;
	Teacher t;
}

此时编译器报错如下:

如果对子类中继承父类中的纯虚函数进行重写,即:

class Teacher : public Person
{
public:
	virtual void func()
	{
		cout << "Teacher-func()" << endl;
	}
};

class Student : public Person
{
public:
	virtual void func()
	{
		cout << "Student-func()" << endl;
	}
};

此时再去分别初始化两个子类的对象,即:

int main()
{

	Student s;
    s.func();
	Teacher t;
    t.func();
}

代码可以正常运行,且运行结果如下:

2. 再谈虚表:

在之前C++基础的文章中提到了,在构造函数中,存在初始化列表,初始化列表初始化成员变量的顺序并不是根据初始化列表的顺序,而是根据成员变量声明的顺序。对于虚函数,其也符合这个特性。具体可以用下面的代码进行证明:

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

通过监视窗口,查看对象b中虚表:

       可以看到,虚函数在虚表中存放的顺序,正是虚函数在类中声明的顺序。对于这一点,也同样可以在内存窗口中进行查看。

       

 从图中不难发现,对象b中的第一个地址,恰好对应了虚表指针的地址。此时再查看虚表中的内容,即:

不难看出,再内存窗口中,第二,第三条地址分别对应了虚表中两个虚函数的地址。

而对于子类,其生成的对象d中的内容如下:

对于子类对象的内容,可以分为两个部分,一是从父类中继承的内容,二是子类中自己的成员变量以及函数。在监视窗口中,可以看到子类继承了父类的虚表,并且对其中进行重写的虚函数的地址进行了覆盖。但是需要注意,在子类中,并不存在自己的虚表 。对于子类虚表中的函数指针如下:
在上面给出的图片中可以看出,蓝线连接的两个地址分别是父类、子类中的虚函数Func1(),但是因为这个函数在子类中发生了重写,因此,父类,子类中这两个虚函数的地址并不相同。

而对于紫线连接的两个虚函数,由于虚函数并未在子类中发生虚函数的重写,因此,父类,子类中俩个虚函数的地址相同。

如果对于子类,再添加一个虚函数,例如:

class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}

	virtual void Func()
	{
		;
	}
private:
	int _d = 2;
};

 此时,在监视窗口中进行查看,子类对象的虚表中并没有出现新的虚函数的函数指针,但是在内存窗口中,却出现了一条新的地址,对于这个新的地址,一般认为就是子类中新加入的虚函数。至于具体的验证,将在文章后面给出。 

 

(注:为了方便演示,下面的代码在x86,即32位环境下运行)

在之前C++基础关于内存管理的文章中(C++(9)——内存管理-CSDN博客 )提到了系统根据不同的需求,将内存划分为不同的部分,具体如下:

1.栈:用于存储非全局、非静态的局部变量,函数参数,返回值等等

2.堆:用于程序运行时的内存的动态开辟

3.数据段(静态区):用于存储全局变量和静态变量

4.代码段(常量区):可执行代码\只读常量

在给出了上述概念后,文章将探讨一个 问题,即:虚表指针是存储在什么地方的。

为了方便测试,首先给出上面四个类型变量的地址,即:

int i = 1;//栈
	int* p = new int;//堆
	static int j = 0;//数据段(静态区)
	const char* p2 = "xxxxxxx";//代码段(常量区)

	printf("栈=%p\n", &i);

	printf("堆=%p\n", p);
	printf("静态区=%p\n", &j);
	printf("常量区=%p\n",p2);

打印结果如下:

对于如何获取虚表指针,本文提供一种方法:由于虚表指针存储在一个类的前四个字节,因此,只需要初始化出一个该类的对象,首先获取这个对象的指针,在将这个指针强转成int*类型,即可获取虚表指针,具体代码如下:

Base* B = &b;
	Derive* D = &d;

	printf("B=%p\n", *(int*)B);
	printf("D=%p\n", *(int*)D);

打印结果如下:

从上述区段以及两个虚表指针的指针对比来看,虚表指针应该存储在常量区,也就是代码段。

上面给出了如何获取虚表指针的存储地址,下面给出虚表中,如何获取虚表中存储各个虚函数的指针,具体方法如下:

typedef void(*VF_PTR)();
void PrintVF(VF_PTR* vf)
{
	for (size_t i = 0; vf[i] != nullptr; i++)
	{
		printf("[%d] :%p", i, vf[i]);
	}
}
PrintVF((VF_PTR*) * (int*)&d);

打印结果如下:

如果在获取了上述指针后,直接调用这些函数指针,便可知道上述 获取的地址是否是类中的虚函数,即:
 

typedef void(*VF_PTR)();
void PrintVF(VF_PTR* vf)
{
	for (size_t i = 0; vf[i] != nullptr; i++)
	{
		printf("[%d] :%p", i, vf[i]);
		VF_PTR f = vf[i];
		f();
	}
}

打印结果如下:

通过这个例子可以看出,虽然在上面添加新的虚函数Func3()时,在子类的虚表中并没有看到这个函数的地址,但是在次数,照样可以通过函数指针调用这个函数,这也间接证明了Func3()其实添加到了子类中,只是在监视窗口不可见。

3. 多继承中的虚函数表:

给定代码如下:

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};
class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

int main()
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	return 0;
}

在上面给出的代码中,存在三个类,其中Base1,Base2被集成到了Derive中,由于Base1最先被继承到子类中,因此,可以认为,父类成员在子类的空间中的位置是最靠前的。对于&d表示取对象d的首地址,由于父类成员在空间中位置是最靠前的,因此,理论上p1==&d。而对于p2,由于其在Base1后继承,因此p2相对于p1是靠后的,因此,在子类中,存在着两张虚表,这两个虚表分别有着自己独立的地址。在监视窗口中,同样可以证明这一点:

而对于Derive中的虚函数func3(),为了验证func3()是存储在哪个虚表中的,可以用下面的代码进行检验:

PrintVF((VF_PTR*)*(int*)p1);

对于Base1中虚表中存储的函数指针打印结果如下:

下面打印Base2中虚表中的函数指针:

由此证明,子类中的虚函数func3()是存储在子类继承并且进行覆盖的Base1中的虚表。

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

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

相关文章

(每日持续更新)jdk api之PipedInputStream基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

Web APIs知识点讲解(阶段二)

DOM-事件基础 一.事件 1.事件 目标&#xff1a;能够给 DOM元素添加事件监听 事件:事件是在编程时系统内发生的动作或者发生的事情&#xff0c;比如用户在网页上单击一个按钮 事件监听:就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函…

【BBuf的CUDA笔记】十四,OpenAI Triton入门笔记三 FusedAttention

0x0. 前言 继续Triton的学习&#xff0c;这次来到 https://triton-lang.org/main/getting-started/tutorials/06-fused-attention.html 教程。也就是如何使用Triton来实现FlashAttention V2。对于FlashAttention和FlashAttention V2网上已经有非常多的介绍了&#xff0c;大家如…

华为配置WLAN高密业务示例

配置WLAN高密业务示例 组网图形 图1 配置高密WLAN环境网络部署组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 体育场由于需要接入用户数量很大&#xff0c;AP间部署距离较小&#xff0c;因此AP间的干扰较大&#xff0c;可能导致用户上网网…

ShardingJdbc实战-ShardingJdbc配置及读写分离

文章目录 一、项目搭建二、测试结果1.访问http://localhost:8085/user/save2.访问http://localhost:8085/user/listuser 一、项目搭建 新建一个Spring Boot工程 引入依赖-sharding、ssm、数据库驱动 <properties><java.version>1.8</java.version><shardi…

linux系统Jenkins工具的node节点配置

Jenkins工具添加节点 node 节点的作用node节点配置 node 节点的作用 分布式构建&#xff1a;通过添加多个节点&#xff0c;可以在多台计算机上并行执行构建任务&#xff0c;从而加快构建速度和提高效率。节点可以是物理计算机、虚拟机、云实例或容器等。扩展计算能力&#xff…

仓储自动化新解:托盘四向穿梭车驶入智能工厂 智能仓储与产线紧密结合

目前&#xff0c;由于对仓库存储量的要求越来越高&#xff0c;拣选、输送以及出入库频率等要求也越来越高&#xff0c;对此&#xff0c;在物流仓储领域&#xff0c;自动化与智能化控制技术得以快速发展&#xff0c;货架穿梭车在自动库领域的应用越来越广泛。现阶段&#xff0c;…

皇冠测评:网络电视盒子哪个品牌好?电视盒子排行榜

欢迎各位来到我们的测评频道&#xff0c;本期我们要分享的产品是电视盒子&#xff0c;因很多网友留言不知道网络电视盒子哪个品牌好&#xff0c;我们通过为期一个月的测评后整理了电视盒子排行榜&#xff0c;想买电视盒子的可以看看下面这五款产品&#xff0c;它们各方面表现非…

MySQL安装部署-NDB版

NDB&#xff08;Network Database&#xff09;是网络数据库&#xff0c;其架构是由MySQL Server集群以及NDB存储引擎集群组成&#xff0c;是存算分离架构&#xff0c;MySQL Server主要是负责计算、NDB存储引擎主要负责数据存储&#xff0c;其特点是支持高可用、支持无单点故障、…

SOCKS55代理与Http代理有何区别?如何选择?

在使用IPFoxy全球代理时&#xff0c;选择 SOCKS55代理还是HTTP代理&#xff1f;IPFoxy代理可以SOCKS55、Http协议自主切换&#xff0c;但要怎么选择&#xff1f;为解决这个问题&#xff0c;得充分了解两种代理的工作原理和配置情况。 在这篇文章中&#xff0c;我们会简要介绍 …

输入一个字符串,将其中的数字字符移动到非数字字符之后

输入一个字符串&#xff0c;将其中的数字字符移动到非数字字符之后&#xff0c;并保持数字字符贺非数字字符输入时的顺序。 代码&#xff1a; #include <cstdio> #include <queue> using namespace std; int main() {char str[200];fgets(str, 200, stdin);//读入…

10分钟SkyWalking与SpringBoot融合并整合到Linux中

1.依赖配置 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.0.RELEASE</version></dependency><dependency><groupId>org.springframe…

经销商文件分发 怎样兼顾安全和效率?

经销商文件分发是指将文件、资料、产品信息等从制造商或经销商传递给经销商的过程。这一过程对于确保经销商能够获取最新的产品信息、销售策略、市场活动资料等至关重要。 想要管理众多经销商合作伙伴之间的文件传输并提高效率&#xff0c;可以采取以下措施&#xff1a; 1、建…

2024 2.24~3.1 周报

目录 一、本周计划 二、DD-Net整体介绍 三、DDNet的体系结构 四、损失函数 五、课程学习 六、实验环境 A. SEG盐数据集 B. OpenFWI数据集 C. 训练和前沿设置&#xff08;未完&#xff09; 七、结论 八、跑代码——对比试验结果&#xff08;CBAM&#xff09; 1. In…

Redis 之五:Redis 的主从复制

概念 主从复制&#xff0c;是指将一台 Redis 服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(master)&#xff0c;后者称为从节点(slave)&#xff1b;数据的复制是单向的&#xff0c;只能由主节点到从节点。 默认情况下&#xff0c;每台Redis服务器都是主节…

Spark Shuffle Tracking 原理分析

Shuffle Tracking Shuffle Tracking 是 Spark 在没有 ESS(External Shuffle Service)情况&#xff0c;并且开启 Dynamic Allocation 的重要功能。如在 K8S 上运行 spark 没有 ESS。本文档所有的前提都是基于以上条件的。 如果开启了 ESS&#xff0c;那么 Executor 计算完后&a…

C#用户界面,UI设置密码隐藏显示

两种方法&#xff0c;第一种&#xff0c;在文本框属性设置UseSystemPasswordChar为True。 这里默认是以黑点隐藏显示。 第二种&#xff0c;在文本框属性设置隐藏显示的符号&#xff0c;这里设置为星号*。

探索Android多屏互动技术:构建无缝交互体验

探索Android多屏互动技术&#xff1a;构建无缝交互体验 1. 简介 在当前移动设备和智能家居应用中&#xff0c;多屏互动技术已经成为一个备受关注的话题。随着移动设备&#xff08;如智能手机、平板电脑&#xff09;和智能家居设备的普及&#xff0c;用户对于多屏协同工作、娱…

基于springboot实现流浪动物救助网站系统项目【项目源码+论文说明】

基于springboot实现流浪动物救助网站系统演示 摘要 然而随着生活的加快&#xff0c;也使很多潜在的危险日益突显出来&#xff0c;比如在各种地方会发现很多无家可归的、伤痕累累的、可怜兮兮的动物&#xff0c;当碰到这种情况&#xff0c;是否会立马伸出双手去帮助、救助它们&…

重磅功能!EasyBoss ERP正式接入Lazada本土店全托管,赋能商家轻松出海东南亚

近两年&#xff0c;在跨境电商圈出现了一个新的名词——“全托管模式”&#xff0c;随着部分跨境电商巨头借助全托管模式大获成功&#xff0c;全托管在跨境电商领域掀起一股热潮&#xff0c;吸引着越来越多的平台与卖家布局探索。 作为东南亚头部电商平台的Lazada也在2023年4月…