南京莱斯信息技术股份有限公司 江苏 南京 210094
摘要:动态多态性提高了代码的可重用性和程序执行时的灵活性。本文详细讨论了 C++ 语言对动态多态的支持机制, 并结合例子说明了动态多态在程序设计中的应用。
关键词:C++ 语言; 动态多态; 抽象类; 虚函数
1 引言
C++ 是以 C 语言为基础, 支持数据抽象和面向对象的程序设计语言。 C++ 对 C 语言的扩充部分绝大多数来自许多著名语言中最优秀的特征。例如, 从 Algol68 中吸取了操作符重载机制, 等等。此外, 由于 C++ 语言具有与 C 语言一样的高执行效率, 并容易被熟悉 C 语言的软件人员接受, 因而得以流行。但这种混合型面向对象的程序设计语言, 毕竟是一种新的程序设计语言, 人们对它许多潜在的特性, 如封装、继承、多态等等, 还没有充分地理解和应用, 因此不能充分发挥其优势。这是作者撰写本文的目的所在。
2 动态多态性的应用
在 C++ 面向对象程序设计时, 通过将类型子类型化, 使一个程序段既能处理类型 T 的对象, 也能够处理类型 T 的子类型 S 的对象, 我们称该程序段为多态程序段, 以此来设计具有多态能力的应用程序。
子类型化在不少语言中存在, 如整数类型中的子集构成一个子类型。每一个子类型中的对象可以被用在高一级的类型中, 因为高一级类型中的所有操作适用于下一级的对象。
在 C++ 语言中公有继承能够实现子类型关系, 一个类可以直接或间接公有继承 1 个基类 ( 父类 ) 或多个基类, 这个类在 C++ 语言中称之为派生类, 那么派生类和 1 个或多个基类间的关系就是子类型关系。如下的声明:
class D:public P1,public p2{ ?? } ;
表示派生类 D 公有继承基类 P1 和 P2 , 即类 D 分别是类 P1 和类 P2 的子类型。这样类 P1 和 P2 的所有操作可用于 D 类的对象, 也就是 D 类的对象可用在 P1类的对象和 P2 类的对象可以出现的场合。
C++ 语言的继承机制允许派生类继承基类的所有操作, 或者说, 基类的操作能被用于操作派生类的对象, 但更经常的情况是, 我们在派生类中还声明有新的数据成员, 当要求基类中声明的操作能够适应派生类中的这种变化时, 简单的继承就不能满足我们的需要, 所以, 我们需要在派生类中需给出这个操作的新的实现, 如下例中的 void circle::showarea() 。我们先声明一个屏幕上的点类 point , 通过 point 类派生一个屏幕上的圆类 circle , 它们的声明如下:
#include <iostream.h>
class point // 屏幕上的点类
{int x,y;
public:point(int x1,int y1)
{x=x1;y=y1;}
void showarea()
{cout<< ” Area of point is: ” <<0.0<<endl;}
class circle:public point // 圆类
{int radius;
public:
circle(int x,int y,int r):point(x,y){radius=r;}
void showarea (){ cout<< ” Area of circle is: ” <<3.14*radius*radius<<endl;}
void disparea(const point &r) // 多态程序段
{r.showarea();}
void main()
{point p1(1,1); disparea(p1);circle c1(1,1,1); disparea(c1);
函数 disparea() 是一个多态程序段, 它既能处理基类 point 的对象, 也能处理派生类 circle 的对象, 在上述的 main() 函数中, 已说明了这一点, 在此不再赘述。下面我们来看一下程序的运行结果:
Area of point is:0
Area of circle is:0
而我们所要的二个结果数据应该是 0和 3.14 。为什么会出错呢? 由于表达式 r.showarea() 中的函数调用在编译时就被绑定到基类 point 的 showarea() 函数体上,使得这个表达式中的函数调用发生时始终执行 point类的 showarea() 。为此, 当程序员在实现一个派生类而变动了基类中的操作的实现时, C++ 提供虚函数机制可将这种变动告诉编译器, 即将关键字 virtual 放在类 point中该函数的函数说明之前 (virtual void showarea()) , 程序其它部分保持不变 (circle::showarea() 自动地成为虚函数 ) , 编译器就不会对函数调用 r.showarea() 进行静态绑定, 而产生有关的代码, 使函数调用与它所应执行的代码的绑定工作在程序运行时进行, 使得上述程序的运行结果的第二个数据为 3.14 。在程序运行时进行的绑定被称为动态绑定。
在进行多态性应用程序设计时, 还涉及到二个很重要的面向对象概念, 静态绑定和动态绑定, 我们来解释一下这二个概念。在编译或连接时进行的绑定, 我们称之为静态绑定, 在程序运行时进行的绑定, 我们称之为动态绑定。在 C++ 语言中, 当用基类指针或引用对虚函数进行访问, 则采用动态绑定, 其它情况将采用静态绑定, 这是掌握多态性的关键所在。
函数 disparea() 中出现的消息 r.showarea() 既能发送到基类 point 的对象, 也能发送到派生类 circle 的对象上, 从而导致不同的行为。当然前提条件是, 基类 point的成员函数 showarea() 必须声明为虚的, 此外, 参数 r 也必须是引用形参或指针形参, 否则就不行。
从上述例子中看出, 利用虚函数, 可在基类和派生类中使用相同的函数原型, 而定义函数的不同实现, 从而实现“一个接口, 多种方式”。当用基类指针或引用对虚函数进行访问时, 软件系统将根据运行时指针或引用所指向或引用的实际对象来确定调用对象所在类的虚函数版本。
在 C++ 语言中是采用虚函数机制支来持动态多态的, 虚拟函数为 C++ 提供了灵活、方便的动态多态机制,这种多态性在程序运行时才能确定, 因此虚拟函数是多态性的精华, 在 C++ 语言中至少含有一个虚函数的类也就称为多态类了。动态多态在程序设计中使用相当频繁, 我们必须理解好这个概念并掌握其应用。
3 纯虚函数
C++ 语言还增加了纯虚函数机制用来更好地支持动态多态性。对于有如图 1(a) 结构的类层次:
假如每个类中都有一个函数“ void display(void);”,那么,怎样对它们按多态性进行统一处理呢?对这类问题我们应先设计一个抽象的类A,使它成为所有类的祖先类,如图1(b)所示。设置类A的目的是由它说明如何统一使用该类层次中的display()函数的方法(赋值兼容规则从语法上保证了A的子孙类可按A说明的方式使用display()函数;多态性则从语义上保证了在执行时,根据实际的对象访问相应对象类中的display()函数)。
为了保证在类A中设置的display()函数是抽象动作,并能说明类A是一个抽象的类,在C++中,可用纯虚函数机制在类A中将display成员函数声明为:
virtual void display(void)=0;
即可, 这样类 A 便是一个抽象类, 在 C++ 语言中称至少含有一个纯虚函数的类为抽象类。请注意, 在类 A的子孙类中要么给出 display() 的实现, 要么最好重新将该函数声明为纯虚函数。
从上面的分析可以看出, 类 A 的设计尽管是用继承性语法表达的, 但它的主要目的不是为代码共享而设计的, 而是为了使用多态性设计的, 它是另一个维度的抽象。
我们在使用纯虚函数时应注意, 派生类中必须给出基类纯虚函数的实现或重声明为纯的, 不能声明抽象类的对象。在面向对象程序设计中, 抽象类有 2 个重要的使用场合, 一是作为公有基类, 二是用来声明指针或引用, 在上面使用多态性的例子中都用到了。
4 结束语
从上面讨论中我们得出, C++ 的动态多态特性不仅提高了程序的灵活性, 因为绑定是在程序运行时进行的, 而且还提高了代码的可重用率, 因为多态程序段不仅适用于基类的对象, 而且还适用于不同直接或间接公有派生类的对象。因此, 我们在进行面向对象程序设计时, 恰当地使用动态多态是很有益处的。
论文作者:胥宝新
论文发表刊物:《防护工程》2017年第33期
论文发表时间:2018/3/26
标签:函数论文; 多态性论文; 对象论文; 绑定论文; 程序论文; 语言论文; 多态论文; 《防护工程》2017年第33期论文;