由浅及深,首先,我们来看一下C++中的对象的类型,对象的类型分为两种:静态类型、动态类型;
看下面的代码:
class Base
{
public:
void FunTest1()
{
cout<<"Base::FunTest1()"<<endl;
}
int _b;
};
class Deriver1:public Base
{
public :
void FunTest2()
{
cout<<"Deriver1::FunTest2()"<<endl;
}
int _c1;
};
class Deriver2:public Base
{
public:
void FunTest3()
{
cout<<"Deriver2::FunTest3()"<<endl;
}
int _c2;
};
void FUnTest()
{
Deriver1 *p1 = new Deriver1;
Base *p2 = p1;
Deriver2* p3 = new Deriver2;
p2 = p3;
}
分析:其中,p1的静态类型为Deriver1,p2初始的静态类型为Base,动态类型为Deriver1,后动态类型改变,变为Deriver2,p3的静态类型为Deriver2;
综上所述,多态也分为静态多态和动态多态;
静态多态:包括在编译阶段就决定的函数重载、泛型编程,但不包括宏(宏在预处理阶段);
实现静态多态时,在编译期间,编译器会根据函数实参的类型(不排除会进行隐式类型转换的可能),推断出要调用哪个函数(该函数必须存在,不然会报错);
动态多态:使用虚函数(即:在基类的成员函数前加上virtual关键字修饰,而在派生类中需要重写基类的虚函数)实现;
在动态多态中还有一个动态绑定的概念,什么是动态绑定?
动态绑定:在程序的执行期间(不是编译期间)判断所引用对象的实际类型,根据其实际类型调用相对应的方法;
现在看一个例子(如何实现动态多态):
class Base
{
public:
virtual void FunTest1()
{
cout<<"Base::FunTest1()"<<endl;
}
int _b;
};
class Deriver1:public Base
{
public :
virtual void FunTest1()
{
cout<<"Deriver1::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"Deriver1::FunTest2()"<<endl;
}
int _c1;
};
void FunTest(Base& b)
{
b.FunTest1();
}
int main()
{
Base b;
Deriver1 c1;
FunTest(b);
FunTest(c1);
system("pause");
return 0;
}
如何实现动态多态:
(1)在继承体系中,基类必须至少有一个虚函数(virtual关键字修饰),除此之外,派生类中必须重写基类的虚函数;
(2)必须使用基类的指针或引用来调用基类的虚函数;
那什么是重写(覆盖)?它与函数重载及重定义(隐藏)有什么区别?
接下来,介绍另一种成员函数,即:纯虚函数;
在成员函数的形参列表后面加上"=0",表明该成员函数为纯虚函数,包含纯虚函数的类称为抽象类(或接口类),抽象类不能实例化出对象,若纯虚函数在派生类中被重新定义,派生类才能实例化出对象;
class Base
{
public:
virtual void FunTest1()=0
{
cout<<"Base::FunTest1()"<<endl;
}
int _b;
};
class Deriver1:public Base
{
public :
virtual void FunTest1()
{
cout<<"Deriver1::FunTest1()"<<endl;
}
int _c1;
};
void FunTest(Base& b)
{
b.FunTest1();
}
int main()
{
/*Base b;*/
Deriver1 c1;
FunTest(c1);
system("pause");
return 0;
}
运行此程序时发现,若在派生类中没有重新定义virtual void FunTest1(){},则会报错;
那么,哪些函数可作为虚函数,哪些不可作为虚函数呢?
构造函数不可作为虚函数:构造函数用来创建对象,若可作为虚函数,则需找出虚表指针,而虚表指针存在于对象的前4个字节,对象没有创建出来,就找不到虚表指针,也无法创建对象,因此,构造函数不可作为虚函数;
友元函数不可作为虚函数:友元函数不是类的成员函数,而虚函数是类的成员函数,因此,友元函数不可作为虚函数;
静态成员函数不可作为虚函数:静态成员函数在内存中只有一份,不隶属于任何对象,而虚函数必须根据对象类型才能知道调用哪一个虚函数,无法通过对象调用虚函数,因此,静态成员函数不可作为虚函数;
析构函数可作为虚函数(最好在基类中声明为虚函数);
赋值运算符可作为虚函数(但最好不要将它设置为虚函数);
虚表剖析:
添加了虚函数的基类的对象模型:(以下面程序为例)
class Base
{
public:
virtual void FunTest1()
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"Base::FunTest3()"<<endl;
}
int _b;
};
int main()
{
Base b;
b._b=2;
system("pause");
return 0;
}
单继承下,对关于派生类的对象模型的研究,可得以下结论:
(1)虚函数表中按声明次序存放虚函数的地址;
(2)在派生类的对象模型中,前面是基类的虚函数,后面是派生类中的虚函数;
多重继承下,派生类的对象模型(以下面程序为例):
class A
{
public:
virtual void FunTest1()
{
cout<<"A::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"A::FunTest2()"<<endl;
}
int _a;
};
class B
{
public:
virtual void FunTest3()
{
cout<<"B::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"B::FunTest4()"<<endl;
}
int _b;
};
class C:public A,public B
{
public:
virtual void FunTest1()
{
cout<<"C::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"C::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"C::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"C::FunTest4()"<<endl;
}
int _c;
};
int main()
{
C c;
c._a =10;
c._b =12;
c._c =7;
system("pause");
return 0;
}
此时,若调换继承A类和B类的顺序,并改变类C中特有的成员函数的定义顺序(如下面代码)则会出现:
class C:public B,public A
{
public:
virtual void FunTest6()
{
cout<<"C::FunTest6()"<<endl;
}
virtual void FunTest1()
{
cout<<"C::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"C::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"C::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"C::FunTest4()"<<endl;
}
virtual void FunTest5()
{
cout<<"C::FunTest5()"<<endl;
}
int _c;
};
从以上的验证中可以看出,
(1)派生类先继承哪一个类,则该类的内存布局位于派生类对象的内存布局的前一部分,后继承的类的内存布局位于派生类对象内存布局的后面一部分,最后是派生类自己的内存布局;
(2)先被继承的类的虚表中含有派生类自己定义的虚函数,且该虚函数位于先被继承的类的虚表中的最后一部分;
(3)派生类中自己定义的虚函数按照声明定义顺序位于先被继承的类的虚表的最后一部分;
(4)各个被继承的类的虚表中按照声明定义的顺序存放着虚函数;
菱形虚拟继承:
class A //菱形虚拟继承
{
public:
virtual void FunTest1()
{
cout<<"A::FunTest1()"<<endl;
}
int _a;
};
class c1:virtual public A
{
public:
virtual void FunTest1()
{
cout<<"c1::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"c1::FunTest2()"<<endl;
}
int _c1;
};
class c2:virtual public A
{
public:
virtual void FunTest1()
{
cout<<"c2::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"c2::Funtest3()"<<endl;
}
int _c2;
};
class D:public c1,public c2
{
public:
virtual void FunTest1()
{
cout<<"D::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"D::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"D::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"D::FunTest4()"<<endl;
}
int _d;
};
int main(){/*D d;d._a=1;d._c1=2;d._c2=3;d._d=4;*/typedef void (*Fun)(); void FunTest() { D d; c1& b1 = d; Fun* p = (Fun*)(*(int*) &b1); while (*p) { (*p)(); p=(Fun*)((int*)p+1); } cout<<endl; c2& b2 = d; p = (Fun*)(*(int*)&b2); while(*p) { (*p)(); p=(Fun*)((int*)p+1); } }
菱形虚拟继承的派生类的对象模型(以上面代码程序为例):FunTest(); system("pause"); return 0; }
在菱形虚拟继承中,通过改变类C1和类C2的继承顺序,可以得到以下几点:
(1)派生类D先继承哪一个类,则该类的内存布局(包括该类的虚表指针、指向偏移量表的指针和该类自己的成员)位于派生类对象的内存布局的前一部分,后继承的类的内存布局(包括该类的虚表指针、指向偏移量表的指针和该类自己的成员)位于派生类对象内存布局的后面一部分,接着是派生类自己的内存布局,最后是基类自己的内存布局(包括基类的虚表指针,基类自己的成员);
(2)先被继承的类的虚表中含有派生类自己定义的虚函数,且该虚函数位于先被继承的类的虚表中的最后一部分,后被继承的类的虚表中不含有派生类自己定义的虚函数;
(3)派生类中自己定义的虚函数按照声明定义顺序位于先被继承的类的虚表的最后一部分;
(4)各个被继承的类的虚表中按照声明定义的顺序存放着虚函数;
同时,若在派生类C1、派生类C2和派生类D中加入构造函数或析构函数,则派生类D的大小会+4(以上面程序为例);原派生类D的大小为36个字节,在派生类C1、派生类C2和派生类D中加入构造函数或析构函数后,派生类D的大小为40个字节,内存分布如下图所示:
注意:
(1)类的对象模型中仅有一张虚表,因此,若该类实例化了多个对象,则该多个对象的虚表指针相同;
(2)其次,
void FunTest() { Base* pb = new Base; pb->FunTest1(); pb = (Base*)new Deriverd; pb->FunTest1(); }
在此段代码中,Base*强制类型转换没有任何意义,因为基类可直接指向派生类的对象;
(3)虚函数的调用过程:【1】先判断是否为虚函数,若为虚函数,则【2】取虚表指针,进而【3】取该虚函数在虚函数表中的偏移量,最后【4】定位虚函数,调用虚函数;
(4)动态多态的实现原理(以下面为例):
class Base { public: virtual void FunTest1() {} virtual void FunTest2() {} }; class Deriverd:public Base { public: virtual void FunTest4() {} virtual void FunTest2() {} virtual void FunTest3() {} };
因此,在派生类的对象模型中,先按照基类的虚函数表保存一份,若派生类中有被重写的虚函数,则该虚函数将基类的相应虚函数覆盖掉,接着按照派生类中自己定义虚函数的次序存放剩下的虚函数,如上图所示;(5)派生类中定义的虚函数不受访问限定符的约束,编译时,将其按基类的对象处理,但基类中定义的虚函数受访问限定符的约束;