【C++】多态的底层原理(虚函数表)

这篇具有很好参考价值的文章主要介绍了【C++】多态的底层原理(虚函数表)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🌏博客主页: 主页
🔖系列专栏: C++
❤️感谢大家点赞👍收藏⭐评论✍️
😍期待与大家一起进步!



前言

一、虚函数表

【C++】多态的底层原理(虚函数表),c++,java,开发语言
【C++】多态的底层原理(虚函数表),c++,java,开发语言

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数
的地址要被放到虚函数表中
,虚函数表也简称虚表

二、派生类中虚函数表

1.原理

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;
}

【C++】多态的底层原理(虚函数表),c++,java,开发语言

1.基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
2.另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
3.虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

总结一下派生类的虚表生成a. 先将基类中的虚表内容拷贝一份到派生类虚表中 b. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

2.例子:

【C++】多态的底层原理(虚函数表),c++,java,开发语言

  1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
  2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。
  3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
    【C++】多态的底层原理(虚函数表),c++,java,开发语言

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

三、虚函数的存放位置

虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针

同类型共用虚函数表:
由于同一类的不同对象的虚函数表是相同的,它们所需的虚函数的地址是一样的,因此可以共用同一份虚函数表,这样就节省了内存空间。这种设计可以使得每个对象只需要一个指针来指向虚函数表,而不需要为每个对象都分配一份独立的虚函数表。

四 、单继承中的虚函数表

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

		int _a = 0;
	};
class Student : public Person {
  
private:
	virtual void Func3(){cout << "Student::Func3()" << endl;}
	protected:
		int _b = 1;
};

	int main() {
		Person p;
		Student s;
		return 0;
	}

我们会发现虚函数表中少了一个func3的函数指针,我们下面会验证func3确实存在与派生类的虚基表中。
【C++】多态的底层原理(虚函数表),c++,java,开发语言
每个对象都有一个自己的虚函数指针,指向相应的虚函数表。这里基类和派生类的虚函数指针指向同一个地址,是因为基类的虚函数在派生类中没有被重写(Func1与Func2)当派生类继承基类时,派生类会继承基类的虚函数表,并在其虚函数表中添加或覆盖自己的虚函数。如果派生类没有重写基类的虚函数,那么派生类的虚函数表中对应的虚函数地址仍然指向基类中的虚函数。
【C++】多态的底层原理(虚函数表),c++,java,开发语言
虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr

思路:取出ps、st对象的头4bytes,就是虚表的指针,然后利用虚表的指针对虚函数数组进行遍历,若能打印出Func3的函数指针,就说明Func3在虚函数表中

typedef void(*FUNC_PTR) ();//FUNC_PTR为函数指针
void PrintVFT(FUNC_PTR* table)//table为函数指针数组
{
	//虚函数表本质是一个存虚函数
	//指针的指针数组,这个数组最后面放了一个nullpt
	for (size_t i = 0; table[i] != nullptr; i++)
	{   printf("[%d]:%p->", i, table[i]);
		FUNC_PTR f = table[i];
		f();//函数指针指向函数的内存地址,
		//通过调用函数指针,实际上是调用了指向的函数
	}
	printf("\n");
}
	int main() {
	Person ps;
	Student st;
//1.先取ps的地址,强转成一个int * 的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针(二重函数指针)
	int vft1 = *((int*)&ps);
	PrintVFT((FUNC_PTR*)vft1);
//3.再强转成VFPTR * ,因为虚表就是一个存FUNC_PTR类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVTable进行打印虚表
	int vft2 = *((int*)&st);
	PrintVFT((FUNC_PTR*)vft2);
//5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最
//后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再编译就好了。
		return 0;
	}

【C++】多态的底层原理(虚函数表),c++,java,开发语言

五、多继承中的虚函数表

多继承的对象虚函数表

【C++】多态的底层原理(虚函数表),c++,java,开发语言

【C++】多态的底层原理(虚函数表),c++,java,开发语言
【C++】多态的底层原理(虚函数表),c++,java,开发语言

多继承中的虚函数表有多个

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
【C++】多态的底层原理(虚函数表),c++,java,开发语言文章来源地址https://www.toymoban.com/news/detail-639460.html

六、问答题

  1. inline函数可以是虚函数吗?
    答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去
    2.静态成员可以是虚函数吗?
    答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
    3.构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的
    4.虚函数表是在什么阶段生成的,存在哪的?
    答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
  2. 对象访问普通函数快还是虚函数更快?
    答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

到了这里,关于【C++】多态的底层原理(虚函数表)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++修炼之路之多态---多态的原理(虚函数表)

    目录 一:多态的原理  1.虚函数表  2.原理分析 3.对于虚表存在哪里的探讨 4.对于是不是所有的虚函数都要存进虚函数表的探讨 二:多继承中的虚函数表 三:常见的问答题  接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧  接上篇的多态的介绍后,接下来介绍

    2024年04月26日
    浏览(30)
  • <c++>虚函数与多态 | 虚函数与纯虚函数 | 多态的实现原理 | 虚析构函数

    🚀 个人简介:CSDN「 博客新星 」TOP 10 , C/C++ 领域新星创作者 💟 作    者: 锡兰_CC ❣️ 📝 专    栏: 从零开始的 c++ 之旅 🌈 若有帮助,还请 关注➕点赞➕收藏 ,不行的话我再努努力💪💪💪 在上一篇文章中,我们介绍了 c++ 中类与对象的继承,继承可以根据一个或

    2023年04月18日
    浏览(30)
  • 【C++】虚函数表 & 多态的原理 & 动态绑定和静态绑定

    梳理虚函数表、多态原理、动静态绑定的知识 目录 一、虚函数表 二、多态的原理 三、动态绑定和静态绑定 在学习多态原理之前,我们需要了解一下虚函数表的概念  我们先一起来看下下面这段代码 通过测试我们发现b对象是8bytes, 除了_b成员,还多一个__vfptr指针放在对象

    2024年02月03日
    浏览(33)
  • C++类和对象-多态->多态的基本语法、多态的原理剖析、纯虚函数和抽象类、虚析构和纯虚析构

    #includeiostream using namespace std; //多态 //动物类 class Animal { public:     //Speak函数就是虚函数     //函数前面加上virtual,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。     virtual void speak()     {         cout \\\"动物在说话\\\" endl;     } }; //猫类 class Cat

    2024年02月20日
    浏览(28)
  • [C++] C++入门第一篇 -- 命名空间,输入输出,缺省函数,函数重载底层原理

      目录 1、  2、命名空间 2.1 命名空间的定义 2.2 命名空间的使用方式 2.2.1 加命名空间名称及作用域限定符 2.2.2 使用using将命名空间中某个成员引入 2.2.3 使用using namespace 命名空间名称引入 3、C++输入与输出 4、缺省参数 4.1 缺省参数的概念 4.2 缺省参数分类 4.2.1 全缺省参

    2024年02月15日
    浏览(40)
  • C++类开发第七篇(详细说说多态和编译原理)

    多态性(polymorphism)提供接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。 c++支持编译时多态(静态多

    2024年03月09日
    浏览(42)
  • 【C++庖丁解牛】面向对象的三大特性之一多态 | 抽象类 | 多态的原理 | 单继承和多继承关系中的虚函数表

    🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 需要声明的,本节课件中的代码及解释都是在vs2013下的x86程序中,涉及的指针都是4bytes。如果要其他平台

    2024年04月10日
    浏览(44)
  • 【C++】多态原理剖析,Visual Studio开发人员工具使用查看类结构cl /d1 reportSingleClassLayout

    author:Carlton tag:C++ topic:【C++】多态原理剖析,Visual Studio开发人员工具使用查看类结构cl /d1 reportSingleClassLayout website:黑马程序员C++ tool:Visual Studio 2019 date:2023年7月24日   目录 父类使用虚函数前后类内部结构变化 子类重写父类虚函数的作用及其机理         首先父类成员

    2024年02月15日
    浏览(45)
  • C++中菱形继承中的多态在底层是如何实现的。

    如果还不了解菱形继承和多态的底层可以看这两篇文章: C++中多态的底层实现_Qianxueban的博客-CSDN博客 C++的继承以及virtual的底层实现_Qianxueban的博客-CSDN博客

    2024年02月09日
    浏览(27)
  • c、c++、java、python、js对比【面向对象、过程;解释、编译语言;封装、继承、多态】

    目录 内存管理、适用 区别 C 手动内存管理:C语言没有内置的安全检查机制,容易出现内存泄漏、缓冲区溢出等安全问题。 适用于系统级编程 C++ 手动内存管理:C++需要程序员手动管理内存,包括分配和释放内存,这可能导致内存泄漏和指针错误。 适用于游戏引擎和系统级编

    2024年02月08日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包