3.多继承无函数覆盖下的虚函数表
看完了单继承,我们来看看多继承的虚函数表:
#include "stdafx.h" class Base1{ public: int a; int b; virtual void Base1_1(){ printf("Base1:Function_1...\n"); } virtual void Base1_2(){ printf("Base1:Function_2...\n"); } }; class Base2{ public: int c; int d; virtual void Base2_1(){ printf("Base2:Function_1...\n"); } virtual void Base2_2(){ printf("Base2:Function_2...\n"); } }; class Sub1:public Base1,Base2{ public: int e; virtual void Sub_1(){ printf("Sub1:Sub_1...\n"); } virtual void Sub_2(){ printf("Sub1: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<6;i++){ pFn = (Function)*(function+i); pFn(); } return 0; }
根据我们上面的讲解,应该在虚函数表中有6个函数地址,但是程序在运行的时候照样提醒我:该地址不允许访问,说明在虚函数表中,不足6个函数。
我们看看程序输出窗口:
那么到底哪里出了问题?父类Base2中的虚函数去哪了?
我们先来看一下Sub的大小:
#include "stdafx.h" class Base1{ public: int a; int b; virtual void Base1_1(){ printf("Base1:Function_1...\n"); } virtual void Base1_2(){ printf("Base1:Function_2...\n"); } }; class Base2{ public: int c; int d; virtual void Base2_1(){ printf("Base2:Function_1...\n"); } virtual void Base2_2(){ printf("Base2:Function_2...\n"); } }; class Sub1:public Base1,Base2{ public: int e; virtual void Sub_1(){ printf("Sub1:Sub_1...\n"); } virtual void Sub_2(){ printf("Sub1:Sub_2...\n"); } }; int main(int argc, char* argv[]) { printf("%d",sizeof(Sub1)); return 0; }
我们可以看到程序输出窗口输出了28,我们来看看Sub的成员:继承了Base1的a和b,继承了Base2的c和d,自己的成员e,还有一张虚表,应该一共是24,可是它为什么输出了28?
其实通过课堂上老师的讲解我们已经知道:多重继承函数时会有多张虚表,而Base2的虚表就存在于this指针的第二个成员,他是Base2的虚表。
二.前期绑定和后期绑定
我们知道当程序调用函数的时候,有两种调用方式,一种是直接调用函数的地址,这种地址在程序编译的时候就已经写死了,另一种是通过一个地址,间接调用函数。
这里介绍一个名词:绑定,将函数与地址链接在一起的过程,叫做绑定。
直接调用函数的方式,在编译时就已将函数与地址绑定,我们称为(前期)编译期绑定
间接调用函数的方式,在运行的时候才进行绑定,我们称这种方式为(运行期)动态绑定或者晚绑定
注意:
只有virtual函数是动态绑定
三.多态
了解了前面的过程,多态的概念这里一句话就明白了:动态绑定还有另一个名字:多态。
这里给出多态的书面定义:
C++中的多态分为静态多态和动态多态。静态多态是函数重载,在编译阶段就饿能够确定调用哪个函数。动态多态是由继承产生的,指同一个属性或行为在基类和各派生类中具有不同的语义,不同的对象根据所接受的消息做出不同的响应,这种现象称为多态。
多态的实现需要满足三个条件:
(1)基类中声明虚函数
(2)派生类重写基类的虚函数
(3)将基类指针指向派生类对象,通过基类指针访问虚函数
我们来看看多态的具体实现,看看多态到底是什么:
#include "stdafx.h" class Base{ public: int x; Base(){ x=100; } virtual void Base_1(){ printf("Base:Function_1...\n"); } virtual void Base_2(){ printf("Base:Function_2...\n"); } }; class Sub:public Base{ public: int e; Sub(){ x=200; } virtual void Base_1(){ printf("Sub1:Sub_1...\n"); } }; void Test(Base* p){ int n = p->x; printf("%d\n",n); p->Base_1(); p->Base_2(); } int main(int argc, char* argv[]) { Base b; Base* p = &b; Test(p); return 0; }
首先我们定义了一个基类对象,并且通过基类指针去访问函数,我们来看看程序输出框:
我们定义了基类对象,并且通过基类指针去访问函数,当然是没有任何问题的。
接下来我们看看多态的实现:
创建一个基类,再创建一个派生类,将基类函数覆盖,通过基类指针访问派生类:
#include "stdafx.h" class Base{ public: int x; Base(){ x=100; } virtual void Base_1(){ printf("Base:Function_1...\n"); } virtual void Base_2(){ printf("Base:Function_2...\n"); } }; class Sub:public Base{ public: int e; Sub(){ x=200; } virtual void Base_1(){ printf("Sub1:Sub_1...\n"); } }; void Test(Base* p){ int n = p->x; printf("%d\n",n); p->Base_1(); p->Base_2(); } int main(int argc, char* argv[]) { Sub b; Base* p = &b; Test(p); return 0; }
我们来看看程序输出窗口:
我们可以看到,基类中属性的值也被改变,并且基类中函数也被覆盖,这就是我们所说的多态,同一个属性或行为在基类和各派生类中具有不同的语义,不同的对象根据所接受的消息做出不同的响应。