四、多态与虚函数
一.构成多态的先决条件
1.必须存在继承关系;(派生类:基类)
2.继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)。(基类:virtual void fun() 派生类:void fun()或者基类:virtual void fun(int a) 派生类:void fun(int b))
为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数
3.存在基类的指针,通过该指针调用虚函数。(基类指针访问派生类的虚函数)
二.了解多态(多态的本质,核心)
C++多态的本质(核心):可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。虽然基类指针指能够访问派生类的成员,但是只能访问继承下来的成员,不可访问派生类新增成员。
三.基类指针访问派生类成员
1.无Virtual关键字

上述例子表明:当基类指针指向派生类时,调用了Student的成员变量,但是没有调用Student的成员函数,导致了结果不伦不类("hi,大家好!我叫李四 ,今年18岁了。但是考了100分。");换句话说,通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。
2.有Virtual关键字
为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。
四.借助引用实现多态

五.多态的一个典型混合案例

在基类 Base 中我们将void func()
声明为虚函数,这样派生类 Derived 中的void func()
就会自动成为虚函数。p 是基类 Base 的指针,但是指向了派生类 Derived 的对象。
语句p -> func()/fun(char* name1, int a1);
调用的是派生类的虚函数,构成了多态。
语句p -> func(10);
调用的是基类的虚函数,因为派生类中没有函数覆盖它。
语句p -> func("http://c.biancheng.net");
出现编译错误,因为通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。
六.纯虚函数与抽象类
1.什么是纯虚函数?
virtual 返回值类型 函数名 (函数参数) = 0;纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0
,表明此函数为纯虚函数;最后的=0
并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。
2.什么是抽象类?
包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。
抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。
3.派生类必须重写基类中全部的虚函数后,才能够实例化

本例中定义了四个类,它们的继承关系为:Line --> Rec --> Cuboid --> Cube。
Line 是一个抽象类,也是最顶层的基类,在 Line 类中定义了两个纯虚函数 area() 和 volume()。
在 Rec 类中,实现了 area() 函数;所谓实现,就是定义了纯虚函数的函数体。但这时 Rec 仍不能被实例化,因为它没有实现继承来的 volume() 函数,volume() 仍然是纯虚函数,所以 Rec 也仍然是抽象类。
直到 Cuboid 类,才实现了 volume() 函数,才是一个完整的类,才可以被实例化。
可以发现,Line 类表示“线”,没有面积和体积,但它仍然定义了 area() 和 volume() 两个纯虚函数。这样的用意很明显:Line 类不需要被实例化,但是它为派生类提供了“约束条件”,派生类必须要实现这两个函数,完成计算面积和体积的功能,否则就不能实例化。