怎么做ps4的视频网站,谷歌云安装wordpress,中国平安财产保险公司官网,小白学编程应该从哪里开始学个人主页~
多态#xff08;上#xff09;~ 多态 四、多态的原理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指针使用类型::成员函数的调用方式无法访问虚函数表所以静态成员函数无法放进虚函数表
构造函数不能是虚函数因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的
最好把基类的析构函数定义为虚函数因为如果基类的析构函数不是虚函数那么只会调用基类的析构函数而不会调用派生类的析构函数这会导致派生类部分的对象没有被正确析构可能会引发资源泄露
对象在访问虚函数与普通函数速度的对比如果是普通对象访问两者一样快如果是多态对象访问指针对象或者引用对象则调用普通函数更快因为虚函数构成多态运行时需要到虚函数表中去查找
虚函数表在编译阶段就生成了 今日分享就到这里了~