二十.多态与虚函数
多态:多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态,简单地说是”一个接口多种实现“
C++提供多态的目的:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。
多态的分类:
1)编译时的多态性。编译时的多态性是通过重载来实现的。
2)运行时的多态性。运行时的多态性是通过虚成员实现的。
下面是构成多态的条件:
1)必须存在继承关系;
2)继承关系中必须有同名的虚函数,并且它们是遮蔽(覆盖)关系。
3)存在基类的指针,通过该指针调用虚函数。
在C++中如何实现多态
答:多态有动态多态,静态多态,函数多态和宏多态等。
动态多态基于继承机制和虚函数来实现的。
静态多态引入了泛型的概念。
函数多态基于函数重载。
宏多态基于宏替换。
虚函数:
作用:实现多态,基类定义虚函数,子类可以重写该函数,基类指针根据赋给它的不同的子类指针,动态调用属于子类的函数。
底层实现原理:C++中虚函数使用虚函数表和 虚函数表指针实现。通过对象内存中的vptr找到虚函数表vtbl,接着通过vtbl找到对应虚函数的实现区域并进行调用。
什么时候声明虚函数:
首先看成员函数所在的类是否会作为基类,然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。
虚函数对于多态具有决定性的作用,有虚函数才能构成多态,这节我们来重点说一下虚函数的注意事项。
1) 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。
2) 为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽(覆盖)关系的同名函数都将自动成为虚函数。
3) 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。
4) 只有派生类的虚函数遮蔽基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。
5) 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。
6) 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。
构造函数和析构函数能否声明为虚函数?
答:构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。
构造函数不能声明为虚函数的原因是:
解释一:所谓虚函数就是多态情况下只执行一个。而从继承的概念来讲,总是要先构造父类对象,然后才能是子类对象。如果构造函数设为虚函数,那么当你在构造父类的构造函数时就不得不显示的调用构造。还有一个原因就是为了防错,试想如果你在子类中一不小心重写了个跟父类构造函数一样的函数,那么你的父类的构造函数将被覆盖,也即不能完成父类的构造.就会出错。
解释二:虚函数的主要意义在于被派生类继承从而产生多态。派生类的构造函数中,编译器会加入构造基类的代码,如果基类的构造函数用到参数,则派生类在其构造函数的初始化列表中必须为基类给出参数,就是这个原因。
析构函数设为虚函数的作用:
在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。
纯虚函数:
纯虚函数在基类中是没有定义的,必须在子类中加以实现。
如果基类含有一个或多个纯虚函数,那么它就属于抽象基类,不能被实例化
虚函数与纯虚函数有什么区别
答:
- 虚函数:如果一个类中声明了虚函数,这个函数是实现的,他的作用是为了能让这个函数在他的子类里面能被覆盖,这样就可以实现动态多态。
- 纯虚函数只是一个接口,是个函数的声明而已,他留在子类中实现。
- 虚函数在子类中可以不重载
- 纯虚函数必须在子类中实现
- 虚函数的类用作”实现继承“,即继承接口的同时也继承了父类的实现。
- 纯虚函数用于”介面继承“,即纯虚函数关注的是接口的统一性,实现由子类完成
- 带纯虚函数的类叫做虚基类,这种类不能直接生成对象。
分别简单讲述一下函数重载,函数覆盖,函数隐藏的概念与特征:
函数重载:重载函数通常用来命名一组功能相似的函数
1.函数要在相同的类域
2.函数的名字要相同
3.函数的参数列表或返回值不同
函数覆盖(重写):覆盖是指派生类函数覆盖基类函数
1.函数是要在不同的类域
2.两个函数的名称相同
3.两个函数的参数相同
4.基类函数必须是虚函数
函数隐藏:指派生类的函数屏蔽了与其同名的基类函数
1.两个函数在不同的类域
2.函数名称相同
3.函数参数不同
4.(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏。