第十九讲:纯虚函数与抽象类

第十九讲:纯虚函数与抽象类

本讲基本要求

    * 掌握:纯虚函数、抽象类的概念。
    * 理解:纯虚函数和抽象基类的应用。
    重点、难点: 纯虚函数、抽象类的概念。

一、 纯虚函数

   有时在基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。基类本身不需要这个函数,但是,在其直接派生类中都需要用到这一函数,而且这个函数的功能不同,在这种情况下应当将声明为虚函数。可以在基类中声明为虚函数。

virtual float area() const{ returnO;}

其返回值为0,表示“点”是没有面积的。其实,在基类中并不使用这个函数,其返回值也是没有意义的。为简化,可以不写出这种无意义的函数体,只给出函数的原型,并在后面加上“=0”
     如: virtual float area() const=O; //纯虚函数
   
这就将area声明为一个纯虚函数(pure virtual function)。纯虚函数是在声明虚函数时被 “初始化”为0的函数。声明纯虚函数的一般形式是:

virtual 函数类型函数名(参数表列)=0;

注意:
   
1、 纯虚函数没有函数体;
   2、 最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
   3、 这是一个声明语句,最后应有分号。

说明:
   
1、纯虚函数只有函数的名字而不具备函数的功能,不能被调用。可以说它是“徒有其 名,而无其实”。它只是通知编译系统:“在这里声明一个虚函数,留待派生类中定义”。在派生类中对此函数提供定义后,它才能具备函数的功能,可以被调用。
   2、纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。
   3、如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。

二、抽象类

   如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来生成对象。定义这些类的惟一目的是用它作为基类去建立派生类。它们作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类。用这些派生类去建立对象。

   不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class)由于它常用作基类,通常称为抽象基类(abstract base class)。凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。

   抽象类的作用:作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。一个类层次结构中当然也可以不包含任何抽象类,每一层次的类都是实际可用的,都是可以用来建立对象的。但是,许多好的面向对象的系统,其层次结构的顶部是一个抽象类,甚至顶部有好几层都是抽象类。

   如果在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用。这个派生类就不是抽象类,而是可以用来定义对象的具体类(concrete class)。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。

   虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针变量,当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。

三、 应用实例

例4 虚函数和抽象基类的应用

   在本章例1介绍了以Point为基类的点一圆一圆柱体类的层次结构。现在要对它 进行改写,在程序中使用虚函数和抽象基类。类的层次结构的顶层是抽象基类Shape(形 状)。Point(点),Circle(圆),Cylinder (圆柱体)都是Shape类的直接派生类和间接派生类,下面是一个完整的程序,程序如下:

第(1)部分
#include <iostream> //声明抽象基类Shape
using namespace std;
class Shape
   { public:
      virtual float area() const {return 0.0;}   //虚函数
      
virtual float volume() const {return 0.0;} //虚函数
      
virtual void shapeName() const =0;};      //纯虚函数

   
Shape类有3个成员函数,没有数据成员。3个成员函数都声明为虚函数,其中shapeName声明为纯虚函数,因此Shape是一个抽象基类。shapeName函数的作用是输出具体的形状(如点、圆、圆柱体)的名字,这个信息是与相应的派生类密切相关的,显然这不应当在基类中定义,而应在派生类中定义。所以把它声明为纯虚函数。Shape虽然是抽象基类,但是也可以包括某些成员的定义部分。类中两个函数area(面积)和volume(体积)包括函数体,使其返回值为0(因为可以认为点的面积和体积都为0)。由于考虑到在Point类中不再对area和volume函数重新定义,因此没有把area和volume函数也声明为纯虚函数。 在Point类中继承了Shape类的area和volume函数。这3个函数在各派生类中都要用到。

第(2)部分
//声明Point类
class Point:public Shape //Point是Shape的公用派生类
   
{ public:
      Point(float=0,float=0);
      void setPoint(float,float);
      float getX() const {return x;}
      float getY() const {return y;}
      virtual void shapeName() const {cout<<"Point:";} //对纯虚函数进行定义
      
friend ostream & operator<<(ostream &,const Point &);
     protected:
      float x,y; };

Point::Point(float a,float b)
   {  x=a;y=b;}

void Point::setPoint(float a,float b)
   { x=a;y=b;}

ostream & operator<<(ostream &output,const Point &p)//重载<<运算符
   
{  utput<<"["<<p.x<<","<<p.y<<"]";
return output; }

   Point从Shape继承了3个成员函数,由于“点”是没有面积和体积的,因此不必重新定义area和volume。虽然在Point类中用不到这两个函数,但是Point类仍然从Shape类继承了这两个函数,以便其派生类继承它们。shapeName函数在Shape类中是纯虚函数,在Point类中要进行定义。Point类还有自己的成员函数(setPoint,getX,getY)和数据成员(x和y)。

第(3)部分
//声明Circle类
class Circle:public Point
   { public:
      Circle(float x=0,float y=0,float r=0);
      void setRadius(float);
      float getRadius() const;
      virtual float area() const;//对虚函数进行重新定义
      
virtual void shapeName() const {cout<<"Circle:";} //对纯虚函数再定义
      
friend ostream &operator<<(ostream &,const Circle &);//重载<<运算符
    
protected:
      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<<"["<<c.x<<","<<c.y<<"], r="<<c.radius;
     return output; }

   在Circle类中要重新定义ama函数,因为需要指定求圆面积的公式。由于圆没有体积,因此不必重新定义volume函数,而是从Point类继承volume函数。shapeName函数是虚函数,需要重新定义,赋予新的内容(如果不重新定义,就会继承Point类中的shapeName函数)。此外,Circle类还有自己新增加的成员函数(setRadius,getRadius)和数据成员(radius)。

第(4)部分
//声明Cylinder类
class Cylinder:public Circle
   { public:
      Cylinder (float x=0,float y=0,float r=0,float h=0);
      void setHeight(float);
      float getHeight() const;
      virtual float area() const;//对虚函数进行重新定义
      
virtual float volume() const;//对虚函数进行重新定义
      
virtual void shapeName() const {cout<<"Cylinder:";} //对纯虚函数再定义
      
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<<"["<<cy.x<<","<<cy.y<<"], r="<<cy.radius<<", h="<<cy.height;
      return output; }

   Cylinder类是从Circle类派生的。由于圆柱体有表面积和体积,所以要对afea和volume函数重新定义。虚函数shapeName也需要重新定义。此外,Cylinder类还有自己的成员函数setHeight和数据成员radius。

第(5)部分

int main()
 { Point point(3.2,4.5); //建立Point类对象point
   
Circle circle(2.4,12,5.6); //建立Circle类对象circle
   
Cylinder cylinder(3.5,6.4,5.2,10.5); //建立Cylinder类对象cylinder

   point.shapeName(); //静态关联
   
cout<<point<<endl;

   circle.shapeName(); //静态关联
   
cout<<circle<<endl;

   cylinder.shapeName(); //静态关联
   
cout<<cylinder<<endl<<endl;

   Shape *pt; //定义基类指针

   pt=&point; //指针指向Point类对象
   
pt->shapeName(); //动态关联
   
cout<<"x="<<point.getX()<<",y="<<point.getY()<<"/narea="<<pt->area()
<<"/nvolume="<<pt->volume()<<"/n/n";   

   pt=&circle; //指针指向Circle类对象
   
pt->shapeName(); //动态关联
   
cout<<"x="<<circle.getX()<<",y="<<circle.getY()<<"/narea="<<pt->area()
<<"/nvolume="<<pt->volume()<<"/n/n";

   pt=&cylinder; //指针指向Cylinder类对象
   
pt->shapeName(); //动态关联
   
cout<<"x="<<cylinder.getX()<<",y="<<cylinder.getY()<<"/narea="<<pt->area()
<<"/nvolume="<<pt->volume()<<"/n/n";
   return 0; }

   运行结果:

Point:[3.2,4.5]       (Point类对象pomt的数据:点的坐标)
Circle:[2.4,l.2],r=5.6 (Circle类对象circle的数据:圆心和半径)
Cylinder:[3.5,6.4],r=5.5,h=lO.5 (Cylinder类对象cylinder的数据:圆心、半径和高)
Point:x=3.2,y=4.5 (输出Point类对象pomt的数据:点的坐标)
area=O           (点的面积)
volume=O         (点的体积)
Circle:x=2.4,y=1.2 (输出Circle类对象circle的数据:圆心坐标)
area=98.5203       (圆的面积)
volume=O          (圆的体积)
Cylinder:x=3.5,y=6.4 (输出Cylinder类对象cylinder的数据:圆心坐标)
area=512.595       (圆的面积)
volume=891.96      (圆柱的体积)

从本例可以进一步明确以下结论:

   1、一个基类如果包含一个或一个以上纯虚函数,就是抽象基类
   2、抽象基类与普通基类不同它可以没有任何物理上的或其他实体
   3、在类的层次结构中,顶层或最上面的几层可以是际意义方面的含义。
   
4、抽象基类是本类族的公共接口。或者说,从同一基类派生出的多个类有同一接口。因此能响应同一形式的消息
   5、区别静态关联和动态关联。
   6、如果在基类声明了虚函数,则在派生类中凡是与该函数有相同的函数名、函数类 型、参数个数和参数类型的函数,均为虚函数(不论在派生类中是否用virtual声明)。
   7、使用虚函数提高了程序的可扩充性。

   第七点对于软件开发是很有意义的,把类的声明与类的使用分离。这对于设计类库的软件开发商来说尤为重要。开发商设计了各种各样的类,但不向用户提供源代码,用户可以不知道类是怎样声明的,但是可以使用这些类来派生出自己的类。当然开发商要向用户提供类的接口(类所在的文件和类成员函数定义的目标文件的路径和文件名),以及使用说明(例如可以调用类中的虚函数area计算出面积)。

   利用虚函数和多态性,程序员的注意力集中在处理普遍性,而让执行环境处理特殊性。例如,抽象基类Shape可以派生出4个派生类Square(正方形)、Circle(圆形)、Rectangle(矩形)、Triangle(三角形),在每一个派生类中都包含~个虚函数draw,其作用是在屏幕上分别画出正方形、圆形、矩形和三角形的图形。程序员只需要进行宏观的操作,让程序调用各对象的draw函数即可。他使用基类指针来控制有关对象。不论对象在继承层次中处于哪一层,都可以用基类指针来指向它,并调用其中的draw函数,画出需要的图形。每个对象的draw函数都知道应怎样工作,这是在类中指定的,这就是执行环境。程序员不必考虑这些细节,他只要简单地告诉每个对象“绘制自己”即可。

   多态性把操作的细节留给类的设计者(他们多为专业人员)去完成,而让编程人员(类的使用者)只需要做一些宏观性的工作,告诉系统做什么,而不必考虑怎么做,这就极大地简化了应用程序的编码工作,大大减轻了程序员的负担,也降低了学习和使用c++编程的难度,使更多的人能更快地进入c++程序设计的大门。有人说,多态性是开启继承功能的钥匙。

 

转自:http://210.44.195.12/cgyy/text/HTML/text/19.htm

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值