多态的原理
虚函数表
class Base { public: virtual void Test() { cout << "Test()" << endl; } private: int _a = 0; }; int main() { Base b; cout << sizeof(b) << endl; return 0; }
按照以往的方式计算 Base类的大小:类中成员变量只有 int类型大小四个字节,成员函数 Test不占空间,所以类的大小应该是四个字节
运行结果如下
运行结果和预算的结果不一样,这是怎么回事呢?难道是成员函数也要计算大小吗?接下来,通过监视一探究竟
原来对象类中除了成员变量之外,还有_vfptr,也就是接下来要学习的虚函数表指针,想要学习指针,就先来了解虚函数表
虚函数表:用来存放虚函数,就如上面[0]中存放的就是Test的地址;本质就是函数指针数组
class Car { public: virtual void Test1() { cout << "Car:Test1()" << endl; } virtual void Test2() { cout << "Car:Test2()" << endl; } private: int _a = 0; }; class NIO : public Car { public: virtual void Test1() { cout << "NIO:Test1()" << endl; } private: int _b = 0; }; int main() { Car c; NIO et7; return 0; }
完成重写的虚函数,表函数表对应位置覆盖成重写的虚函数
多态的原理
int main() { Car c; NIO et7; //多态调用 Car* ptr = &c; ptr->Test1(); ptr = &et7; ptr->Test1(); //普通调用 ptr = &c; ptr->Test2(); ptr = &et7; ptr->Test2(); return 0; }
运行结果如下
普通调用由调用对象类型决定:Test2()函数不是虚函数,调用类型是Car;多态调用由指针/引用指向的对象决定:Test1()函数是虚函数,两次调用指向的对象都不同
再通过汇编进行观察
普通调用在编译时就确定好的(静态);多态调用在编译时是不确定的(动态)运行之后在,根据调用对象指向的类型,在表函数表中找到对应的函数进行调用
动态绑定与静态绑定
静态绑定,在程序编译期间就已经确定程序的行为,也称静态多态
动态绑定,在程序运行期间,根据具体的类型确定程序具体的行为,调用具体的函数,也称动态多态
单继承和多继承关系中的虚函数表
单继承中的虚函数表
观察下列代码
class Car { public: virtual void test1() { cout << "Car test1()" << endl; } virtual void test2() { cout << "Car test2()" << endl; } private: int _a; }; class NIO:public Car { public: virtual void test1() { cout << "NIO test1()" << endl; } virtual void test3() { cout << "NIO test3()" << endl; } private: int _b; }; int main() { Car c; NIO et7; return 0; }
通过监视窗口能够发现:在派生类对象et7中重写了test1,继承了test2,但是本身的test3却没有,在内存窗口看到一个未知的地址,可以猜测是test3函数,现在就是想办法将其打印出来进行验证
上面了解到虚函数表本质是函数指针数组,接下来通过函数指针数组将虚函数表打印出来
typedef void(*_vfptr)(); void Print_vfptr(_vfptr vft[]) { for (int i = 0; vft[i] != nullptr; i++) { printf("[%d]:%p->", i, vft[i]); vft[i](); } cout << endl; }
通过将对象进行取址再强转 int*获取前四个字节,在类型转换 _vfptr*传递给函数进行打印
Print_vfptr((_vfptr*)*(int*)&c); Print_vfptr((_vfptr*)*(int*)&et7);
完整代码如下
class Car { public: virtual void test1() { cout << "Car test1()" << endl; } virtual void test2() { cout << "Car test2()" << endl; } private: int _a; }; class NIO:public Car { public: virtual void test1() { cout << "NIO test1()" << endl; } virtual void test3() { cout << "NIO test3()" << endl; } private: int _b; }; typedef void(*_vfptr)(); void Print_vfptr(_vfptr vft[]) { for (int i = 0; vft[i] != nullptr; i++) { printf("[%d]:%p->", i, vft[i]); vft[i](); } cout << endl; } int main() { Car c; Print_vfptr((_vfptr*)*(int*)&c); NIO et7; Print_vfptr((_vfptr*)*(int*)&et7); return 0; }
打印结果和预期一致
多继承中的虚函数表
class NIO { public: virtual void test1() { cout << "NIO test1()" << endl; } virtual void test2() { cout << "NIO test2()" << endl; } private: int _a; }; class XPENG { public: virtual void test1() { cout << "XPENG test1()" << endl; } virtual void test2() { cout << "XPENG test2()" << endl; } private: int _b; }; class NEA :public NIO, public XPENG { public: virtual void test1() { cout << "NEA test1()" << endl; } virtual void test3() { cout << "NEA test3()" << endl; } private: int _c; }; int main() { NIO et7; XPENG p7; NEA car; return 0; }
通过监视会发现与上面类似的疑问,派生类 NEA对象本身的虚函数存放在第一个基类 NIO中还是第二个基类 XPENG中呢?
接下来再次通过函数指针数组进行验证
typedef void(*_vfptr)(); void Print_vfptr(_vfptr vft[]) { for (int i = 0; vft[i] != nullptr; i++) { printf("[%d]:%p->", i, vft[i]); vft[i](); } cout << endl; } class NIO { public: virtual void test1() { cout << "NIO test1()" << endl; } virtual void test2() { cout << "NIO test2()" << endl; } private: int _a; }; class XPENG { public: virtual void test1() { cout << "XPENG test1()" << endl; } virtual void test2() { cout << "XPENG test2()" << endl; } private: int _b; }; class NEA :public NIO, public XPENG { public: virtual void test1() { cout << "NEA test1()" << endl; } virtual void test3() { cout << "NEA test3()" << endl; } private: int _c; }; int main() { NIO et7; Print_vfptr((_vfptr*)*(int*)&et7); XPENG p7; Print_vfptr((_vfptr*)*(int*)&p7); NEA car; //NIO虚函数表 Print_vfptr((_vfptr*)*(int*)&car); XPENG* ptr = &p7; //XPENG虚函数表 Print_vfptr((_vfptr*)*(int*)ptr); return 0; }
打印结果显示:派生类本身的虚函数是存放在第一个基类的虚函数表中的