小型网站项目策划书,广告设计专业英语,网站建设需要ui吗,第三方平台广告营销是什么C是如何实现多态的结论#xff1a;C通过虚函数来实现多态的#xff0c;根本原因是派生类和基类的虚函数表的不同。
构成多态的必要条件有如下3点#xff1a;
存在继承关系基类存在虚函数#xff0c;且派生类有相同原型的函数遮蔽它存在基类类型的指针指向派生类对象…
C是如何实现多态的结论C通过虚函数来实现多态的根本原因是派生类和基类的虚函数表的不同。
构成多态的必要条件有如下3点
存在继承关系基类存在虚函数且派生类有相同原型的函数遮蔽它存在基类类型的指针指向派生类对象且该指针调用了存在遮蔽关系的虚函数
如下图就是一个简单的多态的例子实验环境:vs2019
大猪眉头一皱觉得事情并不简单。
针对上述实验结果有个疑点
基类指针是如何知道要调用派生类的f函数而不是基类的f函数的呢
这其中的奥秘还是要从内存模型开始探索。谈到内存模型那么我们先探讨最简单的——这个对象有多大吧或者说这个对象占几个字节的内存吧。 如上图所示A的大小在32位编译模式下是4字节在64位编译模式下是8字节由此可见A内存模型里面肯定有个指针原因https://blog.csdn.net/qq_18138105/article/details/105209406
那么继续深入分析对象的内存模型这个指针到底指向什么呢
实际上这个指针指向一个数组数组中的每个元素都是虚函数的入口地址这个数组也就是虚函数表存在于C内存模型中的常量区由于虚函数表和对象在内存上是分开存储的(虚函数在C内存模型中的代码区对象在C内存模型中的堆区指向对象的指针在C内存模型的栈区)因此就需要在对象中需要安插一个指向这个虚函数表的指针
我们再看一个例子 各个对象的内存模型如下(我用cocos creator画的) 如上图所示对象内存和虚函数表内存分开的对象的*vfptr指向对应的虚函数表。注意和很多博客画的图不同为了直观我是把处于内存高地址的放上面处于内存低地址的放下面
仔细观察派生类B的虚函数表
派生类如果存在对基类有遮蔽关系的虚函数则在虚函数表中则取派生类的这个虚函数的入口地址如B::f对于未被派生类遮蔽的基类的虚函数派生类的虚函数表则取基类的这个虚函数的入口地址如A::g派生类新增的虚函数依次往虚函数表后面加如B::h
因此我们通过指针调用虚函数时先根据指针找到对象里的vfptr来定位到虚函数表然后通过虚函数在虚函数表中的索引值来得到虚函数的入口地址。
比如 a-f() 实际上编译器会这么处理 (*(*(a0)0))(a)
a0 是 a对象 vfptr 的地址*(a0)是 vfptr的值 又 vfptr 是指向虚函数表的指针因此 *(a0) 也是虚函数表的首地址由于 A::f 函数在虚函数表中的索引是0因此 (*(a0)0)就是获取 A::f 函数的入口地址知道了A::f 函数的地址*(*(a0)0) 就是对 A::f 的调用把a对象的指针传入 A::f, 就是a作为 A::f 的 this指针
同理调用 b-f() 也是一样的只不过访问的是 B的虚函数表最后调用的是 B::f 而不是 A::f 这就解释了 “基类指针是如何知道要调用派生类的f函数而不是基类的f函数” 的问题就是因为虚函数表的不同。
疑问虽然已经解决了但是我们还是要继续细探究竟经过下面的实验得出的 结果和内存模型完全相符
#include iostream
using namespace std;class A {
public:A() : a(100) {}virtual void f() {cout A::f endl;}virtual void g() {cout A::g endl;}
protected:int a;
};class B : public A {
public:B() : b(50) {}virtual void f() {cout B::f endl;}virtual void h() {cout B::h endl;}
protected:int b;
};// 定义一个 参数为 A*类型 返回值是 void 的 函数指针 Fun
typedef void (*Fun)(A*);// 指针值类型64位编译模式下是long long 32位编译模式下是int
#ifdef _WIN64
#define ptr_value long long
#else
#define ptr_value int
#endif
// 根据对象指针和偏移量 获取 指针值类型的指针
#define ptr(obj, offset) ((ptr_value*)objoffset)int main() {A* a new A;A* b new B;// 接下来探究 a对象 和 b对象 的 内存模型 //ptr_value a_vfptr_value *ptr(a, 0); // a_vfptr的值 即 b的虚函数表的首地址((Fun)*ptr(a_vfptr_value, 0))(a); // A::f((Fun)*ptr(a_vfptr_value, 1))(a); // A::g ptr_value a_a *ptr(a, 1); // a对象 的 int a成员cout (int)a_a endl; // 100ptr_value b_vfptr_value *ptr(b, 0); // b_vfptr的值 即 b的虚函数表的首地址((Fun)*ptr(b_vfptr_value, 0))(b); // B::f((Fun)*ptr(b_vfptr_value, 1))(b); // A::g ((Fun)*ptr(b_vfptr_value, 2))(b); // B::hptr_value b_a *ptr(b, 1); // b对象 的 int a成员cout (int)b_a endl; // 100ptr_value b_b *ptr(b, 2); // b对象 的 int b成员cout (int)b_b endl; // 50return 0;
}因此只要理解了虚函数表C的多态自然就迎刃而解了。