- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章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;};
子类重写父类的虚函数,需要函数签名保持一致,该种情况在内存中的结构为
父类指针指向子类对象的情况下,如果指针调用的是虚函数,则编译器会将会从虚指针所指的虚函数表中找到对应的地址执行相应的函数.
子类很多的话,每个子类都覆盖了对应的虚函数,则通过虚表找到的虚函数执行后不就执行了不同的代码嘛,表现出多态了嘛.
我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了.
那么,什么时候会执行函数的动态绑定?这需要符合以下三个条件.
为什么父类指针可以指向子类?
子类继承自父类,子类也属于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++ 虚函数,虚表的资料请关注我其它相关文章! 。
原文链接:https://segmentfault.com/a/1190000039294766 。
最后此篇关于c++ 虚函数,虚表相关总结的文章就讲到这里了,如果你想了解更多关于c++ 虚函数,虚表相关总结的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
第一段代码工作正常,并给出了我需要的结果。我现在想做的是让它在 'as num' 上返回 3 个数字值对于“as num”上的 3 个不同值,对于同一列上的 3 个不同位置 SELEC
我想分析一些数据以编写定价算法。以下日期可用: 我需要三个变量/维度的函数/相关因子,它显示三个维度(pers_capacity、卧室数量、浴室数量)增长时中位数(价格)的变化。例如Y(#pers_c
正如标题所说 - 我的 Sprite Kit 游戏时不时地在后台崩溃,总是出现此错误 - Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Sub
假设我尝试保存以下数据,并且Songs模型的name属性上设置了Phalcon \ Mvc \ Model \ Validator \ PresenceOf验证器 // Get an existing
我有一个 if 控件,如下所示; if (Directory.Exists(System.IO.Path.Combine(systemPath, "Reports", companyName))
有人可以告诉我我们使用 ReadLine() 从文件 (.txt) 中读取特定行吗?现在我想读取文件的全部内容(不仅仅是第一行)。为此我需要使用什么方法。我用谷歌搜索了很多,但找不到解决方案。 我的代
我相信在大学时我用从 C 派生的语言为 FPGA 编写了一个程序。我了解 VHDL 和 verilog 等语言。但是,我不明白的是程序员在使用哪个方面有多少选择?它依赖于FPGA吗?我将使用 Xili
我有一个 if 控件,如下所示; if (Directory.Exists(System.IO.Path.Combine(systemPath, "Reports", companyName))
如何在运行时更改 Dashcode (Javascript) 中图像对象的源? 我试过: var image = document.getElementById("image").object;ima
我有几个相互关联的类,它们将被多种不同的算法使用 例子: struct B; struct A { B* parent; }; struct B { std::vector child
我正在开发一个网站,用户在客户收到的表中输入金额,如果任何客户没有提供分期付款(金额),则用户不会在表中输入任何金额,并且用户希望获取违约者的信息客户以10天为基础。所以我的问题是应该定义什么表和属性
我试图从上一个条目中选择一个值,并每次将该数字加一。我让它工作到选择当前条目值(默认 1000)并递增 1 并重新插入该值(因此每次最终都是 1001)。我需要它来选择该字段的最后一个条目,这样它将变
我不擅长“制作”查询。假设这是我的数据库: artist pics ------------------- -
最近,我要为我的网站做一个即时通知系统。我听说 COMET 在这种情况下必不可少。 我已经搜索 PHP 和 Comet 一段时间了,但是,我发现的指南和文章似乎只是循环中的 ajax 请求。例如,有一
我正在开发一款 iOS 游戏,我希望 clown 在场景外生成,然后向下移动。我的想法是全部创建它们,并将它们以 360 像素的距离放置在不可见的场景中。 像这样: SKSpriteNode *clo
我有以下子订单表。 mysql> select * from suborder; +-------------+------------------+ | order_state | bookin
这可能是一个有点初学者的问题,但考虑到在 Java 中调试编码是相当相关的:什么时候编码与 String 对象相关? 假设我有一个要保存到文件中的字符串对象。 String 对象本身是否使用某种我应该
首先我想说我是 CPP 的新手(我从 cpp11 开始):)考虑以下实体:学生(名字+姓氏)和组(描述+更多学生)。我在 C++ 中创建了以下 2 个类: class Student { privat
我正在尝试在单击该复选框时同步更新我的数据库。我决定使用 aJax,但它似乎无法识别 ajax。 代码:将成为 Switch_Active(this.id) 函数的元素 ... Deactivat
我正在创建一个菜单。菜单如下。 $('.category').mouseover(function() { $(this).removeClass('category').addClass('cate
我是一名优秀的程序员,十分优秀!