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

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

目录

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中的虚表。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis-Plus介绍

目录 一、Mybatis-Plus简介 1.1、介绍 1.2、特性 1.3、架构 1.4、Mybatis-Plus与Mybatis的区别 二、快速入门 2.1、首先创建数据库mybatis-plus 2.2、创建user表 2.3、插入数据 2.4、创建Spring-Boot项目 2.5、添加依赖 2.6、连接数据库 一、Mybatis-Plus简介 1.1、…

Springboot项目集成短信验证码(超简单)

操作流程 注册验证码平台创建验证码模版开始集成&#xff08;无需引入第三方库&#xff09; 注册并登陆中昱维信验证码平台 获取AppID和AppKey。 创建验证码模版 创建验证码模版&#xff0c;获取验证码模版id 开始集成 创建controller import org.springframework.web.bi…

跨域引起的两个接口的session_id不是同一个

来源场景&#xff1a; RequestMapping(“/captcha”)接口设置了SESSION_KEY&#xff0c;也能获取到&#xff0c;但是到了PostMapping(“/login”)接口就是空的&#xff0c;由于跨域导致的两个session_id不是同一个 /*** 系统用户 前端控制器*/ Controller CrossOrigin(origins…

自定义el-dialog的样式

实现效果&#xff1a; 样式代码如下&#xff1a;&#xff08;可以写在common.scss文件夹中&#xff09; .el-dialog__header {padding: 16px 20px;border-bottom: 1px solid #DCDFE6;display: flex;align-items: center;.el-dialog__title {font-size: 16px;position: relativ…

MySQL(基础篇)——事务

一.事务简介 事务是一组操作的集合&#xff0c;他是一个不可分割的单位&#xff0c;事务会把所有的操作作色一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 默认MySQL的事务是自动提交的&#xff0c;也就是说&#xff0c…

在VMware中安装CentOS 7并配置Docker

VMware安装CentOS 7 一、介绍 该文章介绍如何使用启动U盘在虚拟机里面安装系统&#xff0c;虚拟机版本为VMware Workstation 16 pro&#xff0c;Linux版本为CentOS Linux release 7.9.2009 (Core)。 二、安装 1、创建虚拟机 点击创建新的虚拟机 选择典型就可以了&#xf…

spring-boot static-path-pattern如何配置生效

WebMvcAutoConfiguration AbstractUrlHandlerMapping ResourceHttpRequestHandler springboot 版本 2.3.9.RELEASE 一、如何用 yaml配置 spring:mvc:static-path-pattern: /doctest/**resources:static-locations: classpath:/doc/资源文件配置 访问路径 二、原理 第一个问…

幻兽帕鲁联机服务器搭建

幻兽帕鲁联机服务器搭建 开通云服务器 云主机购买|香港云服务器|香港云主机|美国云服务器|弹性云主机租用尽在-特网科技 建议选择4核心 16G内存 10M带宽&#xff0c;可满足6-15人游玩 下载安装脚本 windows系统: 下载 http://downinfo.myhostadmin.net/palserver/install…

List集合的Stream流式操作实现数据类型转换

问题现象&#xff1a; 最近在项目中&#xff0c;有一些逻辑想用List集合的Stream流式操作来快速实现&#xff0c;但由于之前没做好学习笔记和总结&#xff0c;导致一时间想不起来&#xff0c;只能用本方法来解决&#xff0c;如下&#xff1a; 可以看出来代码量是比较冗长的&…

day57 集合 List Set Map

List实现类 List接口特点&#xff1a;元素有序 可重复 Arraylist 可变数组 jdk 8 以前Arraylist容量初始值10 jdk8 之后初始值为0&#xff0c;添加数据时&#xff0c;容量为10&#xff1b; ArrayList与Vector的区别&#xff1f; LinkList&#xff1a;双向链表 优点&#xff1…

服务器权限:Error: EACCES: permission denied, open‘/Cardiac/uniquC.csv

背景&#xff1a; 我想在服务器上传一个文件uniquC.csv&#xff0c;但是服务器说我没有权限 解决方案&#xff1a; 1. 查看目前是否存在对文件夹的权限 ls -ld /Cardiac/ # your fold path 此时&#xff0c;我发现 这也意味着root也没有赋予写的权限。 2. 拿到root权限 …

软件设计师软考题目解析10 --每日五题

想说的话&#xff1a;要准备软考了。0.0&#xff0c;其实我是不想考的&#xff0c;但是吧&#xff0c;由于本人已经学完所有知识了&#xff0c;只是被学校的课程给锁在那里了&#xff0c;不然早找工作去了。寻思着反正也无聊&#xff0c;就考个证玩玩。 本人github地址&#xf…

C++基于多设计模式下的同步异步日志系统day1

C基于多设计模式下的同步&异步日志系统day1 &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&am…

死记硬背spring bean 的生命周期

1.bean的生命周期 我们平常经常使用类似于new Object()的方式去创建对象&#xff0c;在这个对象没有任何引用的时候&#xff0c;会被gc给回收掉。而对于spring而言&#xff0c;它本身存在一个Ioc容器&#xff0c;就是用来管理对象的&#xff0c;而对象的生命周期也完全由这个容…

67-箭头函数,new.target,模版字符串

1.箭头函数 ES6新增语法&#xff0c;用来简化函数的书写()>{} <script>//箭头函数的基本使用let a (a,b)>{return ab;}let c a(1,2);console.log(c);//输出3</script> 2.简写形式&#xff1a; 2.1参数&#xff1a;只有一个参数时可以省略小括号a>{}&…

Java Stream 流?看这一篇就够了!

大家好&#xff0c;从我开始写博客也过去半年多了&#xff0c;c 站陪我走过了学习 Java 最艰苦的那段时光&#xff0c;也非常荣幸写的博客能得到这么多人的喜欢。 某一天当我开始学习分布式的时候突然想到这可能是补充 Java 知识拼图的最后几块部分了&#xff0c;为了将前面的知…

《springcloud alibaba》 三 sentinel流量控制

目录 sentinel准备流控规则 qpspom.xmlapllication.yml启动类controller查看结果流控提示不太友好 流控规则 线程数全局异常处理pom.xmlapplication.yml启动类实体类controller类异常类测试 关联流控模式关联jmeter 链路servicecontroller代码调整 流控效果Warm UP 熔断降级规则…

idea项目中文乱码

背景&#xff1a; 从gitee下download了项目发现配置值文件application.properies中出现了乱码&#xff0c;如下 其他文件都正常&#xff0c;例如 解决&#xff1a; 不要 忘记 ok 解决后配置文件 application.properties

2.1 表结构数据

1、表结构数据 字段&#xff1a;整列数 记录&#xff1a;整行数 维度&#xff1a;业务角度 度量&#xff1a;业务行为结果 维度字段&#xff1a;文本型&#xff08;状态&#xff09; 度量字段&#xff1a;数值型&#xff08;交易结果&#xff09; 2、事实表&维度表 维度表…

ubuntu22.04安裝mysql8.0

官网下载mysql&#xff1a;MySQL :: Download MySQL Community Server 将mysql-server_8.0.20-2ubuntu20.04_amd64.deb-bundle.tar上传到/usr/local/src #解压压缩文件 tar -xvf mysql-server_8.0.20-2ubuntu20.04_amd64.deb-bundle.tar解压依赖包依次输入命令 sudo dpkg -i m…