一、多态性
在C++中多态性的表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。从系统实现的角度来看,多态性分为两类:静态多态性和动态多态性。
1.静态多态性是通过函数重载(参数个数、参数类型或参数顺序三者中必须至少有一种不同)实现,在程序编译时系统就能决定要调用的是哪个函数,又称编译时多态性。
2.动态多态性是通过虚函数实现,在程序运行过程中才动态地确定操作所针对的对象,又称运行时多态。
二、虚函数
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
我们通过定义一个基类与该基类的派生类来验证虚函数的特性:
#include <iostream>
#include <string>
using namespace std;
class Student//基类
{
public:
Student(int n, string name, float s);
void display();
protected:
int m_num;
string m_name;
float m_score;
};
Student::Student(int n, string name, float s)
{
m_num = n;
m_name = name;
m_score = s;
}
void Student::display()
{
cout << "基类:\nnum:" << m_num << "\nname:" << m_name << "\nscore:" << m_score << endl;
}
class Graduate:public Student//派生类
{
public:
Graduate(int n, string name, float s, float wage);
void display();
private:
float m_wage;
};
Graduate::Graduate(int n, string name, float s, float wage):Student(n, name, s),m_wage(wage)
{
}
void Graduate::display()
{
cout << "派生类:\nnum:" << m_num << "\nname:" << m_name << "\nscore:" << m_score << "\nwage:" << m_wage << endl;
}
int main()
{
Student stud1(100, "Li", 87.6);
Graduate grad1(200, "Ding", 98.5, 150000);
Student* pt = &stud1;
pt->display();
pt = &grad1;
pt->display();
return 0;
}
在以上的代码中,我们并没有使用虚函数。在主函数中定义了指向基类对象的指针变量pt,并先使pt指向基类对象stud1,通过基类指针pt调用display函数输出基类对象stud1的全部数据成员,然后使pt指向派生类对象grad1,再通过基类指针pt调用display函数输出基类对象grad1的全部数据成员。
运行结果如下:
基类:
num:100
name:Li
score:87.6
基类:
num:200
name:Ding
score:98.5
由运行结果可知基类指针pt并没有调用派生类对象grad1的display函数,而是调用了基类的display函数。如果能够用同一种方式去调用同一类族中不同类的所有同名函数,那就好了,即根据基类指向的对象调用该对象所属类的函数。
用虚函数就能实现这个想法。对程序作一点修改,在Student类中声明display函数时,在最左面加上一个关键字virtual,即
virtual void display();
这样就把Student类中的display函数声明为虚函数。程序其它部分都不改动。再编译和运行程序:
基类:
num:100
name:Li
score:87.6
派生类:
num:200
name:Ding
score:98.5
wage:150000
由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。
当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。
虚函数的实现属于函数重写,重写是指派生类函数覆盖基类函数,而与函数重写容易混淆的是函数隐藏
函数隐藏
隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
三、虚析构函数
当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用派生类的构造函数。但是当我们用一个基类的指针new一个派生类的对象后,再用delete运算符撤销对象时,会发生一个情况:系统只会执行基类的构造函数,而不执行派生类的析构函数。而根据我们前面对虚函数的介绍,我们立马就会想到把基类的析构函数声明为虚函数不就行了。这样基类的指针如果指向派生类的对象,用delete销毁时便会执行派生类的析构函数。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。
四、纯虚函数与抽象类
纯虚函数
是在声明虚函数时被初始化为0的函数。声明纯虚函数的一般形式为:virtual 函数类型 函数名(参数列表) = 0;
注:纯虚函数没有函数体。
抽象类
如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来生成对象。定义这些类的唯一目的是用它作为基类去建立派生类。它们作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类。用这些派生类去建立对象。
这种不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类,通常称为抽象基类(abstract base class)。凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。
五、结语
打个比方,汽车制造厂往往向汽车装配厂提供卡车的底盘(包括发动机、传动部分、车轮等),组装厂可以把它组装成货车、公共汽车、工程车或客车等不同功能的车辆。底本身不是车辆,要经过加工才能成为车辆,但它是车辆的基本组成部分。它相当于基类。在现代化的生产中,大多采用专业化的生产方式,充分利用专业化工厂生产的部件,加工集成为新品种的产品。生产公共汽车的厂家决不会从制造发动机到生产轮胎、制造车厢都由本厂完成。不同品牌计算机的基本部件是一样的或相似的。这种观念对软件开发是十分重要的。一个优秀的软件工作者在开发一个大的软件时,决不会从头到尾都由自己编写程序代码,他会充分利用已有资源(例如类库)作为自己工作的基础。