FAQ 2.24
继承是一个很强大的手段,它具有很强的扩张能力,它使软件可以有is-a和kind-of的关系。
在下面的例子里,类Vehicle定义了一个=0的成员函数; 也就是在startEngine()成员函数的声明后面写上"=0";这种语法的意思是startEngine()成员函数是纯虚函数并且Vehicle类是一个抽象的基类。实际上,对于其他的类来说,Vehicle是一个重要的用来继承的类。那些继承的类,通常需要提供一个startEngine()成员函数。
class Vehicle { public: virtual void startEngine() = 0; virtual ~Vehicle(); <-- 1 }; Vehicle::~Vehicle() { // Intentionally left blank }
(1) 基类的析构函数通常是虚函数
应用程序多半在编译时不需要知道继承类,而只知道基类就可以了。例如,下面的函数知道基类,而不知道继承类。
void f(Vehicle& v)
{
// ...
v.startEngine();
// ...
}
如果基类被合理的设计,应用程序的一大部分将被写在该层次上,那么增加新的继承类不会对应用程序产生冲突,另外,最小的连锁反应是增加新类最希望达到的目标,例如,下面的继承类的增加不会影响到函数f().
#include <iostream> using namespace std; class Car : public Vehicle { public: virtual void startEngine(); }; void Car::startEngine() { cout << "Starting a Car's engine/n"; } class NuclearSubmarine: public Vehicle { public: virtual void startEngine(); }; void NuclearSubmarine::startEngine() { cout << "Starting a NuclearSubmarine's engine/n"; }
不影响函数f()的原因在于C++的两个特征:is-a转换是指一个继承类的对象,例如一个Car类对象,能作为一个基类引用传递。这样编译器就允许一个从继承类(例如,Car对象)到继承类(例如,一个Vehicle引用)的转化。
int main()
{
Car c;
NuclearSubmarine s;
f(c);
f(s);
}
is-a转化总是安全的,因为继承的意思是"是可代替的",也就是,Car是可以替代Vehicle的,所示f()函数中如果v是Car引用的化并没有什么可奇怪的。
动态绑定就相当于一枚同样的硬币的另一面,鉴于is-a转化是从继承类到基类的安全转化,动态绑定就是从基类到继承类的安全转化,例如。函数f()内的v.startEngine()实际上调用的是与对象相关的合适的startengine()函数。也就是说,当main()传递一个NuclearSubmarine到fn()时,v.startEngine()调用的startEngine()成员函数与类NuclearSubmarine相关。这是很强大的,因为类
NuclearSubmarine可能在f()函数之后很久才创建和编译并且可能放在了一个library内.也就是说,动态绑定允许旧代码调用新代码,(NuclearSubmarine::startEngine())而不需要修改旧代码甚至重新编译.这是扩展性的本质:在一个应用程序中增加新的特征而不会对已存在的代码造成重大的冲突的能力。它对C++来说是可行的,但是这需要很好的设计,它不会白白的产生。
UML用下面的符号显示继承