在上一章节中,我们讲述了虚函数和虚函数表,我们知道了不管在类中有多少个虚函数,都不会使类的大小扩大,在this指针中,只会多出一个虚函数表的地址,是this指针的第一个内容,在虚函数表中,函数是根据虚函数定义的顺序排列的,在这一章节中,我们将通过深入解析虚函数表,从而从本质上理解多态。
一.深入探索虚函数表
我们知道在虚函数表中存储的是该函数的地址,那么我们该如何验证?
我们可以通过函数指针的方式来调用存储在虚函数表中的函数:
#include "stdafx.h" class Base{ public: int a; int b; Base(){ a = 1; b = 2; } void Function_1(){ printf("Base:Function_1...\n"); } virtual void Function_2(){ printf("Base:Function_2...\n"); } virtual void Function_3(){ printf("Base:Function_3...\n"); } }; int main(int argc, char* argv[]) { typedef void (*Function)(void); Base b; int* p; p = (int*)&b; int* function; function = (int*)(*p); Function pFn; for(int i=0;i<2;i++){ pFn = (Function)*(function+i); pFn(); } return 0; }
这里我们通过函数指针来调用虚函数表中的地址,发现虚函数表中存的确实是虚函数的地址,且顺序是按照定义虚函数的顺序排列的。
1.单继承无函数覆盖下的虚函数表
我们知道在虚函数表中存的只有虚函数的地址,所以我们在写代码的时候不再写构造函数和析构函数,我们只写虚函数
#include "stdafx.h" class Base{ public: int a; int b; virtual void Function_1(){ printf("Base:Function_1...\n"); } virtual void Function_2(){ printf("Base:Function_2...\n"); } }; class Sub1:public Base{ public: int c; virtual void Sub_1(){ printf("Sub1:Sub_1...\n"); } virtual void Sub_2(){ printf("Sub2:Sub_2...\n"); } }; int main(int argc, char* argv[]) { typedef void (*Function)(void); Sub1 b; int* p; p = (int*)&b; int* function; function = (int*)(*p); Function pFn; for(int i=0;i<4;i++){ pFn = (Function)*(function+i); pFn(); } return 0; }
我们看看程序输出窗口:
我们观察代码就能知道,我们通过函数指针调用函数的时候,是根据虚函数表的顺序调用的,所以我们得出结论:
当继承父类的时候,父类的虚函数会在派生类虚函数的上面,且它们的顺序都为定义虚函数的顺序。
2.单继承有函数覆盖下的函数虚表
#include "stdafx.h" class Base{ public: int a; int b; virtual void Function_1(){ printf("Base:Function_1...\n"); } virtual void Function_2(){ printf("Base:Function_2...\n"); } }; class Sub1:public Base{ public: int c; virtual void Function_1(){ printf("Sub1:Function_1...\n"); } virtual void Sub_1(){ printf("Sub1:Sub_1...\n"); } virtual void Sub_2(){ printf("Sub2:Sub_2...\n"); } }; int main(int argc, char* argv[]) { typedef void (*Function)(void); Sub1 b; int* p; p = (int*)&b; int* function; function = (int*)(*p); Function pFn; for(int i=0;i<5;i++){ pFn = (Function)*(function+i); pFn(); } return 0; }
这里注意一个细节,在通过函数指针调用函数的时候,我写了一个for循坏,并且次数为5,但是在程序运行的时候,它弹出来一个窗口告诉我该地址不可访问,则说明虚函数表里的函数肯定比五个少。
我们来看看程序输出窗口:
这里打印出的函数顺序,实际上就是虚函数表里的函数顺序。
这时候,我们应该记得在课堂上,“铁男“说过一句话:”覆盖的是哪个,就在那个表里“。这里我解释一下:子类Function_1覆盖的是父类的虚函数,那么这个函数就出现在”父类的虚函数表“里(注意这里我们只是形象地称为父类的虚函数表,实际上这里只有一张虚函表哦),但是函数的具体功能是我们后定义的那个函数的功能。