作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何东西。
class ASCEShape
{
public:
virtual void draw() const = 0; //纯虚函数,因此ASCEShape成为一个抽象类
virtual void error(const string& msg); //一般虚函数
int objectID() const; //非虚函数
...
};
class ASCERectangle : public ASCEShape {...};
class ASCEEllipse : public ASCEShape {...};
纯虚函数最显著的特征是:它们必须在继承了它们的任何具体类中重新声明,而且它们在抽象类中往往没有定义。因此:定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
为一个纯虚函数提供定义也是可能的,我们可以为ASCEShape::draw提供实现,C++编译器不会报错,但调用它的唯一方式是通过类名完整的指明是哪个调用:
//假设在ASCEShape中定义了纯虚函数draw
ASCEShape *ps = new ASCEShape; //错误,ASCEShape是抽象类
ASCEShape *ps1 = new ASCERectangle; //正确
ps1->draw(); //调用ASCERectangle::draw
ASCEShape *ps2 = new ASCEEllipse; //正确
ps2->draw(); //调用ASCEEllipse::draw
ps1->ASCEShape::draw(); //调用ASCEShape::draw
ps2->ASCEShape::draw(); //调用ASCEShape::draw
(纯虚函数必须在子类中重新声明,但它还是可以在基类中有自己的实现)
有时,声明一个除了纯虚函数之外什么也不包含的类是很有用的,这样的类叫做协议类。它为派生类仅提供函数接口,完全没有实现。
声明一般虚函数的目的在于:使派生类继承函数的接口和缺省实现。
声明非虚函数的目的在于:使派生类继承函数的接口和强制性实现。注意,非虚函数表示一种特殊性上的不变性,所以决不能在子类中重新定义。
理解了纯虚函数。简单虚函数和非虚函数在声明上的区别,我们就可以精确地指定想让派生类继承什么:仅仅是接口,还是接口和一个缺省实现,或者是接口和一个强制实现。
常见的错误:
1)把所有的函数都声明为非虚函数。这就使得派生类没有特殊化的余地。事实上是:几乎任何一个作为基类使用的类都有虚函数。
2)将所有的函数都声明为虚函数。有时这没错,例如协议类。但往往一些函数不能在派生类中重定义,只要是这种情况,我们就应该将它声明为非虚函数。