php c++多态区别,C++_详解C++编程的多态性概念,多态性(polymorphism)是面向对象 - phpStudy...

详解C++编程的多态性概念

多态性(polymorphism)是面向对象程序设计的一个重要特征。如果一种语言只支持类而不支持多态,是不能被称为面向对象语言的,只能说是基于对象的,如Ada、VB就属此类。C++支持多态性,在C++程序设计中能够实现多态性。利用多态性可以设计和实现一个易于扩展的系统。

顾名思义,多态的意思是一个事物有多种形态。多态性的英文单词polymorphism来源于希腊词根poly(意为“很多”)和morph(意为“形态”)。在C ++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

其实,我们已经多次接触过多态性的现象,例如函数的重载、运算符重载都是多态现象。只是那时没有用到多态性这一专门术语而已。例如,使用运算符“+”使两个数值相加,就是发送一个消息,它要调用operator +函数。实际上,整型、单精度型、双精度型的加法操作过程是互不相同的,是由不同内容的函数实现的。显然,它们以不同的行为或方法来响应同一消息。

在现实生活中可以看到许多多态性的例子。如学校校长向社会发布一个消息:9月1日新学年开学。不同的对象会作出不同的响应:学生要准备好课本准时到校上课;家长要筹集学费;教师要备好课;后勤部门要准备好教室、宿舍和食堂……由于事先对各种人的任务已作了规定,因此,在得到同一个消息时,各种人都知道自己应当怎么做,这就是 多态性。可以设想,如果不利用多态性,那么校长就要分别给学生、家长、教师、后勤部门等许多不同的对象分别发通知,分别具体规定每一种人接到通知后应该怎么做。显然这是一件十分复杂而细致的工作。一人包揽一切,吃力还不讨好。现在,利用了多态性机制,校长在发布消息时,不必一一具体考虑不同类型人员是怎样执行的。至于各类人员在接到消息后应气做什么,并不是临时决定的,而是学校的工作机制事先安排决定好的。校长只需不断发布各种消息,各种人员就会按预定方案有条不紊地工作。

同样,在C++程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类 时,不必考虑它们是什么类型,只要发布消息即可。正如在使用运算符“ ”时不必考虑相加的数值是整型、单精度型还是双精度型,直接使用“+”,不论哪类数值都能实现相加。可以说这是以不变应万变的方法,不论对象千变万化,用户都是用同一形式的信息去调用它们,使它们根据事先的安排作出反应。

从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。以前学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(Virtual fiinction)实现的。

下面是一个承上启下的例子。一方面它是有关继承和运算符重载内容的综合应用的例子,通过这个例子可以进一步融会贯通前面所学的内容,另一方面又是作为讨论多态性的一个基础用例。

希望大家耐心、深入地阅读和消化这个程序,弄清其中的每一个细节。

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

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

1) 声明基类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 <

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 <

{

output<

return output;

}

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

为了提高程序调试的效率,提倡对程序分步调试,不要将一个长的程序都写完以后才统一调试,那样在编译时可能会同时出现大量的编译错误,面对一个长的程序,程序人员往往难以迅速准确地找到出错位置。要善于将一个大的程序分解为若干个文件,分别编译,或者分步调试,先通过最基本的部分,再逐步扩充。

现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出main函数。实际上它是一个测试程序。

int main( )

{

Point p(3.5,6.4); //建立Point类对象p

cout<

p.setPoint(8.5,6.8); //重新设置p的坐标值

cout<

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 <

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 <

{

output<

return output;

}

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

int main( )

{

Circle c(3.5,6.4,5.2); //建立Circle类对象c,并给定圆心坐标和半径

cout<

c.setRadius(7.5); //设置半径值

c.setPoint(5,5); //设置圆心坐标值x,y

cout<

Point &pRef=c; //pRef是Point类的引用变量,被c初始化

cout<

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类中声明了一次运算符“ <

请注意main函数第8行:

Point & pRef = c;

定义了 Point类的引用变量pRef,并用派生类Circle对象c对其初始化。前面我们已经讲过,派生类对象可以替代基类对象为基类对象的引用初始化或赋值(详情请查看:C++基类与派生类的转换)。现在 Circle是Point的公用派生类,因此,pRef不能认为是c的别名,它得到了c的起始地址, 它只是c中基类部分的别名,与c中基类部分共享同一段存储单元。所以用“cout<

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 <

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 <

{

output<

return output;

} //重载运算符“<

可以写出下面的主函数:

int main( )

{

Cylinder cy1(3.5,6.4,5.2,10);//定义Cylinder类对象cy1

cout<

<

<

cy1.setHeight(15);//设置圆柱高

cy1.setRadius(7.5);//设置圆半径

cy1.setPoint(5,5);//设置圆心坐标值x,y

cout<

Point &pRef=cy1;//pRef是Point类对象的引用变量

cout<

Circle &cRef=cy1;//cRef是Circle类对象的引用变量

cout<

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<

main函数中最后4行的含义与在定义Circle类时的情况类似。pRef是Point类的引用变量,用cy1对其初始化,但它不是cy1的别名,只是cy1中基类Point部分的别名,在输出pRef时是作为一个Point类对象输出的,也就是说,它是一个“点”。同样,cRef是Circle类的引用变量,用cy1对其初始化,但它只是cy1中的直接基类Circle部分的别名, 在输出 cRef 时是作为Circle类对象输出的,它是一个"圆”,而不是一个“圆柱体”。从输 出的结果可以看出调用的是哪个运算符函数。

在本例中存在静态多态性,这是运算符重载引起的(注意3个运算符函数是重载而不是同名覆盖,因为有一个形参类型不同)。可以看到,在编译时编译系统即可以判定应调用哪个重载运算符函数。相关阅读:

Oracle读取excel数据

防御SQL注入的方法总结

Android用HandlerThread模拟AsyncTask功能(ThreadTask)

Windows PostgreSQL 安装图文教程

C#使用oledb导出数据到excel的方法

一次Linux系统被服务器被rootkit攻击的处理思路和处理过程

table行随鼠标移动变色示例

php 修改、增加xml结点属性的实现代码

Android编程之动态壁纸实例分析

Js的Array数组对象详解

android使用videoview播放视频

Android下拉列表spinner的实例代码

当鼠标移动时出现特效的JQuery代码

用户发现Cortana文件阻碍导致Win10系统还原失败

你可能感兴趣的:(php,c++多态区别)