C++之多态的一个例子

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

这个例题难度不大,但程序很长。对于一个比较大的程序,应当分成若干步骤进行。先声明基类,再声明派生类,逐级进行,分步调试。 1) 声明基类Point 类可写出声明基类Point的部分如下:

#include <iostream>
//声明类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,y初始化
   x=a;
   y=b;
}
void Point::setPoint(float a,float b) //设置x和y的坐标值
{  //为x,y赋新值
   x=a;
   y=b;
}
//重载运算符“<<”,使之能输出点的坐标
ostream & operator <<(ostream &output, const Point &p)
{
   output<<"["<<p.x<<","<<p.y<<"]"<<endl;
   return output;
}

以上完成了基类Point类的声明。 为了提高程序调试的效率,提倡对程序分步调试,不要将一个长的程序都写完以后才统一调试,那样在编译时可能会同时出现大量的编译错误,面对一个长的程序,程序人员往往难以迅速准确地找到出错位置。要善于将一个大的程序分解为若干个文件,分别编译,或者分步调试,先通过最基本的部分,再逐步扩充。 现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出main函数。实际上它是一个测试程序。

int main( )
{
   Point p(3.5,6.4);  //建立Point类对象p
   cout<<"x="<<p.getX( )<<",y="<<p.getY( )<<endl;  //输出p的坐标值
   p.setPoint(8.5,6.8);  //重新设置p的坐标值
   cout<<"p(new):"<<p<<endl;  //用重载运算符“<<”输出p点坐标
   return 0;
}

getX和getY函数声明为常成员函数,作用是只允许函数引用类中的数据,而不允许修改它们,以保证类中数据的安全。数据成员x和y声明为protected,这样可以被派生类访问(如果声明为private,派生类是不能访问的)。 程序编译通过,运行结果为: 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=["<<c.x<<","<<c.y<<"],r="<<c.radius<<",area="<<c.area( )<<endl;
   return output;
}

为了测试以上Circle类的定义,可以写出下面的主函数:

int main( )
{
   Circle c(3.5,6.4,5.2);  //建立Circle类对象c,并给定圆心坐标和半径
   cout<<"original circle:\\nx="<<c.getX()<<", y="<<c.getY()<<", r="<<c.getRadius( )<<", area="<<c.area( )<<endl;  //输出圆心坐标、半径和面积
   c.setRadius(7.5);  //设置半径值
   c.setPoint(5,5);  //设置圆心坐标值x,y
   cout<<"new circle:\\n"<<c;  //用重载运算符“<<”输出圆对象的信息
   Point &pRef=c;  //pRef是Point类的引用变量,被c初始化
   cout<<"pRef:"<<pRef;  //输出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] (输出圆的圆心“点”的数据) 可以看到,在Point类中声明了一次运算符“ <<”重载函数,在Circle类中又声明了一次运算符“ <<”,两次重载的运算符“<<”内容是不同的,在编译时编译系统会根据输出项的类型确定调用哪一个运算符重载函数。main函数第7行用“cout<< ”输出c,调用的是在Circle类中声明的运算符重载函数。 请注意main函数第8行:     Point & pRef = c; 定义了 Point类的引用变量pRef,并用派生类Circle对象c对其初始化。前面我们已经讲过,派生类对象可以替代基类对象为基类对象的引用初始化或赋值(详情请查看:C++基类与派生类的转换)。现在 Circle是Point的公用派生类,因此,pRef不能认为是c的别名,它得到了c的起始地址, 它只是c中基类部分的别名,与c中基类部分共享同一段存储单元。所以用“cout<<pRef”输出时,调用的不是在Circle中声明的运算符重载函数,而是在Point中声明的运算符重载函数,输出的是“点”的信息,而不是“圆”的信息。 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=["<<cy.x<<","<<cy.y<<"],r="<<cy.radius<<",h="<<cy.height <<"\\narea="<<cy.area( )<<", volume="<<cy.volume( )<<endl;
   return output;
} //重载运算符“<<”

可以写出下面的主函数:

int main( )
{
   Cylinder cy1(3.5,6.4,5.2,10);//定义Cylinder类对象cy1
   cout<<"\\noriginal cylinder:\\nx="<<cy1.getX( )<<", y="<<cy1.getY( )<<", r="
      <<cy1.getRadius( )<<", h="<<cy1.getHeight( )<<"\\narea="<<cy1.area()
      <<",volume="<<cy1.volume()<<endl;//用系统定义的运算符“<<”输出cy1的数据
   cy1.setHeight(15);//设置圆柱高
   cy1.setRadius(7.5);//设置圆半径
   cy1.setPoint(5,5);//设置圆心坐标值x,y
   cout<<"\\nnew cylinder:\\n"<<cy1;//用重载运算符“<<”输出cy1的数据
   Point &pRef=cy1;//pRef是Point类对象的引用变量
   cout<<"\\npRef as a Point:"<<pRef;//pRef作为一个“点”输出
   Circle &cRef=cy1;//cRef是Circle类对象的引用变量
   cout<<"\\ncRef as a Circle:"<<cRef;//cRef作为一个“圆”输出
   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作为一个“圆”输出) 说明:在Cylinder类中定义了 area函数,它与Circle类中的area函数同名,根据前面我们讲解的同名覆盖的原则(详情请查看:C++多重继承的二义性问题),cy1.area( ) 调用的是Cylinder类的area函数(求圆柱表面积),而不是Circle类的area函数(圆面积)。请注意,这两个area函数不是重载函数,它们不仅函数名相同,而且函数类型和参数个数都相同,两个同名函数不在同 —个类中,而是分别在基类和派生类中,属于同名覆盖。重载函数的参数个数和参数类型必须至少有一者不同,否则系统无法确定调用哪一个函数。 main函数第9行用“cout<<cy1”来输出cy1,此时调用的是在Cylinder类中声明的重载运算符“<<”,按在重载时规定的方式输出圆柱体cy1的有关数据。 main函数中最后4行的含义与在定义Circle类时的情况类似。pRef是Point类的引用变量,用cy1对其初始化,但它不是cy1的别名,只是cy1中基类Point部分的别名,在输出pRef时是作为一个Point类对象输出的,也就是说,它是一个“点”。同样,cRef是Circle类的引用变量,用cy1对其初始化,但它只是cy1中的直接基类Circle部分的别名, 在输出 cRef 时是作为Circle类对象输出的,它是一个"圆”,而不是一个“圆柱体”。从输 出的结果可以看出调用的是哪个运算符函数。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据结构与算法

03:八进制小数

03:八进制小数 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 65536kB描述 八进制有限小数均可以用十进制有限小数精确地表示。比如,八...

37670
来自专栏Java学习网

Java面试题系列之基础部分(二)——每天学5个问题

Java基础部分学习的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io的语法,虚拟机方面的语法,这些都是最基...

27450
来自专栏小小挖掘机

深入理解Python变量作用域与函数闭包

1、引言 最近遇到一个求最长回文子串的题目,于是,我写了如下的代码: class Solution(object): def longestPalind...

52760
来自专栏十月梦想

JavaScript数组元素排序

使用for循环遍历出数组;然后判断i号元素和i+1号大小,如果判断大于,存储小的元素,如果判断小于存储大的元素

9630
来自专栏WebDeveloper

python,详说正则表达式(对常用的关键字符的讲解)

[...]如果匹配的是个范围,可以这个写[0-9a-zA-B]表示0到9并a到z并A到B

13620
来自专栏MasiMaro 的技术博文

结构体和类

在C++中类与结构体并没有太大的区别,只是默认的成员访问权限不同,类默认权限为私有,而结构体为公有,所以在这将它们统一处理,在例子中采用类的方式。

22720
来自专栏逆向技术

C语言第五讲,语句 顺序循环选择.

括号的内容我们给真假就行, 对应到高级语言中 则是 true (真) 和 false(假)

41550
来自专栏Java帮帮-微信公众号-技术文章全总结

13.Java数据结构案例

13.Java数据结构案例 Java 实例 - 数字求和运算 以下实例演示了使用do...while结构求0~100的整数数字之和: TestInput.jav...

40560
来自专栏linux驱动个人学习

如何使用C语言的面向对象

我们都知道,C++才是面向对象的语言,但是C语言是否能使用面向对象的功能? (1)继承性 1 typedef struct _parent 2 { 3 int ...

42850
来自专栏猿人谷

【Objective-C】05-第一个OC的类

说明:这个Objective-C专题,是学习iOS开发的前奏,也为了让有面向对象语言开发经验的程序员,能够快速上手Objective-C。如果你还没有编程经验,...

233100

扫码关注云+社区

领取腾讯云代金券