多态
1.继承和虚函数
1)没有继承时虚函数表
Base结构,里面有3个函数:Function1、Function2、Function3;
虚表:
2)单继承无函数覆盖
Base结构:
Function1、2、3;
Sub结构继承Base:
Function4、5、6;
虚表:
子类对象的虚表中包含子类和父类的全部虚函数;
3)单继承有函数覆盖
Base:
Function1、2、3;
Sub继承Base:
Function1、2、6;
虚表:
虚表中包含子类中的全部虚函数和父类中没有被覆盖的虚函数;
4)多继承无覆盖
Base1:
Function1、2;
Base2:
Function3、4;
Sub继承了Base1和Base2:
Function5、6;
虚表:
有两个虚表;
虚表1中有Base1和Sub中的所有虚函数;
虚表2中有Base2中所有的虚函数;
Sub结构相对没有虚函数的结构于多了8个字节;
也就是说,有几个虚表结构中就需要保存几个虚表的地址;
5)多继承有覆盖
Base1:
Function1、2;
Base2:
Function3、4;
Sub继承Base1和Base2:
Function1、3、5;
虚表:
子类对象覆盖了哪个函数,就将覆盖后的函数的地址放在哪个被覆盖函数的父类的虚表中;
子类特有的函数放第一个虚表中;
6)多重继承无覆盖
Base1:
Function1、2;
Base2继承Base1:
Function3、4;
Sub继承Base2:
Function5、6;
虚表:
有一个虚表,包含子类和父类以及上级父类所有的虚函数;
7)多重继承有覆盖
Base1:
Function1、2;
Base2继承Base1:
Function1、3;
Sub继承Base2:
Function5;
虚表:
8)多重继承有覆盖2
Base1:
Function1、2;
Base2继承Base1:
Function1、3;
Sub继承Base2:
Function1、5;
虚表:
9)多重继承有覆盖3
Base1:
Function1、2;
Base2继承Base1:
Function3;
Sub继承Base2:
Function1、3;
虚表:
总之多重继承中有一个虚表,如果有覆盖则虚表中保存最下级对象的函数的地址
2.绑定
绑定就是将函数调用与地址关联起来.
1)关于绑定的过程
代码:
class Base { public: int x; public: Base() { x = 100; } void Function_1() { printf("Base:Function_1...\n"); } virtual void Function_2() { printf("Base:Function_2...virtual\n"); } }; class Sub:public Base { public: int x; public: Sub() { x = 200; } void Function_1() { printf("Sub:Function_1...\n"); } virtual void Function_2() { printf("Sub:Function_2...virtual\n"); } }; void TestBound(Base* pb) { int n = pb->x; printf("%d\n",n); pb->Function_1(); //函数调用 pb->Function_2(); } int main(int argc, char* argv[]) { printf("传入Base对象:\n"); Base b; TestBound(&b); printf("传入sub对象:\n"); Sub sb; TestBound(&sb); getchar(); return 0; }
结果:
2)什么是前期绑定?什么是后期绑定、动态绑定、运行期绑定?
void TestBound(Base* pb) { int n = pb->x; printf("%x\n",n); pb->Function_1(); pb->Function_2(); //体现出了不同的行为 称为多态 }
前期绑定:
如果程序编译完函数的地址就已经确定,称为编译期绑定,也叫前期绑定;
后期绑定:
如果函数的地址只有在真正调用时才确定,称为动态绑定,也叫后期绑定;
反汇编分析:
虚函数调用时,FF call的是虚表地址;
此时实际call的地址并不能确定,也就是编译时没有写成绝对地址;
该地址根据传入的参数,也就是对象地址来决定;
也就是调用函数时才确定函数的实际地址,即动态绑定;
3)总结
1】只有virtual的函数是动态绑定.(其它的是编译器绑定的)
2】动态绑定还有一个名字:多态
一种类型能体现不同的行为,称为多态(也就是父类类型通过指向不同的子类对象调用同一虚函数时可能有不同效果);
动态绑定 = 多态;
绑定分为前期绑定和动态绑定;
在c++中,动态绑定是通过虚表来实现的;
如果没有虚表就没有多态,这就意味着父类指针永远无法访问被子类覆盖的方法,只能访问自己未被覆盖前的方法;
一般需要把析构函数设为virtual,考虑到父类指针指向的子类对象释放内存时,如果析构函数不是虚函数,将释放的是父类对象;
3.练习
有一个Base类,3个sub类都继承Base;
每个类都有一个print方法,用来输出类成员;
在一个Base数组中保存这4个类的对象,然后分别调用其print方法;
观察当print函数位普通函数和虚函数时输出的区别;
#include "stdafx.h" class Base{ public: int x; int y; Base(){ x = 1; y = 2; } void print(){ printf("Base\t->x=%d,y=%d\n",x,y); } }; class sub1:public Base{ public: int a; sub1(){ x = 11; y = 12; a = 91; } void print(){ printf("sub1\t->x=%d,y=%d,a=%d\n",x,y,a); } }; class sub2:public Base{ public: int b; sub2(){ x = 21; y = 22; b = 92; } void print(){ printf("sub2\t->x=%d,y=%d,b=%d\n",x,y,b); } }; class sub3:public Base{ public: int c; sub3(){ x = 31; y = 32; c = 93; } void print(){ printf("sub3\t->x=%d,y=%d,c=%d\n",x,y,c); } }; int main(int argc, char* argv[]) { Base b; sub1 s1; sub2 s2; sub3 s3; Base* sb[] = {&b,&s1,&s2,&s3}; for(int i=0;i<4;i++){ sb[i]->print(); } getchar(); return 0; }
结果:
可以看到:
当print函数为普通函数时,Base的指针指向子类的对象,调用的都是Base的print函数;
将所有的print函数改为虚函数时:
可以看到:
当print函数为虚函数时,Base指针指向的子类对象,调用的是子类覆盖后的print函数;实现了多态;