c++多态特性总结

 

 

将父类比喻为电脑的外设接口,子类比喻为外设,如今我有移动硬盘、U盘以及MP3,它们3个都是能够做为存储可是也各不相同。若是我在写驱动的时候,我用个父类表示外设接口,而后在子类中重写父类那个读取设备的虚函数,那这样电脑的外设接口只须要一个。但若是我不是这样作,而是用每一个子类表示一个外设接口,那么个人电脑就必须有3个接口分别来读取移动硬盘、U盘以及MP3。若之后我还有SD卡读卡器,那我岂不是要将电脑拆了,焊个SD卡读卡器的接口上去?html

因此,用父类的指针指向子类,是为了面向接口编程。你们都遵循这个接口,弄成同样的,到哪里均可以用,准确说就是“一个接口,多种实现“。ios

对比于重载,派生类中的重载方法,在基类是不能调用的,编译的时候就会出错。编程

#include <iostream.h>  
class animal  
{  
public:  
    void sleep()  
    {  
        cout<<"animal sleep"<<endl;  
    }  
    virtual void breathe()  
    {  
        cout<<"animal breathe"<<endl;  
    }  
};  
  
class fish:public animal  
{  
public:  
    void breathe()  
    {  
        cout<<"fish bubble"<<endl;  
    }  
};  
void main()  
{  
    fish fh;  
    animal *pAn=&fh; // 隐式类型转换  
    pAn->breathe();  
}  

 

结果是“fish bubble”数组

 编译器在编译的时候,发现animal类中有虚函数,此时编译器会为每一个包含虚函数的类建立一个虚表(即vtable),该表是一个一维数组,在这个数组中存放每一个虚函数的地址。函数

对于例1-2的程序,animal和fish类都包含了一个虚函数breathe(),所以编译器会为这两个类都创建一个虚表,(即便子类里面没有virtual函数,可是其父类里面有,因此子类中也有了)以下图所示:ui

 

        那么如何定位虚表呢?编译器另外还为每一个类的对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就可以找到正确的函数。对于例1-2的程序,因为pAn实际指向的对象类型是fish,所以vptr指向的fish类的vtable,当调用pAn->breathe()时,根据虚表中的函数地址找到的就是fish类的breathe()函数。
       正是因为每一个对象调用的虚函数都是经过虚表指针来索引的,也就决定了虚表指针的正确初始化是很是重要的。换句话说,在虚表指针没有正确初始化以前,咱们不可以去调用虚函数。那么虚表指针在何时,或者说在什么地方初始化呢?
        答案是在构造函数中进行虚表的建立和虚表指针的初始化。还记得构造函数的调用顺序吗,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。对于例2-2的程序来讲,当fish类的fh对象构造完毕后,其内部的虚表指针也就被初始化为指向fish类的虚表。在类型转换后,调用pAn->breathe(),因为pAn实际指向的是fish类的对象,该对象内部的虚表指针指向的是fish类的虚表,所以最终调用的是fish类的breathe()函数。
要注意:对于虚函数调用来讲,每个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。因此在程序中,无论你的对象类型如何转换,但该对象内部的虚表指针是固定的,因此呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。指针

总结(基类有虚函数):
1. 每个类都有虚表。
2. 虚表能够继承,若是子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。若是基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,若是重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。若是派生类有本身的虚函数,那么虚表中就会添加该项。
3. 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。htm

        这就是C++中的多态性。当C++编译器在编译的时候,发现animal类的breathe()函数是虚函数,这个时候C++就会采用迟绑定(late binding)技术。也就是编译时并不肯定具体调用的函数,而是在运行时,依据对象的类型(在程序中,咱们传递的fish类对象的地址)来确认调用的是哪个函数,这种能力就叫作C++的多态性。咱们没有在breathe()函数前加virtual关键字时,C++编译器在编译时就肯定了哪一个函数被调用,这叫作早期绑定(early binding)。

C++的多态性是经过迟绑定技术来实现的。

C++的多态性用一句话归纳就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。若是对象类型是派生类,就调用派生类的函数;若是对象类型是基类,就调用基类的函数。对象

虚函数的定义要遵循如下重要规则: 

1.若是虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不一样,或者是返回类型不一样,那么即便加上了virtual关键字,也是不会进行滞后联编的。 

2.只有类的成员函数才能说明为虚函数,由于虚函数仅适合用与有继承关系的类对象,因此普通函数不能说明为虚函数。 

3.静态成员函数不能是虚函数,由于静态成员函数的特色是不受限制于某个对象。 

4.内联(inline)函数不能是虚函数,由于内联函数不能在运行中动态肯定位置。即便虚函数在类的内部定义定义,可是在编译的时候系统仍然将它看作是非内联的。 

5.构造函数不能是虚函数,由于构造的时候,对象仍是一片未定型的空间,只有构造完成后,对象才是具体类的实例。 

6.析构函数能够是虚函数,并且一般声名为虚函数。析构函数为何一般定义为虚函数,是为了在多态时,当基类指针操做派生类对象时,防止在析构时只析构基类而不析构派生类的状况发生。
blog

相关文章
相关标签/搜索