[例12.4]虚函数和抽象基类的应用。我们在例12.1介绍了以Point为基类的点—圆—圆柱体类的层次结构。现在要对它进行改写,在程序中使用虚函数和抽象基类。类的层次结构的顶层是抽象基类Shape(形状)。Point(点),Circle(圆),Cylinder(圆柱体)都是Shape类的直接派生类和间接派生类。下面是一个完整的程序,为了便于阅读,分段插入了一些文字说明。程序如下:第(1)部分.#includeiostream.usingnamespacestd;.//声明抽象基类Shape.classShape.{.public:.virtualfloatarea()const{return0.0;}//虚函数.virtualfloatvolume()const{return0.0;}//虚函数.virtualvoidshapeName()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类.classPoint:publicShape//Point是Shape的公用派生类.{.public:.Point(float=0,float=0);.voidsetPoint(float,float);.floatgetX()const{returnx;}.floatgetY()const{returny;}.virtualvoidshapeName()const{coutPoint:;}//对虚函数进行再定义.friendostream&operator(ostream&,constPoint&);.protected:.floatx,y;.};..//定义Point类成员函数.Point::Point(floata,floatb).{x=a;y=b;}.voidPoint::setPoint(floata,floatb).{x=a;y=b;}.ostream&operator(ostream&output,constPoint&p).{.output[p.x,p.y];.returnoutput;.}Point从Shape继承了3个成员函数,由于“点”是没有面积和体积的,因此不必重新定义area和volume。虽然在Point类中用不到这两个函数,但是Point类仍然从Shape类继承了这两个函数,以便其派生类继承它们。shapeName函数在Shape类中是纯虚函数,在Point类中要进行定义。Point类还有自己的成员函数(setPoint,getX,getY)和数据成员(x和y)。第(3)部分.//声明Circle类.classCircle:publicPoint.{.public:.Circle(floatx=0,floaty=0,floatr=0);.voidsetRadius(float);.floatgetRadius()const;.virtualfloatarea()const;.virtualvoidshapeName()const{coutCircle:;}//对虚函数进行再定义.friendostream&operator(ostream&,constCircle&);.protected:.floatradius;.};.//声明Circle类成员函数.Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}.voidCircle::setRadius(floatr):radius(r){}.floatCircle::getRadius()const{returnradius;}.floatCircle::area()const{return3.14159*radius*radius;}.ostream&operator(ostream&output,constCircle&c).{.output[c.x,c.y],r=c.radius;.returnoutput;.}在Circle类中要重新定义area函数,因为需要指定求圆面积的公式。由于圆没有体积,因此不必重新定义volume函数,而是从Point类继承volume函数。shapeName函数是虚函数,需要重新定义,赋予新的内容(如果不重新定义,就会继承Point类中的shapeName函数)。此外,Circle类还有自己新增加的成员函数(setRadius,getRadius)和数据成员(radius)。第(4)部分.//声明Cylinder类.classCylinder:publicCircle.{.public:.Cylinder(floatx=0,floaty=0,floatr=0,floath=0);.voidsetHeight(float);.virtualfloatarea()const;.virtualfloatvolume()const;.virtualvoidshapeName()const{.coutCylinder:;.}//对虚函数进行再定义.friendostream&operator(ostream&,constCylinder&);.protected:.floatheight;.};.//定义Cylinder类成员函数.Cylinder::Cylinder(floata,floatb,floatr,floath):Circle(a,b,r),height(h){}.voidCylinder::setHeight(floath){height=h;}.floatCylinder::area()const{.return2*Circle::area()+2*3.14159*radius*height;.}.floatCylinder::volume()const{.returnCircle::area()*height;.}.ostream&operator(ostream&output,constCylinder&cy){.output[cy.x,cy.y],r=cy.radius,h=cy.height;.returnoutput;.}Cylinder类是从Circle类派生的。由于圆柱体有表面积和体积,所以要对area和volume函数重新定义。虚函数shapeName也需要重新定义。此外,Cylinder类还有自已的成员函数setHeight和数据成员radius。第(5)部分.//main函数.intmain().{.Pointpoint(3.2,4.5);//建立Point类对象point.Circlecircle(2.4,1.2,5.6);.//建立Circle类对象circle.Cylindercylinder(3.5,6.4,5.2,10.5);.//建立Cylinder类对象cylinder.point.shapeName();.//静态关联.coutpointendl;.circle.shapeName();//静态关联.coutcircleendl;.cylinder.shapeName();//静态关联.coutcylinderendlendl;.Shape*pt;//定义基类指针.pt=&point;//指针指向Point类对象.pt-shapeName();//动态关联.coutx=point.getX(),y=point.getY()\narea=pt-area().\nvolume=pt-volume()\n\n;.pt=&circle;//指针指向Circle类对象.pt-shapeName();//动态关联.coutx=circle.getX(),y=circle.getY()\narea=pt-area().\nvolume=pt-volume()\n\n;.pt=&cylinder;//指针指向Cylinder类对象.pt-shapeName();//动态关联.coutx=cylinder.getX(),y=cylinder.getY()\narea=pt-area().\nvolume=pt-volume()\n\n;.return0;.}在主函数中调用有关函数并输出结果。先分别定义了Point类对象point,Circle类对象circle和Cylinder类对象cylinder。然后分别通过对象名point,circle和cylinder调用了shapeNanme函数,这是属于静态关联,在编译阶段就能确定应调用哪一个类的shapeName函数。同时用重载的运箅符“”来输出各对象的信息,可以验证对象初始化是否正确。再定义一个指向基类Shape对象的指针变量pt,使它先后指向3个派生类对象point,Circle和cylinder,然后通过指针调用各函数,如pt-shapeName(),pt-area(),pt-volume()。这时是通过动态关联分别确定应该调用哪个函数。分别输出不同类对象的信息。程序运行结果如下:Point:[3.2,4.5](Point类对象point的数据:点的坐标)Circle:[2.4,1.2],r=5.6(Circle类对象circle的数据:圆心和半径)Cylinder:[3.5,6.4],r=5.5,h=10.5(Cylinder类对象cylinder的数据:圆心、半径和高)Point:x=3.2,y=4.5(输出Point类对象point的数据:点的坐标)area=0(点的面积)volume=0(点的体积)Circle:x=2.4,y=1.2(输出Circle类对象circle的数据:圆心坐标)area=98.5203(圆的面积)volume=0(圆的体积)Cylinder:x=3.5,y=6.4(输出Cylinder类对象cylinder的数据:圆心坐标)area=512.595(圆的面积)volume=891.96(圆柱的体积)从本例可以进一步明确以下结论:.一个基类如果包含一个或一个以上纯虚函数,就是抽象基类。抽象基类不能也不必要定义对象。.抽象基类与普通基类不同,它一般并不是现实存在的对象的抽象(例如圆形(Circle)就是千千万万个实际的圆的抽象),它可以没有任何物理上的或其他实际意义方面的含义。.在类的层次结构中,顶层或最上面的几层可以是抽象基类。抽象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象基类中声明。.抽象基类是本类族的公共接口。或者说,从同一基类派生出的多个类有同一接口。.区别静态关联和动态关联。如果是通过对象名调用虚函数(如point.shapeName()),在编译阶段就能确定调用的是哪一个类的虚函数,所以属于静态关联。如果是通过基类指针调用虚函数(如pt-shapeName()),在编译阶段无法从语句本身确定调用哪一个类的虚函数,只有在