C++——多态与虚表

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

目录

1.多态的实现

2.虚表

2.1虚函数重写是怎么实现的

2.2多态的原理

2.3静态绑定与动态绑定

3.单继承体系中的虚函数表

​编辑4.多继承体系中的虚函数表

5.菱形继承的虚函数表

6.菱形虚拟继承的虚函数表

1.多态的实现

在C++中,要想实现多态,必须满足以下几个条件:

  1. 有继承关系
  2. 有虚函数
  3. 虚函数要重写

根据继承关系中,派生类向基类赋值没有发生类型转换的特性,可以得出一个结论:基类的指针或引用指向了一个"基类对象",这个"基类对象"有可能是基类本身的对象,也有可能是派生类当中的基类部分,由于编译时确定不了(因为这两种基类对象没有差别),所以多态又称运行时绑定

这幅图用来解释上面那段话:

C++——多态与虚表

 以一段代码来体会运行时绑定(动态绑定):

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

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

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

void testPolimorphic(Person &rp)
{
	rp.slefMessage();//调用虚函数
}
int main()
{
	Student s;
	Teacher t;
	testPolimorphic(s);
	testPolimorphic(t);
	return 0;
}

C++——多态与虚表

2.虚表

虚表全称虚函数表,所以它是一个函数指针数组。 虚表指针将会被存放在对象中,所以以下代码的输出结果可能会令人诧异:

class Person
{
public:
	virtual void slefMessage()
	{
		cout << "Person" << endl;
	}
};
int main()
{
	Person p;
	cout << sizeof(p) << endl;
	return 0;
}

C++——多态与虚表

实际上类的对象模型当中确实不存储任何成员函数,包括虚函数在内。虚函数被存放在了虚函数表当中,但是编译为了能够找到虚函数表,所以有必要维护一个虚函数表指针,并将它存放在对象当中。所以最后的输出结果为4(64位平台下的输出结果为8,本篇文章的所有测试用例都在Visual Studio 2013下编译运行)。也就是说对象的前4/8个字节为虚表指针

对于上面的程序,以调试-监视窗口查看是这样的:

C++——多态与虚表

以调试-内存窗口查看是这样的:

C++——多态与虚表

所以对于Person类对象来说,它的对象模型应该是这样的:

C++——多态与虚表

 虚表实际上不一定是以空结尾,只是对于我所使用的编译器来说它就是以空结尾的。其他的编译器可能不一样。

2.1虚函数重写是怎么实现的

在语法层面上,派生类继承了基类的虚函数,再定义实现一遍基类的虚函数就是"重写"。但是在实现原理上远比这复杂的多。以下面的代码为例:

class Person
{
public:
	virtual void func1()
	{}
	virtual void func2()
	{}
};

class Student : public Person
{
public:
	virtual void func1()
	{}
};
int main()
{
	Person t;
	Student s;
	return 0;
}

对于这段代码,以调试-监视窗口观察是这样的:

C++——多态与虚表

由此可以得出两个结论

  1. 因为数组的首元素地址可以代表数组的地址,对比上图可以发现派生类的虚表是基类虚表的一份拷贝
  2. 如果派生类发生了重写基类虚函数,原本存放在虚表的基类虚函数地址会被替换为派生类虚函数地址;反之,派生类没有重写虚函数,虚表放的还是基类的虚函数地址。 

所以重写的实质不是程序员再定义实现一遍虚函数就行,而是虚表当中的虚函数地址被替换,这种替换行为称为重写(覆盖)

补充一个结论:如果派生类实现了一个全新的虚函数,这个虚函数的地址会追加进虚表当中

虚表的存放位置在代码段(常量区),以下面这段代码证明:

class Test
{
public:
	virtual void func(){}
};
int main()
{
	Test t;
	
	int a = 0;
	cout << "栈: " << (void*)&a << endl;

	static int b = 0;
	cout << "数据段: " << (void*)&b << endl;

	const char * str = "nice";
	cout << "代码段: " << (void*)str << endl;

	cout << "虚表指针位置? " << *(void**)&t << endl;
	return 0;
}

C++——多态与虚表

 "*(void**)&t"是什么写法?解引用之后得到一个void*类型的指针,void*类型在32位平台下有4个字节,64位平台下有8个字节。所以这种写法能够自适应不同的平台。

C++——多态与虚表

2.2多态的原理

上面开头的代码已经证明多态是可以被实现的,那么它的原理一定与虚表有关。

实际上要调用虚函数,就先要搞清楚虚表在哪;为了搞清楚虚表在哪,对象当中就必须有虚表指针。并且由于编译器编译时根本就不知道基类的指针或引用到底指向哪个类的对象,所以编译器就非常智能地采用多态策略。那么多态的原理就是:调用虚函数时不会直接调用,而是在程序运行时根据对象的虚表确定调用的虚函数

以一张图理解多态的原理:

C++——多态与虚表

2.3静态绑定与动态绑定

  • 静态绑定:又称静态多态,在编译时就确定了调用的行为。典型的例子就是函数重载,根据调用函数时传入的类型不同就可以确定不同的调用方法。
  • 动态绑定:又称多态,在编译时确定不了具体的行为而将工作留在程序运行时。主要是利用了继承当中,派生类向基类赋值没有类型转换的特性。动态绑定的核心就是运行时找虚表

3.单继承体系中的虚函数表

实际上可以将虚函数分为三类:

  1. 派生类未重写的虚函数
  2. 派生类重写的虚函数
  3. 派生类新增的虚函数

对于1来说,这个虚函数依然是基类的虚函数;对于2来说,该虚函数将之前的基类虚函数替换掉,完成重写;对于3来说,这个虚函数将会追加在虚表的后面。

由此可以推出虚表的生成条件

  1. 基类当中有虚表,派生类继承后会生成一份一模一样的虚表(拷贝)
  2. 基类当中没有虚表,但是派生类有虚函数

需要注意的是,虚表在对象调用构造函数之前已经生成了,构造函数初始化的是虚表指针。这就意味着重写工作由编译器完成。

以一段代码作为样例:

class A
{
public:
	virtual void func1()
	{}
};

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

class C : public B
{
public:
	virtual void func1()
	{}

	virtual void func2()
	{}
};

int main()
{
	B b;
	C c;
	return 0;
}

 以调试-监视窗口观察:

C++——多态与虚表

以一张图来理解单继承体系中的虚表:

4.多继承体系中的虚函数表

 以一段代码作为样例:

class A
{
public:
	virtual void func1()
	{}
};

class B
{
public:
	virtual void func1()
	{}
};

class C : public A,public B
{
public:
	virtual void func1()
	{}

	virtual void func2()
	{}
};
int main()
{
	C c;
	return 0;
}

 以调试-监视窗口观察:

C++——多态与虚表

从结果上来看,C类对象当中有两份虚表,分别是从A类继承而来的和从B类继承而来的。根据三同原则(返回类型、函数名、参数类型都相同),所以C类当中的func1虚函数与A类、B类的func1虚函数构成重写关系。那么C类对象当中有一新增的虚函数func2,它被追加进了两份虚表当中的其中一份,即A类的虚表当中。由此可以得出一个结论:多继承体系中,派生类的新增虚函数追加在派生类的第一张虚表中

以一张图解释上面的结论:

C++——多态与虚表

实际上凡是关于虚表的,只需要保证对象的前4/8个字节是虚表指针即可。 

对于多继承来说,派生类不一定有两张虚表,主要看被继承的基类有没有虚表。

5.菱形继承的虚函数表

对于菱形继承来说,它的本质就是一个多继承体系,所以它的虚表与上面说介绍的多继承体系的虚表没什么差别。

菱形继承就是两个单继承+一个多继承,以一张图来理解:

C++——多态与虚表

6.菱形虚拟继承的虚函数表

说实在的菱形继承本身就没有什么价值更何况菱形虚拟继承。但是这里还是简单的谈谈。

首先以一段代码来明确虚拟单继承的虚函数表在哪:

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

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

 以调试-内存窗口观察:

C++——多态与虚表

理解的思路很简单:虚继承将基类的部分单独作为派生类的一个部分。所以派生类中如果新增虚函数,那么派生类将会再生成一个虚表,并且派生类对象的前4/8个字节将会是指向新开虚表的虚表指针。

在菱形虚拟继承中,最终派生类的两个基类,被继承之后会将虚基表指针、两个虚表合成一份,这就注定了最终派生类必须完成虚函数的重写。试想一下,基类1重写了虚函数,基类2也重写了虚函数,那么最终类如果不重写如函数,那么继承下来的虚表当中的虚函数是用基类1的还是基类2的?

以一份代码来理解上面的那段话:文章来源地址https://www.toymoban.com/news/detail-471485.html

class A
{
public:
	virtual void func1()
	{}
};

class B : virtual public A
{
public:
	// B类重写了A类的虚函数
	virtual void func1()
	{}
};

class C : virtual public A
{
public:
	// C类重写了A类的虚函数
	virtual void func1()
	{}
};

class D : public B,public C
{
public:
	// D类也必须完成重写,因为虚继承没有数据冗余和二义性
	// 所以A类部分只有一份,被放在D类对象的末尾
	// 那么不重写的话,虚表当中放哪个函数?
	virtual void func1()
	{}
};

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

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

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

相关文章

  • 【C++】 为什么多继承子类重写的父类的虚函数地址不同?『 多态调用汇编剖析』

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》 《数据结构》 《蓝桥杯试题》 《LeetCode刷题笔记》 《实训项目》 《C++》 《Linux》《算法》 🌝 每一个不曾起舞的日子,都是对生命的辜负 本篇文章主要是为了解答有关多态的那篇文章那块的一个奇怪现象,大家还记得这张

    2024年02月19日
    浏览(35)
  • 【C++】继承和多态、共有私有和保护、重写

    继承是一种机制,通过它 一个类可以从另一个类继承属性和方法 。派生类(子类)继承基类(父类)的成员函数和数据成员,并且可以在其基础上扩展自己的成员函数和数据成员。C++支持多重继承,即一个派生类可以同时从多个基类中继承。 多态是指 同一种操作作用于不同

    2024年02月03日
    浏览(38)
  • 【C++】继承和多态、public、private、protected、重写

    继承是一种机制,通过它 一个类可以从另一个类继承属性和方法 。派生类(子类)继承基类(父类)的成员函数和数据成员,并且可以在其基础上扩展自己的成员函数和数据成员。C++支持多重继承,即一个派生类可以同时从多个基类中继承。 多态是指 同一种操作作用于不同

    2024年02月03日
    浏览(49)
  • 多态 多继承的虚表深度剖析 (3)

    💯 博客内容:多态 😀 作  者:陈大大陈 🚀 个人简介:一个正在努力学技术的准C++后端工程师,专注基础和实战分享 ,欢迎私信! 💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘 目录 普通菱形继承  虚表指针偏移 

    2024年02月05日
    浏览(36)
  • 简单明了证明多态虚表是位于常量区

    证实虚表存储与常量区

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

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

    2023年04月18日
    浏览(38)
  • C++:多态的底层实现原理 -- 虚函数表

    目录 一. 多态的原理 1.1 虚函数表 1.2 多态的实现原理 1.3 动态绑定与静态绑定 二. 多继承中的虚函数表 2.1 虚函数表的打印 2.2 多继承中虚函数表中的内容存储情况 对于一个含有虚函数的的类,在实例化出来对象以后,对象所存储的内容包含两部分: 类的成员变量。 一

    2023年04月21日
    浏览(37)
  • C++中的多态是什么?如何实现多态?解释一下C++中的虚函数和纯虚函数,它们的作用是什么?

    C++中的多态是什么?如何实现多态? 在C++中,多态(Polymorphism)是面向对象编程的三大特性之一,另外两个是封装(Encapsulation)和继承(Inheritance)。多态指的是允许一个接口(或一个父类引用)在多种数据类型上被实现,或者一个接口被多个不同的类以不同的方式实现。

    2024年02月19日
    浏览(56)
  • C++八股 | 函数重写(覆盖)

            函数重载也是C++内一个重要板块,面试挖八股时会从 三大特性-多态-虚函数-函数重写(覆盖)这样的形式提问         派生类对基类同名同参函数进行重新修改/重写的过程 基类有virtual虚函数 同名同参函数 基类指针or引用指向派生类对象 如下代码,构成重写

    2023年04月24日
    浏览(40)
  • c++——重写(覆盖),实际上对应的就是虚函数

    重写 是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

    2024年02月12日
    浏览(39)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包