Chinaunix首页 | 论坛 | 博客
  • 博客访问: 165979
  • 博文数量: 29
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 806
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-05 10:31
个人简介

share your ideas

文章分类

全部博文(29)

文章存档

2015年(1)

2013年(28)

分类: C/C++

2013-08-14 20:09:21

多态性(polymorphism)是面向对象程序设计的一个重要特征。利用多态性可以设计和实现一个易于扩展的系统。

在C++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。

在C++程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。

前边学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。

动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(virtual function)实现的。

有关静态多态性的应用已经介绍过了,这里主要介绍动态多态性和虚函数。

要研究的问题是:当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个对象的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以说,多态性是“一个接口,多种方法”。
下面是一个承上启下的例子。一方面它是有关继承和运算符重载内容的综合应用的例子,通过这个例子可以进一步融会贯通前面所学的内容,另一方面又是作为讨论多态性的一个基础用例。

例12.1 先建立一个Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个Cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符“<<”和“>>”,使之能用于输出以上类对象。

对于一个比较大的程序,应当分成若干步骤进行。先声明基类,再声明派生类,逐级进行,分步调试。声明基类Point类可写出声明基类Point的部分如下:
#include
//声明类Point

class Point
{
   public:
   Point(float x=0,float y=0);//有默认参数的构造函数
   void setPoint(float ,float);//设置坐标值
   float getX( )const {return x;}//读x坐标
   float getY( )const {return y;}//读y坐标
   friend ostream & operator <<(ostream &,const Point &);//重载运算符“<<”
   protected ://受保护成员
   float x, y;
};
//下面定义Point类的成员函数
Point::Point(float a,float b) //Point的构造函数
{x=a;y=b;} //对x,y初始化
void Point::setPoint(float a,float b) //设置x和y的坐标值
{x=a;y=b;} //为x,y赋新值
ostream & operator <<(ostream &output, const Point &p){output<<″[″<

以上完成了基类Point类的声明。

现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出main函数。实际上它是一个测试程序。
int main( )
{
   Point p(3.5,6.4);//建立Point类对象p
   cout<<″x=″<
   p.setPoint(8.5,6.8);//重新设置p的坐标值
   cout<<″p(new):″<
}
程序编译通过,运行结果为
x=3.5,y=6.4
p(new):[8.5,6.8]
测试程序检查了基类中各函数的功能,以及运算符重载的作用,证明程序是正确的。

(2)声明派生类Circle在上面的基础上,再写出声明派生类Circle的部分:
class Circle:public Point//circle是Point类的公用派生类
{
   public:
   Circle(float x=0,float y=0,float r=0);//构造函数
   void setRadius(float );//设置半径值
   float getRadius( )const;//读取半径值
   float area ( )const;//计算圆面积
   friend ostream &operator <<(ostream &,const Circle &);//重载运算符“<<”
   private:
   float radius;
};
//定义构造函数,对圆心坐标和半径初始化
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){}
//设置半径值
void Circle::setRadius(float r){radius=r;}
//读取半径值
float Circle::getRadius( )const {return radius;}
//计算圆面积
float Circle::area( )const
{
   return 3.14159*radius*radius;
}
//重载运算符“<<”,使之按规定的形式输出圆的信息
ostream &operator <<(ostream &output,const Circle &c)
{
   output<<″Center=[″<
   return output;
}
为了测试以上Circle类的定义,可以写出下面的主函数:
int main( )
{
   Circle c(3.5,6.4,5.2);//建立Circle类对象c,并给定圆心坐标和半径
   cout<<″original circle:\\nx=″<
   c.setRadius(7.5);//设置半径值
   c.setPoint(5,5);//设置圆心坐标值x,y
   cout<<″new circle:\\n"<
   Point &pRef=c;//pRef是Point类的引用变量,被c初始化
   cout<<″pRef:″<
   return 0;
}
程序编译通过,运行结果为
original circle:(输出原来的圆的数据)
x=3.5, y=6.4, r=5.2, area=84.9486
new circle:(输出修改后的圆的数据)
Center=[5,5], r=7.5, area=176.714
pRef:[5,5] (输出圆的圆心“点”的数据)

(3)声明Circle的派生类Cylinder前面已从基类Point派生出Circle类,现在再从Circle派生出Cylinder类。
class Cylinder:public Circle// Cylinder是Circle的公用派生类
{
   public :
   Cylinder (float x=0,float y=0,float r=0,float h=0);//构造函数
   void setHeight(float );//设置圆柱高
   float getHeight( )const;//读取圆柱高
   loat area( )const;//计算圆表面积
   float volume( )const;//计算圆柱体积
   friend ostream& operator <<(ostream&,const Cylinder&);//重载运算符<<”
   protected :
   float height;//圆柱高
};
//定义构造函数
Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){}
//设置圆柱高
void Cylinder::setHeight(float h){height=h;}
//读取圆柱高
float Cylinder::getHeight( )const {return height;}
//计算圆表面积
float Cylinder::area( )const { return 2*Circle::area( )+2*3.14159*radius*height;}
//计算圆柱体积
float Cylinder::volume()const {return Circle::area()*height;}
ostream &operator <<(ostream &output,const Cylinder& cy)
{
   output<<″Center=[″<
   return output;
} //重载运算符“<<”
可以写出下面的主函数:
int main( )
{
   Cylinder cy1(3.5,6.4,5.2,10);//定义Cylinder类对象cy1
   cout<<″\\noriginal cylinder:\\nx=″<
      <
      <<″,volume=″<
   cy1.setHeight(15);//设置圆柱高
   cy1.setRadius(7.5);//设置圆半径
   cy1.setPoint(5,5);//设置圆心坐标值x,y
   cout<<″\\nnew cylinder:\\n″<
   Point &pRef=cy1;//pRef是Point类对象的引用变量
   cout<<″\\npRef as a Point:″<
   Circle &cRef=cy1;//cRef是Circle类对象的引用变量
   cout<<″\\ncRef as a Circle:″<
   return 0;
}
运行结果如下:
original cylinder:(输出cy1的初始值)
x=3.5, y=6.4, r=5.2, h=10 (圆心坐标x,y。半径r,高h)
area=496.623, volume=849.486 (圆柱表面积area和体积volume)
new cylinder: (输出cy1的新值)
Center=[5,5], r=7.5, h=15 (以[5,5]形式输出圆心坐标)
area=1060.29, volume=2650.72(圆柱表面积area和体积volume)
pRef as a Point:[5,5] (pRef作为一个“点”输出)
cRef as a Circle:Center=[5,5], r=7.5, area=176.714(cRef作为一个“圆”输出)

在本例中存在静态多态性,这是运算符重载引起的。可以看到,在编译时编译系统即可以判定应调用哪个重载运算符函数。

在类的继承层次结构中,在不同的层次中可以出现名字相同、参数个数和类型都相同而功能不同的函数。编译系统按照同名覆盖的原则决定调用的对象。

在例12.1程序中用cy1.area( )调用的是派生类Cylinder中的成员函数area。如果想调用cy1中的直接基类Circle的area函数,应当表示为:cy1.Circle::area( )。用这种方法来区分两个同名的函数。

但是这样做很不方便。人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数。在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们。

例如,用同一个语句“pt->display( );”可以调用不同派生层次中的display函数,只需在调用前给指针变量pt赋以不同的值(使之指向不同的类对象)即可。C++中的虚函数就是用来解决这个问题的。

虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

请分析例12.2。这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况。

例12.2 基类与派生类中有同名函数。在下面的程序中Student是基类,Graduate是派生类,它们都有display这个同名的函数。
#include
#include
using namespace std;
//声明基类Student
class Student
{
   public:Student(int, string,float);//声明构造函数void display( );//声明输出函数
   protected ://受保护成员,派生类可以访问
   int num;
   string name;
   float score;
};
//Student类成员函数的实现
Student::Student(int n, string nam,float s)//定义构造函数{num=n;name=nam;score=s;}
void Student::display( )//定义输出函数{cout<<″num:″<
阅读(2854) | 评论(0) | 转发(7) |
给主人留下些什么吧!~~