C++中多态
一、多态有狭义和广义之说
狭义上:为对象的类型变化,行为随之变化
广义上:程序中的标识符(对象,宏 函数 模板)的不同。
有静态多态和动态多态。这里主要讨论的主要是动态多态。
静态多态:预处理时期和编译时期的多态,主要通过函数重载、模板、宏定义来表现。
动态多态:运行时时期的多态。随着程序的运行,不同的对象,行为也会改变。
因为C++是一种强类型语言,变量一旦声明,其类型是不会改变的,
所以在C++中实现多态不能直接用对象,而是用指向对象的指针或引用
,并且对象的类型在定义时也有一定要求。具有动态特征的实体是对象。
二、动态多态
实现方式:
1、将与多态行为相关的函数,在基类中声明为虚函数
2、派生类改写基类的虚函数
3、通过基类型的指针(或者引用),调用虚函数
注:实现动态多态依靠的主要是虚函数。
虚函数的声明:(只能是成员函数)
比普通函数前面多一个virtual
virtual 返回类型 函数名(形参表)
{
函数体
}
三、虚函数在派生类中的处理
1、不做任何修改,像普通函数一样直接继承,接受基类中此函数的默认实现。
2、改写此虚函数。
改写规则:如果要在派生中类重新改写此函数,就必须重新声明
1、函数名 参数列表 完全相同
2、与基类的虚函数同为const或者非const成员函数
3、返回值类型相同,或者是存在继承关系类型的指针 或 引用
如:基类中返回 A * 派生类中返回 B *,B派生于A类。
4、派生类重新声明改写虚函数时,virtual可加可不加,一个成员函数
只要在基类中声明为虚函数,则在其后所有的派生类中都是虚函数。
四、纯虚函数与抽象类
1、纯虚函数
纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,只有声明。
但要求在它的派生类中根据需要对它进行定义,或仍然说明为纯虚函数。
作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进
行重新定义。
纯虚函数声明语法:
virtual 返回类型 函数名(参数表)=0 ;
2、抽象类
如果一个类至少拥有一个纯虚函数,那么就是抽象类。
同时也可以有其它成员,如数据成员,普通成员函数。
性质:
a、抽象类只能用作其他类的基类,不能建立抽象类对象实例。
b、抽象类不能用作参数类型、函数返回类型、或显示转换的类型,
但可以声明指向抽象类的指针变量、此指针可以指 向它的派生类
,进而实现多态性
c、如果在抽象类的派生类中没有重新说明纯虚函数,则该函数在派生类中
仍然为纯虚函数,而这个派生类仍然还是一个抽象类。
注:在实现动态多态时,一般将基类的析构函数声明为虚函数,不然如:当声明一个
指向基类的引用,然后用这个引用指向派生类,析构时,只调用基类的析构函数
,而不会调用派生类的。如果将基类的析构函数声明为虚函数,那么就会先调用派
生类的析构函数,再调用基类的构造函数。而且其所有层次的派生类的析构函数
都自动成为了虚析构函数。
五、接口
:仅有纯虚函数,且没有数据成员的类
如果一个类继承某个接口类,则说明该类支持这个接口。当然该类也应该实现这个接口
的全部成纯虚函数。
注:在声明一个接口类时,经常用struct,而不是class,因为sturct的默认访问机制
是pulbic,而class的是private
六、虚函数与动态绑定
动态绑定:虚函数的调用机制
C++编译器通过虚函数表(v-table)和虚函数表指针(vptr)来实现。
如果一个类定义了虚函数,则编译器就会为该类创建一个虚函数表。虚函数表
是一个数组,其中的每一个元素都是函数指针,指向该类的虚函数。
编译器会为该类的对象嵌入一个虚函数表指针,从而建立对象和虚函数表的索引
关系,编译器也会派生类创建虚函数表,如果派生类没有改写基类中的虚函数,则
其虚函数表的内容只是基类虚函数表的复制。如果派生类改写了基类中的虚函数,
则其虚函数表中的元素就是改写后虚函数的地址。
编译时的动态绑定过程:
1、编译器通过对象地址计算得到虚函数表指针(vptr)
2、通过虚函数表指针得到虚函数表(v-table)的地址
3、根据被调虚函数在类中的次序,得到该虚函数的地址在虚函数表中的索引
4、根据这个索引获取虚函数的入口地址,也就是实际所调虚函数的地址。
七、虚函数的默认实参
1、如果是一个基类类型的指针或引用,则使用基类中的默认实参
2、如果是派生类类型的指针或引用,则使用派生类中的默认实参。
八、虚函数的静态调用
调用基类中的虚函数,而不是派生类中重写的虚函数
语法:
派生类对象指针->基类名::虚函数名();