先给出一个类的定义:
class Shape
{
public:
virtual void draw() const = 0;//纯虚(pure virtual)函数
virtual void error(const std::string& msg);//虚(impure virtual)函数
int objectID() const;//普通函数
......
};
Shape是个抽象类,它的纯虚函数draw使它成为一个抽象class。所以客户不能够创建Shape类的实体,只能创建其派生类的实体。但是Shape会影响以public形式继承它的派生类。Shape类定义了三种类型的函数:纯虚函数、虚函数和普通函数。这些不同的声明带来了什么样的暗示呢?
纯虚函数:只继承函数接口
class Shape
{
public:
virtual void draw() const = 0;
//......
};
纯虚函数有两个突出的特性:1、必须在派生类中重新声明;2、它们在抽象类中通常没有定义。通过这两个性质大致明白,声明一个纯虚函数的目的是为了让派生类只继承函数的接口。例如,每种shape类型都是可绘制的,这是合理的要求。不过Shape不能为draw提供合理的缺省,毕竟椭圆的画法跟长方形的画法不一样。
纯虚函数在抽象类中通常不进行定义,但并不代表它不可以定义。在Shape类中可以微Shape::draw供应一份实现代码,只不过得使用类名进行调用。
Shape* ps = new Shape; //错误!Shape是抽象类
Shape* ps1 = new Rectangle;
ps1->draw(); //调用Rectangle::draw
Shape* ps2 = new Ellipse;
ps2->draw(); //调用Ellipse::draw
ps1->Shape::draw();//调用Shape::draw
ps2->Shape::draw();//调用Shape::draw
虚函数:继承函数接口和缺省(default)实现
虚函数在基类中有实现代码,派生类可能覆写它。声明非纯虚函数的目的是让派生类继承该函数的接口和缺省实现。
class Shape
{
public:
virtual void error(const std::string& msg);//虚(impure virtual)函数
......
};
这个接口表示,每个类都必须支持一个error的函数,但是每个类可以自由处理错误。如果派生类不想针对错误做出任何特殊行为,则可以退回到Shape class提供缺省错误处理行为。也就是说Shape::error的声明式告诉派生类的设计者“你必须支持一个error函数,但如果你不想自己写一个可以使用Shape class提供缺省版本”。但是,允许非纯虚函数同时制定函数声明和函数缺省行为,有可能造成危险。
class Airplane
{
public:
virtual Airplane::fly(const Airport& destination);
//......
};
void Airplane::fly(const Airport& destination)
{
//缺省代码,将飞机飞至指定的目的地
}
class ModelA : public Airplane {...};
class ModelB : public Airplane { ... };
为了表示所有飞机都一定能飞,并阐明“不同型飞机原则上需要不同的fly实现”,Airplane::fly被声明为virtual。为了避免在ModelA和ModelB中撰写相同代码,缺省飞行行为由Airplane::fly提供,它同时被ModelA和ModelB继承。
现在假设某公司决定购买一种新式C型,它的飞行方式不同。这个公司的程序员针对C型飞机添加了一个类,但是由于他们着急让新飞机上线服务,竟然忘记重新定义其fly函数:
Airpalne* pa = new ModelC;
pa->fly(AAA); //调用Airplane::fly
这就可能酿成大灾难,试图以ModelA或者ModelB的飞行方式飞ModelC。如何解决这样的问题?
可以利用“纯虚函数必须在derived classes中重新声明,但它们也可以拥有自己的实现”。
class Airplane
{
public:
virtual Airplane::fly(const Airport& destination) = 0;
//......
};
void Airplane::fly(const Airport& destination)
{
//缺省行为,将飞机飞至指定的目的地
}
class ModelC : public Airplane {
public:
virtual Airplane::fly(const Airport& destination);
...};
void ModelC::fly(const Airport& destination)
{
//将C型飞机飞至指定的目的地
}
将fly声明为纯虚函数使得ModelC类必须继承fly函数并改写。
普通函数(non-virtual):派生类继承接口以及强制实现
class Shape
{
public:
int objectID() const;//普通函数
......
};
声明为non-virtual的函数只可以继承不能被改写,是一份强制性的实现。每个继承于Shape类的对象都有一个用来产生对象ID的函数,此ID是采用相同计算方法,任何派生类都不应该尝试改变其行为。