公有继承的含义
时间:2010-09-21 来源:zyd_cu
公有继承体现“是一个”的含义,类D(Derived)从类B(Base)公有继承时,实际上是告诉编译器:类型D的每一个对象也是类型B的一个对象,但反之不成立。如Student类公有继承自Person类,说明每一个Student都是一个Person,但反之则不然,并不是每一个Person都是Student。
class Person { ... }; |
另外公有继承声明,对基类使用的任何东西也适应于派生类,如下例:正方形公有继承自矩形则不合适,对于矩形,长宽可以不等,但正方形长宽一定相等,makebigger对矩形适用,但对正方形不适用(长变了宽一定跟着变),故并不是对矩形适用的一切对正方形都使用,故这样的公有继承时不合理的。
class Rectangle { |
对于公有继承,接口总是被继承下来的,作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何东西。如下例:
class Shape { |
Shape类中声明了三个函数。第一个函数,draw,在某一画面上绘制当前对象。第二个函数,error,被其它成员函数调用,用于报告出错信息。第三个函数,objectID,返回当前对象的一个唯一整数标识符。每个函数以不同的方式声明:draw是一个纯虚函数;error是一个简单的虚函数;objectID是一个非虚函数。这些不同的声明各有什么含义呢?
对于纯虚函数draw,纯虚函数最显著的特征是:它们必须在继承了它们的任何具体类中重新声明,而且它们在抽象类中往往没有定义(但也可有定义),定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
这对Shape::draw函数来说非常有意义,因为,让所有Shape对象都可以被绘制是很合理,但Shape类无法为Shape::draw提供一个合理的缺省实现。例如,绘制椭园的算法就和绘制矩形的算法大不一样。打个比方来说,上面Shape::draw的声明就象是在告诉子类的设计者,你必须提供一个draw函数,但我不知道你会怎样实现它。
简单虚函数的情况和纯虚函数有点不一样。照例,派生类继承了函数的口,但简单虚函数一般还提供了实现,派生类可以选择改写它们或不改写它们思考片刻就可以认识到:声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。
具体到Shape::error,这个接口是在说,每个类必须提供一个出错时可以被调用的函数,但每个类可以按它们认为合适的任何方式处理错误。如果某个类不想做什么特别的事,可以借助于Shape 类中提供的缺省出错处理函数。也就是说,Shape::error的声明是在告诉子类的设计者,你必须支持error 函数,但如果你不想写自己的版本,可以借助Shape类中的缺省版本。
对于非虚函数objectID。当一个成员函数为非虚函数时,它在派生类中的行为就不应该不同。实际上,非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为,不管一个派生类有多特殊。所以,声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现,并且决不要重新定义继承而来的非虚函数。如下例:
class B { |
行为的两面性产生的原因在于,象B::mf 和D::mf这样的非虚函数是静态绑定的。这意味着,因为pB 被声明为指向B的指针类型,通过pB调用非虚函数时将总是调用那些定义在类B中的函数 ---- 即使pB指向的是从B派生的类的对象。
相反,虚函数是动态绑定的,因而不会产生这类问题。如果mf 是虚函数,通过pB或pD调用mf 时都将导致调用D::mf,因为pB和pD实际上指向的都是类型D的对象。
结论是,如果写类D时重新定义了从类B 继承而来的非虚函数mf,D的对象就可能表现出精神分裂症般的异常行为。即D的对象在mf被调用时,行为有可能象B,也有可能象D,决定因素和对象本身没有一点关系,而是取决于指向它的指针所声明的类型。引用也会和指针一样表现出这样的异常行为。故决不要重新定义继承而来的非虚函数