多态的原理、单继承和多继承的虚函数表、以及虚函数表的打印。

一、多态原理

1、下面这个结果是多少?

class A
{
public:
	virtual void func()
	{
		cout << "func()" << endl;
	}

private:
	int _a = 1;
};

int main()
{
	printf("%d\n", sizeof(A));
	return 0;
}

是 4?8?还是多少?打印结果如下:

 

为什么是 8 呢?,通过调试看一下,如下:

 通过调试可以看到里面不只是有 _a 成员,还多了一个成员 __vfptr,这个成员就是虚函数表指针,为什么会有虚函数表指针呢?因为虚函数的地址要存放到虚函数表中!并且含有虚函数的类中至少有一个虚函数表指针!

2. 上面的代码修改一下,如下

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

	virtual void Func2()
	{
		cout << "A::Func2()" << endl;
	}

	void Func3()
	{
		cout << "A::Func3()" << endl;
	}
private:
	int _a = 1;
};

class B : public A
{
public:
	virtual void Func1()
	{
		cout << "B::Func1()" << endl;
	}
private:
	int _b = 2;
};

int main()
{
	A a;
	B b;
	return 0;
}

调试看一下内部结构是什么,如下:

  

通过上面的调试可以看到:

(1) 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。

(2) 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

(3)另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。

(4)虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

(5)总结一下派生类的虚表生成:

        a.先将基类中的虚表内容拷贝一份到派生类虚表中 

        b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 

        c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

(6)那么虚函数存在哪的?虚表存在哪的? 
        虚函数存在虚表?虚表存在对象中?
        不对!!!虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。

通过上面的例子,下面正式看一下多态原理!!

上一篇文章讲了多态的条件,满足多态条件的时候,传给父类指针或者引用的时候,会完成不一样的调用,那么下面解释一下为什么,如下:

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 q;
	Func(q);

	Student st;
	Func(st);
 
	return 0;
}

因为 q 传给 p 引用的时候,p 是指向 q 对象的,所以发生了切片,但是又因为都是Person这个类的,所以调用了自己的方法。

st 传给 p 引用的时候,p 是指向 st 对象的,student 又继承了Person类,并且重写了虚函数,形成了多态,但是 st 对象里面的虚函数是重写了之后的!而不是继承下来的虚函数!可以调试查看一下,如下:

虽然发生切片, p 指向 st 对象中 Person 前面的地址,但是因为是方法是重写的,所以调用的是student 的方法!

下面汇编代码看一下,如下:

 多态的时候 call 的时候,call 的是寄存器!具体细节不展开讲了。

总之多态调用的时候,运行时去指向对象的虚表中找虚函数地址,进行调用!

那么不构成多态是什么呢?

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

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

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

int main()
{
	Person q;
	Func(q);

	Student st;
	Func(st);

	return 0;
}

我随便破坏一个条件已经不构成多态,那么调用的时候是什么样呢? 如下:

可以看到 call 的直接是函数地址! 

因为不满足多态的话就是普通函数调用,编译链接时候,就确认了函数地址,运行时直接调用。

二、单继承和多继承的虚函数表。

// 单继承
// 打印虚函数表
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "Person::BuyTicket()" << endl;
	}

	virtual void func_person()
	{
		cout << "Person::func_person()" << endl;
	}
};

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

	// 监视窗口查看 s 的虚表 里面查看不到 func_student() 函数
	virtual void func_student()
	{
		cout << "Person::func_student()" << endl;
	}
};


typedef void(*VFTABLE)();

void PRINT_VFTABLE(VFTABLE table[], int n)
{
	//for (int i = 0; table[i] != nullptr; i++) // vs 在后面默认给的 0 ; 所以可以用 table[i] != nullptr; linux 需要显示写打印几个
	for (int i = 0; i < n; i++)
	{
		printf("table[%d]->%p::", i, table[i]);

		VFTABLE pf = table[i];
		pf(); // 函数指针+() 调用自己对应的函数
	}

	cout << endl;
}


//int main()
//{
//	Student s;
//
//	Person p;
//
//	//PRINT_VFTABLE((VFTABLE*)*(int*)&s);
//	PRINT_VFTABLE((VFTABLE*)*(int*)&s, 3); // 子类有 3 个 显示写打印 3 个
//	PRINT_VFTABLE((VFTABLE*)*(int*)&p, 2); // 父类有 2 个 显示写打印 2 个
//
//	return 0;
//}



// 多继承
// 打印虚函数表


class Base1
{
public:
	virtual void func1()
	{
		cout << "Base1::func1()" << endl;
	}

	virtual void func_Base1()
	{
		cout << "Base1::func_Base1()" << endl;
	}
};


class Base2
{
public:
	virtual void func1()
	{
		cout << "Base2::func1()" << endl;
	}

	virtual void func_Base2()
	{
		cout << "Base2::func_Base2()" << endl;
	}
};


class Base3 : public Base1, public Base2
{
public:
	virtual void func1()
	{
		cout << "Base3::func1()" << endl;
	}

	virtual void func_Base3()
	{
		cout << "Base3::func_Base3()" << endl;
	}
};

typedef void(*VFTABLE)();

void PRINT_VFTABLE(VFTABLE  table[])
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("table[%d]->%p->", i, table[i]);

		VFTABLE pf = table[i];
		pf();
	}
	cout << endl;
}

int main()
{
	Base3 b3;
	Base1 b1 = b3;
	Base2 b2 = b3;

	PRINT_VFTABLE((VFTABLE*)*(int*)&b1);
	PRINT_VFTABLE((VFTABLE*)*(int*)&b2);
	PRINT_VFTABLE((VFTABLE*)*(int*)&b3);

	return 0;
}

上面写了一个函数打印虚函数表,可以尝试调试打印看一下。 

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

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

相关文章

MVC 接收不到参数? —— 看我如何给你安排得明明白白

文章结构 问题背景&#xff1a;问题处理总结 问题背景&#xff1a; 现有如下代码&#xff1a; PostMapping(value "/payment/create") ResponseBody public CommonResult create(Payment payment) {}乍眼看去是不是很好&#xff0c;至少没啥问题很自然&#xff0c…

什么是日志关联

什么是日志关联 日志关联是一种分析来自不同源的日志数据以识别事件模式的技术。它用于更好地了解网络的活动&#xff0c;从而有效地保护网络免受漏洞和威胁。 日志关联是日志管理过程的关键部分。收集和存储日志后&#xff0c;集中式日志服务器将执行分析以检测特定事件。日…

LC-3 机器码编程实验

一、实验目的 分析和理解试验指定的需解决问题。利用LC-3的机器代码设计实现相关程序。通过LC-3仿真器调试和运行相关程序并得到正确的结果。 二、实验内容 利用LC-3的机器代码计算一个16位的字中有多少位是“1”&#xff0c;程序从x3000开始&#xff0c;需计算的字存储在x3…

c++ 11标准模板(STL) std::map(九)

定义于头文件<map> template< class Key, class T, class Compare std::less<Key>, class Allocator std::allocator<std::pair<const Key, T> > > class map;(1)namespace pmr { template <class Key, class T, clas…

知识图谱实战应用12-食谱领域智能问答系统,实现菜谱问答

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用12-食谱领域智能问答系统,实现菜谱问答,本项目基于py2neo和neo4j图数据库,将知识图谱应用于菜谱领域。通过构建菜谱知识图谱,实现简单的菜谱食材问答系统。用户可以通过问答系统,快速获取简单的菜谱食材信息。 一…

悦灵犀-全新的智能AI工具

最近一段时间&#xff0c;人工智能再次成为人类创新的焦点&#xff0c;不得不说&#xff0c;人工智能正在以一种全新的方式改变人们的生活&#xff0c;这是一个以大模型为核心的人工智能新时代&#xff0c;大模型的出现让千行百业将迎来新的机遇。 悦享星光作为国内高新技术企…

VC GDI双缓冲绘图

VC GDI双缓冲绘图 VC GDI双缓冲绘图创建内存DC和内存图片&#xff0c;缺一不可最好是封装一下内存绘制绘制效果 关键是不闪烁PS 重绘机制 VC GDI双缓冲绘图 双缓冲绘图&#xff0c;知道这个知识点&#xff0c;每次用的时候还得踩一遍坑&#xff0c;真是服&#xff0c;总结记录…

如何实现 ESP 设备多证书管理?

设置特定分区存储证书文件多证书文件管理证书格式转换证书下载使用证书文件 1、设置特定分区存储证书文件 在项目工程下分区表文件下定义证书分区表文件&#xff0c;如下&#xff1a; 如上&#xff0c;转换的 certificate.bin 下载地址就为 0x41000证书分区文件的大小可不做设…

Typora改变字体颜色

方法一&#xff1a;下载AutoHotkey并创建快捷键的方法&#xff08;推荐&#xff09; 第一步&#xff1a;在官网&#xff08;https://www.autohotkey.com/&#xff09;下载 AutoHotkey并傻瓜式安装&#xff0c;安装在任意盘符下均可&#xff1b; 第二步&#xff1a;在安装目录…

JavaScript 基础 DOM (三)

日期对象 实例化 获得当前时间 const date new Date() 获得指定时间 const date1 new Date( 指定时间) 方法 // 1. 实例化const date new Date();// 2. 调用时间对象方法// 通过方法分别获取年、月、日&#xff0c;时、分、秒const year date.getFullYear(); // 四位年份 时…

docker的基本相关知识和操作

镜像相关操作命令&#xff1a; 访问DockerHub搜索镜像&#xff0c;https://hub.docker.com/ 查看本地镜像&#xff1a;docker images 搜索镜像 docker search redis &#xff08;搜索redis&#xff09; 拉取镜像&#xff1a;docker pull redis &#xff08;默认版本&#x…

【Python】列表和字典

知识目录 一、写在前面✨二、列表应用三、字典应用四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是初心&#xff0c;希望我们一路走来能坚守初心&#xff01; 今天跟大家分享的文章是 Python函数式编程第二弹&#xff0c;再次以两个简单的例子带大家更好的…

OpenStreetMap实战

介绍 OpenStreetMap&#xff08;OSM&#xff09;是一个由志愿者创建并维护的免费和开源的地图数据库。其目的是为全球任何人提供可自由使用、编辑和分发的地图数据。OpenStreetMap数据库中的地理要素包括道路、建筑、河流、森林、山脉、公共设施等。由于OpenStreetMap是开放的…

单点登录二:登录过程使用摘要算法和加盐的意义以及demo练习

上一篇《springboot项目使用redis、springSecurity、jwt实现单点登录》写了关于单点登录的架子&#xff0c;但是没有实现密码验证的细节。这里使用盐和摘要算法来实现一个密码验证的完整过程demo。 1、依赖没变&#xff0c;还是上一篇内容那些 <dependencies><depen…

接口测试简介以及接口测试用例设计思路

接口测试简介 1.什么是接口 接口就是内部模块对模块&#xff0c;外部系统对其他服务提供的一种可调用或者连接的能力的标准&#xff0c;就好比usb接口&#xff0c;他是系统向外接提供的一种用于物理数据传输的一个接口&#xff0c;当然仅仅是一个接口是不能进行传输的&#x…

多层感知机学习XOR实例

多层感知机学习XOR实例 多层感知机介绍使用多层感知机学习XOR实例传统统计数学方法&#xff08;传统机器学习&#xff09;使用多层感知机学习XOR 总结 多层感知机介绍 多层感知机&#xff08;Multilayer Perceptron&#xff0c;MLP&#xff09;&#xff0c;又称为深度前馈网络…

JAVA并发编程之锁应用

Java并发包是Java中提供的一个用于支持多线程编程的工具包。Java并发包提供了多种机制来控制线程的执行&#xff0c;保证线程的安全性和可靠性。下面我们将介绍Java并发包的使用方法&#xff0c;并给出示例。 synchronized public class SynchronizedDemo { ​private int v;…

自学网络安全,一般人我劝你还是算了吧

学前感言: 我为什么会这样说&#xff0c;要一般人自学网络安全就算了&#xff0c;因为我不是一般人 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发 .3.有时多 google,baidu,我们往往都…

termux-x11教程

小粉丝已经求稿两个星期了&#xff0c;不写是不行了。 termux-x11 是Termux的一个图形化项目&#xff0c;官方是这么介绍的。 A Termux add-on app providing Android frontend for Xwayland.安装工具 我们需要在Termux和安卓系统上安装工具以成功的运行程序。 x11-repo&am…

使用canvas给图片添加水印

上接文章“图片处理” canvas元素其实就是一个画布&#xff0c;我们可以很方便地绘制一些文字、线条、图形等&#xff0c;它也可以将一个img标签里渲染的图片画在画布上。 我们在上传文件到后端的时候&#xff0c;使用input标签读取用户本地文件后得到的其实是一个Blob对象&a…