为啥要用虚函数呢,那先分析下,如果不用虚函数会怎样:
class cBase{
public:
cBase();
~cBase();
void fn1();
int fn2();
void fn3();};
class cDeriveA: public cBase{
public:
cDeriveA();
~cDeriveA();
void fn1();
voif derAfn1();}
class cDeriveB: public cBase{
public:
cDeriveB();
~cDeriveB();
voif derBfn1();}
如此定义后,在定义cDeriveA或cDeriveB的实例时,实例中,CBase的所有函数都会有。
弊端是:
1:空间较大
2:不利于某些情况下多态实现,如定义:cBase *ptr =(cBase) new cDeriveA;
后续ptr->fn1(),实际调用的是cBase中的fn1(),而不是cDeriveA中的(做个函数验证下)。
优点也有啊:
1:实现简单
2:如果cBase类比较小,存储空间和实现效率有一定优势。
综上的话,我们看如果用虚函数会怎样呢
class cBase{
public:
cBase();
virtual ~cBase();
virtual void fn1();
virtual int fn2();
void fn3();};
class cDeriveA: public cBase{
public:
cDeriveA();
~cDeriveA();
virtual void fn1();
virtual voif derAfn1();}
如上,如果CBase中定义了虚拟函数,那么cDeriveA只是继承了cBase的实函数,那虚函数呢?
一般的编译器操作是:基类有个虚函数表(virtual tables),将相关虚函数放置到表中,这样cDeriveA和cDeriveB都继承了cBase,但cBase里面的虚函数只有一个vtbl。这样的优点:
1:节省了内存空间
2:效率和重载实函数基本一样(主要是一个指针调用)
限制:
1:虚函数不要做成inline,因为inline本质是编译的时候将函数代码实际填充到函数调用处,而多态实现的时候,new 子类的时候还不知道具体是cDeriveA还是cDeriveB,所以无法使用。
虚拟类
既然有虚拟函数,那也就有虚拟类,类似如下行为:
class A {...};
class B: virtual class A {...};
class C: virtual class A {...};
class D: public B, public C {...};
继承虚拟类的B和C实际上都有一个指向基类的指针(pointer to virtual base class)。同时,结合上述,还有一个vptr(实例的指针,指向基类的虚函数table)。
这样的好处和类的虚函数基本一样,就是基类A只有一个实例,B和C都有个指针指向它,如此省空间。
多态的实现方式
多态的两种实现方式,
静态联编:编译期确定,主要是运算符重载和函数重载
动态联编:当子类重新定义了父类的虚函数后,父类指针根据赋值给它的不同的子类指针,动态的调用属于子类的该函数,这样函数调用在编译期间是无法确定的,这样的函数地址在运行期间绑定称为动态联编.
本质上就是编译阶段,编译器是否能确定类函数位置,可以就是静态,不能就是动态。
class cBase{
public:
cBase();
~cBase();
void fn1();
vitual int fn2();
void fn3();};
class cDeriveA: public cBase{
public:
cDeriveA();
~cDeriveA();
void fn1();
vitual int fn2();
voif derAfn1();};
class cDeriveB: public cBase{
public:
cDeriveA();
~cDeriveA();
vitual int fn2();
voif derAfn1();};
如上,如果是静态的编译,如直接cDeriveA *ptr = new cDeriveA.
那么就是确定的,ptr->fn1()调用的就是cDeriveA中的fn1,
如果动态编译,cBase *ptr =(cBase *)new cDeriveA.
此时调用ptr->fn1(),实际就是调用cBase中的fn1,而ptr->fn2()调用的是cDeriveA中的fn1().