程序的动态特性

来源:互联网 发布:虚拟化软件 价格 编辑:程序博客网 时间:2024/05/06 08:35

程序的动态特性:大多数情况下,程序的功能是在编译的时候确定下来的,称之为静态特性;而如果程序的功能是在运行时才确定的称为动态特性。

动态特性是面向对象语言最强大的功能之一,它在语言层面上支持程序的可扩展性。

动态特性:由C++虚函数、抽象基类、动态绑定和多态构成。

(1)虚函数:可用于使用统一的方式来调用相同功能的函数实现做类似但不同的事情。例如,假定几何形状Shape,其派生类为Circle、Rectangle、Ellicps等,每个派生类都能绘制自己所代表的形状。不管派生类如何,我们希望用统一的方式来调用绘制函数,最好使用Shape定义的接口函数Draw(),并让程序在运行时动态去顶使用哪一个派生类。可以把类Shape中的函数Draw()声明为虚函数,然后派生类中重新定义Draw()使之绘制正确的形状,这种方法叫覆盖。

(2)抽象基类:抽象类的唯一目的就是让派生类继承并实现它的接口方法(Method),因此它通常被称为抽象基类(Abstract Base Class)。如果将基类的虚函数声明为纯虚函数,那么该类就被定义为了抽象基类。纯虚函数是在声明时将其“初始化”为0的函数。将一个函数初始化为0意味着函数的地址将为0,即是告诉编译器:不要为该函数编址,从而阻止该类的实例化行为。

很多良好的面向对象系统中,基类层次结构的顶部通常都是抽象基类,甚至可以有好几层的抽象类。例如集合形状的类结构可分为三层,顶层是抽象基类Shape,第二层也是抽象基类Shape2D和Shape3D,在第三层才是可以被实例化为对象的具体类,如二维形状类Circle、Rectangel和Ellipse,三维形状类Cube、Cylinder和Sphere.

抽象基类的主要用途是“接口与实现分离”:不仅要把数据成员(信息)隐藏起来,而且还要把实现完全隐藏起来,只留一些接口给外部调用。

一般的信息隐藏就是把类的所有数据成员声明为private或者protected的,并提供相应的get和set函数来访问对象的数据。抽象基类则更进一步,它把数据和函数实现都隐藏在实现类中,而在抽象基类中提供丰富的接口函数供调用,这些函数都是public的纯虚函数。这样的抽象基类叫接口类。

由于抽象基类不能够实例化,并且实现类被完全隐藏,所以必须以其他的途径使用户能够获得实现类的对象,比如提供入口函数来动态创建实现类的对象。如果函数可以是全局函数,但最好是静态称冠函数。

(3)动态绑定

动态绑定(或运行时绑定、晚绑定):如果将基类Shape的函数Draw()声明为virtual的,然后用指向派生类对象的基类指针或引用来调用Draw(),那么程序会在运行时选择该派生类的Draw()函数而不是Shape::Draw()(即是,运行时绑定特性)。如下示例:


每一个具有虚函数的类都叫做多态类,这个虚函数或者是从基类继承来的,或者是自己新增加的。C++编译器必须为每一个多态类至少创建一个虚函数(vtable),它其实就是一个函数指针数组,其中存放着这个类所有的虚函数的地址及该类的类型信息,其中包括那些继承但未改写(overrides)的函数。每一个多态对象都有一个隐含的指针成员,它指向所属类型的vtable,这就是vptr.

虚函数的动态绑定采用的是运行时函数寻址技术,该类型信息主要用在RTTI技术上。通过基类指针或引用对虚函数的调用语句都会被编译器改写成下面形式:

(*(p->_vptr[slotNum]))(p, arg-list); //指针当做数组来用,最后改写为指针运算

标准C++实现采用的策略:派生类定义中的名字(对象或函数名)将义无反顾地遮蔽(即隐藏)掉基类中任何同名的对象或函数。但如果派生类定义了一个与基类的虚函数同名的虚函数,但是参数列表有所不同,那么就不会被编译器认为是对基类虚函数的改写,而是隐藏,所以不可能发生运行时绑定。

要想达成运行时绑定的效果,派生类和基类中同名的虚函数必须具有相同的原型。举例如下声明//(1)不是对基类的Draw()函数的改写,而是新增的virtual函数,声明//(2)才是对基类Draw()函数的改写.



上例中,如果RectangleImpl不重新定义Draw()函数,那么下面的代码:RectangleImple 不重新定义Draw()函数,那么下面的代码:

RectangleImpl *pRectImpl = new RectangleImpl;

pRectImpl->Draw(); //(3)

pRectImpl->Draw(200); //ok!

将无法编译,因为//(3)处调用的Draw()是基类的函数,它被RectangleImpl中的同名函数Draw(int)隐藏了。

(4)运行时多态

运行时多态:由于许多派生类因为继承了共同的基类而建立is-a关系时,每一个派生类的对象都可以被当成基类的对象来使用,这些派生类对象能贵统一函数调用做出不同的反应,这就是运行时多态。

C++支持运行时多态特性有两种手段,一种是虚函数机制,另一种是RTTI。c++支持多态方法如下:

@  经过隐含的转型操作,令一个public多态基类的指针或者引用指向它的一个派生类的对象

@  通过这个指针或者引用调用基类的虚函数,包括通过指针的反引用调用虚函数,因为反引用一个指针将返回所指对象的引用;

@  使用dynameic_cast<>和typeid运算符。

综合C++的“虚函数”和“多态”,有如下特点:

@  应用程序不必为每一个派生类编写功能调用,只需要对基类的虚函数进行改写和扩展即可。

@派生类的功能可以被基类指针引用,这叫向后兼容。以前写的程序可以被将来写的程序调用;将来写的程序也可以被以前写得程序调用。

现在许多分布式中间件比如CORBA、COM等充分利用了虚函数动态绑定这一优点,把底层的网络通信功能全部封装起来,而在上层预留一些回调接口由用户实现,这些接口往往表现为虚函数。

(5)多态数组

多态和指针算术运算不能混合运用,而数组操作几乎总是会涉及到指针运算,因此多态和数组不应该混合运用(Scott Meyers).

解决办法:不要在数组中直接存放多态对象,而是换之以基类指针或者基类的智能指针。(直接使用基类的指针可能需要自己负责删除它们指向的对象,但是使用智能指针则不需要操这份心)。

普通指针使用:

智能指针多态数组