c++ 虚函数,虚表相关总结
面向对象,从单一的类开始说起。
class A { private: int m_a; int m_b; };
这个类中有两个成员变量,都是int类型,所以这个类在内存中占用多大的内存空间呢?
sizeof(A), 8个字节,一个int占用四个字节。下图验证:
这两个数据在内存中是怎样排列的呢?
原来是这样,我们根据debug出来的地址画出a对象在内存的结构图
如果 class A 中包含成员函数呢? A 的大小又是多少?
class A { public: void func1() {} private: int m_a; int m_b; };
直接告诉你答案,类的成员函数多大? 没人能回答你,并且不是本文的重点,类的成员函数是放在代码区的,不算在类的大小内。
类的对象共享这一段代码,试想,如果每一个对象都有一段代码,光是存储这些代码得占用多少空间?所以同一个类的对象共用一段代码。
共用同一段代码怎么区分不同的对象呢?
实际上,你在调用成员函数时,a.func1() 会被编译器翻译为 A::func1(&a),也就是A* const this, this 就是 a 对象的地址。
所以根据this指针就能找到对应的数据,通过这同一段代码来处理不同的数据。
接下来我们讨论一下继承,子类继承父类,将会继承父类的数据,以及父类函数的调用权。
以下的测试可以验证这个情况。
class A { public: void func1() { cout << "A func1" << endl; } private: int m_a; int m_b; }; class B : public A { public: void func2() { cout << "B func2" << endl; } private: int m_c; }; int main(int argc, char const* argv[]) { B b; b.func1(); b.func2(); return 0; }
输出:
// A func1 // B func2
那么对象b在内存中的结构是什么样的呢?
继承关系,先把a中的数据继承过来,再有一份自己的数据。
每个包含虚函数的类都有一个虚表,虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表。
为了指定对象的虚表,对象内部包含指向一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。
class A { public: void func1() { cout << "A func1" << endl; } virtual void vfunc1() { cout << "A vfunc1" << endl; } private: int m_a; int m_b; };
cout << sizeof(A);, 输出12,A中包括两个int型的成员变量,一个虚指针,指针占4个字节。
a的内存结构如下:
虚表是一个函数指针数组,数组里存放的都是函数指针,指向虚函数所在的位置。
对象调用虚函数时,会根据虚指针找到虚表的位置,再根据虚函数声明的顺序找到虚函数在数组的哪个位置,找到虚函数的地址,从而调用虚函数。
调用普通函数则不像这样,普通函数在编译阶段就指定好了函数位置,直接调用即可。
class A { public: void func1() { cout << "A func1" << endl; } virtual void vfunc1() { cout << "A vfunc1" << endl; } private: int m_a; int m_b; }; class B : public A { public: void func1() { cout << "B func1" << endl; } virtual void vfunc2() { cout << "B vfunc2" << endl; } private: int m_a; };
像这样,B类继承自A类,B中又定义了一个虚函数vfunc2, 它的虚表又是怎么样的呢?
给出结论,虚表如下图所示:
我们来验证一下:
A a; B b; void(*avfunc1)() = (void(*)()) *(int*) (*(int*)&a); void (*bvfunc1)() = (void(*)()) *(int*) *((int*)&b); void (*bvfunc2)() = (void(*)()) * (int*)(*((int*)&b) + 4); avfunc1(); bvfunc1(); bvfunc2();
来解释一下代码: void(*avfunc1)() 声明一个返回值为void, 无参数的函数指针 avfunc1, 变量名代表我们想要取A类的vfunc1这个虚函数。
右半部分的第一部分,(void(*)()) 代表我们最后要转换成对应上述类型的指针,右边需要给一个地址。
我们看 (*int(*)&a), 把a的地址强转成int*, 再解引用得到 虚指针的地址。
*(int*) (*(int*)&a) 再强转解引用得到虚表的地址,最后强转成函数指针。
同理得到 bvfunc1, bvfunc2, +4是因为一个指针占4个字节,+4得到虚表的第二项。
覆盖
class A { public: void func1() { cout << "A func1" << endl; } virtual void vfunc1() { cout << "A vfunc1" << endl; } private: int m_a; int m_b; }; class B : public A { public: void func1() { cout << "B func1" << endl; } virtual void vfunc1() { cout << "B vfunc1" << endl; } private: int m_a; };
子类重写父类的虚函数,需要函数签名保持一致,该种情况在内存中的结构为:
多态
父类指针指向子类对象的情况下,如果指针调用的是虚函数,则编译器会将会从虚指针所指的虚函数表中找到对应的地址执行相应的函数。
子类很多的话,每个子类都覆盖了对应的虚函数,则通过虚表找到的虚函数执行后不就执行了不同的代码嘛,表现出多态了嘛。
我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。
那么,什么时候会执行函数的动态绑定?这需要符合以下三个条件。
- 通过指针来调用函数
- 指针 upcast 向上转型(继承类向基类的转换称为 upcast)
- 调用的是虚函数
为什么父类指针可以指向子类?
子类继承自父类,子类也属于A的类型。
最后通过一个例子来体会一下吧:
class Shape { public: virtual void draw() = 0; }; class Rectangle : public Shape { void draw() { cout << "rectangle" << endl; } }; class Circle : public Shape { void draw() { cout << "circle" << endl; } }; class Triangle : public Shape { void draw() { cout << "triangle" << endl; } }; int main(int argc, char const *argv[]) { vector<Shape*> v; v.push_back(new Rectangle()); v.push_back(new Circle()); v.push_back(new Triangle()); for (Shape* p : v) { p->draw(); } return 0; }
有些话是大白话,哈哈,如果这篇文章写的不错,解决了你的疑惑的话,点个赞再走吧!
不对的地方也请指出来,大家一起学习进步。
以上就是c++ 虚函数,虚表相关总结的详细内容,更多关于c++ 虚函数,虚表的资料请关注猪先飞其它相关文章!
相关文章
- vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
- 这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
- 整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
- 这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
- 这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
- 这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
- 这篇文章主要介绍了C#虚函数用法,实例分析了C#中虚函数的功能与基本使用技巧,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
- 这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
- 虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
- 这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
- 这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
- 这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25