当前位置: 首页 > news >正文

wordpress文章站电子商务推广方式

wordpress文章站,电子商务推广方式,江苏付费网络推广培训,新乡手机网站建设哪家专业个人主页~ 多态(上)~ 多态 四、多态的原理1、虚表的存储位置2、多态的原理3、动态绑定和静态绑定 五、单继承和多继承关系的虚函数表1、单继承中的虚函数表2、多继承中的虚函数表 六、多态中的一些小tips 四、多态的原理 1、虚表的存储位置 class A {…

在这里插入图片描述

个人主页~

多态(上)~


多态

  • 四、多态的原理
    • 1、虚表的存储位置
    • 2、多态的原理
    • 3、动态绑定和静态绑定
  • 五、单继承和多继承关系的虚函数表
    • 1、单继承中的虚函数表
    • 2、多继承中的虚函数表
  • 六、多态中的一些小tips

四、多态的原理

1、虚表的存储位置

class A {
public:virtual void func1() {cout << "A::func1" << endl; }virtual void func2() {cout << "A::func2" << endl; }
private:int _a;
};void func()
{cout << "void func()" << endl;
}int main()
{A a1;A a2;static int a = 0;int b = 0;int* p1 = new int;const char* p2 = "hello world";printf("静态区:%p\n", &a);printf("栈:%p\n", &b);printf("堆:%p\n", p1);printf("代码段:%p\n", p2);printf("虚表:%p\n", *((int*)&a1));printf("虚函数地址:%p\n", &A::func1);printf("普通函数地址:%p\n", func);return 0;
}

在这里插入图片描述
被static修饰的变量a存放在静态区,局部变量b存储在栈区,指针p1指向在堆上开辟出的对象,常量字符串的指针存放在代码段

虚表这里,因为a1是一个类对象,它的地址存放了虚表指针和内置类型_a两部分,虚表指针是一个void类型的指针,占4个字节,把它强制转换成int*类型的指针,再解引用,得到的是虚表的指针

虚函数地址就是固定用法,要把是哪个类的虚函数标注出来,然后用取地址符号

从上图我们可以观察到,虚函数和普通函数存放位置接近,代码段和虚表存放位置接近,而虚表和虚函数相对于静态区栈区以及堆区来说还是离代码段更近近,也就是说,虚函数和普通函数以及虚表存放在代码段

2、多态的原理

class A
{
public:virtual void D(){cout << "A : virtual void D()" << endl;}
};class B : public A
{
public:virtual void D(){cout << "B : virtual void D()" << endl;}
};void func(A& ra)
{ra.D();
}void test()
{A a;B b;func(a);func(b);
}

在这里插入图片描述
在这里插入图片描述
当ra为A对象时,函数调用时在A的虚表中找到func,当ra为B对象时,函数调用时在B的虚表中找到func,然后调用,这样就实现出了不同对象去完成同一行为时,展现出不同的形态

我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数

class A
{
public:virtual void D(){cout << "A : virtual void D()" << endl;}
};class B : public A
{
public:virtual void D(){cout << "B : virtual void D()" << endl;}
};void func(A* p)
{p->D();
}void test()
{A a;func(&a);a.D();
}

在这里插入图片描述
在这里插入图片描述
对于多态调用:
p中存的是A对象的指针,将p移动到eax中:
00382571 mov eax,dword ptr [p]

[eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx:
00382574 mov edx,dword ptr [eax]

[edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax:
0038257B mov eax,dword ptr [edx]

call eax中存虚函数的指针,这里可以看出满足多态的调用,不是在编译时确定的,是运行起来以后到对象的中取找的

对于普通调用:
因为不满足多态调用所以是普通函数调用,直接call地址

3、动态绑定和静态绑定

静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载

动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态

五、单继承和多继承关系的虚函数表

1、单继承中的虚函数表

class A 
{
public:virtual void func1() {cout << "A::func1" << endl; }virtual void func2() {cout << "A::func2" << endl; }
private:int _a;
};
class B :public A 
{
public:virtual void func1() {cout << "B::func1" << endl; }virtual void func3() {cout << "B::func3" << endl; }virtual void func4() {cout << "B::func4" << endl; }
private:int _b;
};int main()
{A a;B b;return 0;
}

在这里插入图片描述
图中的监视窗口中我们发现看不见func3和func4,这里是编译器的监视窗口故意隐藏了这两个函数

那我们如何查看整个b的虚表呢

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << "虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf("第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{A a;B b;VFPTR * vTablea = (VFPTR*)(*(int*)&a);PrintVTable(vTablea);VFPTR* vTableb = (VFPTR*)(*(int*)&b);PrintVTable(vTableb);return 0;
}

在这里插入图片描述

PrintVTable函数:
核心点就是这个函数指针VFPTR,这是一个函数指针,指向的类型是void*,参数为(),也就是无参,也就是说这个指针可以指向任意一个返回类型为void*并且无参的函数

PrintVTable函数的参数也可以写成VFPTR* vTable,虚表的地址就是指针vTable,后加[]就是对表中的指针进行访问,打印出它们的指针,并且将这些指针指向的函数调用表示出来,让我们可以看到这个地址对应的是哪个函数

main函数:
取出a、b对象的头4bytes,就是虚表的指针,虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr
1.先取a的地址,强转成一个int*的指针

2.再解引用取值,就取到了a对象头4bytes的值,这个值就是指向虚表的指针

3.再强转成 VFPTR* ,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组

4.虚表指针传递给PrintVTable进行打印虚表

5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题,我们只需要点重新生成解决方案就行

2、多继承中的虚函数表

class A1 
{
public:virtual void func1() {cout << "A1::func1" << endl; }virtual void func2() {cout << "A1::func2" << endl; }
private:int a1;
};
class A2 
{
public:virtual void func1() {cout << "A2::func1" << endl; }virtual void func2() {cout << "A2::func2" << endl; }
private:int a2;
};
class B : public A1, public A2 
{
public:virtual void func1() {cout << "B::func1" << endl; }virtual void func3() {cout << "B::func3" << endl; }
private:int b;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{cout << "虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf("第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}int main()
{B b;VFPTR* vTablea1 = (VFPTR*)(*(int*)&b);PrintVTable(vTablea1);VFPTR* vTablea2 = (VFPTR*)(*(int*)((char*)&b + sizeof(A1)));PrintVTable(vTablea2);return 0;
}

在这里插入图片描述

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中,也就是func3函数,第一个继承基类就是最左边继承的这个基类

在这里插入图片描述

六、多态中的一些小tips

内联函数可以是虚函数,但是如果被inline修饰的函数是虚函数,那么inline特性将会消失,被修饰的函数相当于没被修饰

静态成员不可以是虚函数,因为静态成员没有this指针使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

构造函数不能是虚函数,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

最好把基类的析构函数定义为虚函数,因为如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这会导致派生类部分的对象没有被正确析构,可能会引发资源泄露

对象在访问虚函数与普通函数速度的对比,如果是普通对象访问,两者一样快,如果是多态对象访问(指针对象或者引用对象),则调用普通函数更快,因为虚函数构成多态,运行时需要到虚函数表中去查找

虚函数表在编译阶段就生成了


今日分享就到这里了~

在这里插入图片描述

http://www.ritt.cn/news/6277.html

相关文章:

  • 消防电气火灾监控系统网站开发网站主页
  • 刚做网站做多用户还是单用户网络营销的概述
  • 京东网上购物平台深圳seo排名
  • 朔州做网站的公司免费做网站推广的软件
  • 医院网站建设 价格低广告联盟哪个比较好
  • 做艺人资料卡的网站哈尔滨网络优化推广公司
  • 网站如何做三端适配上海专业的seo公司
  • 发卡网站建设方案网站推广的案例
  • 中企动力网站策划百度收录接口
  • 网站建设是属于b2免费推广方法有哪些
  • 漯河网站制作网址域名大全2345网址
  • 完成网站建设成本搜索引擎推广步骤
  • 汕头网站排名优化贵阳网站优化公司
  • 网址大全2345电脑版seo推广思路
  • 中国建设银行湖南分行官网站搜索引擎收录查询工具
  • 苏州模板建站定制品牌推广和品牌营销
  • 网站图片特效源码广州seo关键词
  • wordpress小工具分享谷歌seo招聘
  • 17网站一起做网店广促销策略的四种方式
  • 模板网站建站优化大师网页版
  • 英文网站编辑怎么做东莞seo优化seo关键词
  • 群晖 nas 做网站附近广告公司联系电话
  • 淘宝客网站怎么做分销网站怎么做出来的
  • 服装网站论文深圳百度代理
  • 青岛哪里做网站百度推广怎么操作
  • 网站产品演示seo免费推广
  • 做电商需要哪些网站有哪些360免费建站系统
  • 上海三凯监理建设管理咨询公司网站注册网站
  • 有关做能源的网站营销案例100例
  • 西宁网站百度网站推广一年多少钱