在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。
其实,我们已经多次接触过多态性的现象,例如函数的重载、运算符重载都是多态现象。只是那时没有用到多态性这一专门术语而已。
在C++中,多态性表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。
系统实现的角度来看,多态性分为两类:静态多态性和动态多态性
程序设计要求:
先建立一个Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个Cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符“<<”和”使之能用于输出以上类对象。
(1)声明基类Point类
class Point{
public:
//构造函数
Point(float tx, float ty):x(tx),y(ty){}
//功能函数声明
float getPX();
float getPY();
void setPoint(float tx, float ty);
//重载输出函数 做友元 输出点坐标
friend ostream & operator<<(ostream &output, Point &pt);
protected:
float x,y;
};
/*************Point 类函数定义 **************/
float Point::getPX(){
return x;
}
float Point::getPY(){
return y;
}
void Point::setPoint(float tx, float ty){
x = tx;
y = ty;
}
//重载输出函数 输出点坐标
ostream & operator<<(ostream &output, Point &pt)
{
output << "(" << + pt.getPX() << ", " << pt.getPY() << ")";
return output;
}
功能测试:
int main()
{
Point p1(1,1);
cout << p1 << endl;
p1.setPoint(2,3);
cout << p1 << endl;
return 0;
}
验证基类及函数的功能,核心关注/回顾“<<”运算符重载函数。
(2)声明派生类Circle类
//构造圆类
class Circle:public Point
{
public:
//构造函数
Circle(float tx, float ty, float tr):Point(tx,ty),r(tr){}
//功能函数声明
void setR(float tr);
float getR();
float getArea();
//重载输出函数 做友元 输出圆信息
friend ostream & operator<<(ostream &output, Circle &c);
protected:
float r;
};
/*************派生类Circle 功能函数定义**************/
void Circle::setR(float tr){
r = tr;
}
float Circle::getR(){
return r;
}
float Circle::getArea(){
return 3.14*r*r;
}
//重载输出函数 输出点圆的信息
ostream & operator<<(ostream &output, Circle &c)
{
output << "Center: (" << + c.getPX() << ", " << c.getPY() << ") , r: " << c.getR() << " area= " << c.getArea();
return output;
}
测试程序:
int main()
{
Point p1(1,1); //实例化一个点(2,2)
Circle c1(1, 1, 2); //实例化圆, 圆心(1,1) r=2
cout << c1 << endl;
c1.setPoint(2,2); //修改点坐标为(2,2);
c1.setR(4); //修改圆半径为 4
Point &pRef = c1; //基类点引用
//输出圆中点的信息
cout << "pRef: " << pRef << endl; //(2, 2)
return 0;
}
程序分析: 输出结果: (3)声明Circle的派生类Cylinder 测试函数 程序分析: 输出结果: cy1 Info: Center: (1, 1) , r: 2 h: 4 在上述程序中,Circle和Cylinder类中都有getArea这个函数,这两个函数不仅名字相同,而且参数个数相同,但功能不同,函数体是不同的。这是合法的,因为它们不在同一个类中。编译系统按照同名覆盖的原则决定调用的对象。 程序运行结果: num: 1001 程序分析: 这样就把Student类的display 函数声明为虚函数。程序其他部分都不改动,再编译和运行就行正常输出Graduate类的数据。 虚函数的使用方法: 程序分析: 最好把基类的析构函数声明为虚函数,这将使所有派生类的析构函数自动成为虚函数。 在第一个程序:一个典型的例子中,基类Point中没有求面积的getArea函数因为“点”是没有面积的,也就是说基类本身不需要这个函数。所以在Point类中没有定义getArea函数。但是,在其直接派生类Circle 和间接派生类Cylinder 中都需要有getArea 函数,而且这两个函数的功能不同,一个是求圆面积,另一个是求圆柱体表面积。我们自然会想到,在这种情况下应当将getArea声明为虚函数。可以在基类Point中加一个getArea函数并声明为虑函数: 其返回值为0,表示“点”是没有面积的。其实,在基类中并不使用这个函数。为了简化,可以不要写出这种无意义的函数体。只给出函数的原型,并在后面加上“=0”,如 这就将getArea声明为一个纯虚函数。纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是: 纯虚函数的作用: 如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来生成对象,目的是用它作为基类去建立派生类。 抽象类的派生类需要对基类中的纯虚函数进行定义,这个派生类就是可以用来定义对象的具体类。 虽然抽象类不能定义对象,但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。 (1)声明抽象基类Shape (2)声明Point类 Point从Shape 继承了3个成员函数,由于“点”是没有面积和体积的因此不必重新定义getArea和getVolume,但Point类仍然从Shape类继承了这两个函数以便其派生类继承它们。shapeName函数在Shape类中是纯虚函数在Point类中要进行定义。Point类还有自己的成员函数(setPoint, getX,getY)和数据成员(x和y)。 (3)声明Circle类 (4)同理,声明Cylinder类 (5)main函数 程序分析: Point: (3.2, 4.5) Point: x=3.2, y=4.5 Circle:x=2.4, y=1.2 Cylinder:x=3.5, y=6.4
(1)在Point类中声明了一次运算符“<<”重载函数,在Circle类中又声明了一次运算符“<<”,两次重载的运算符“<<”内容是不同的,在编译时编译系统会根据输出项的类型确定调用哪一个运算符重载函数。
(2)注意main函数中的第8行
Point &pRef = c1;
定义了Point类的引用pRef,并用派生类Circle对象c1对其初始化。在继承与派生中提到,派生类对象可以替代基类对象向基类对象的引用初始化或赋值。
pRef 不能认为是c的别名,它只是c1 中基类部分的别名,得到了c1的起始地址,与c1中基类部分共享同一段存储单元。所以用“cout <
//圆柱体类
class Cylinder:public Circle
{
public:
//圆柱构造函数
Cylinder(float tx, float ty, float tr, float th): Circle(tx, ty, tr),h(th){}
//功能函数声明
void setH(float th);
float getH();
float getArea();
float getVolume();
//重载输出函数 做友元 输出圆柱体信息
friend ostream & operator<<(ostream &output, Cylinder &c);
protected:
float h;
};
/*************派生圆柱体类功能函数实现*************/
void Cylinder::setH(float th){
h = th;
}
float Cylinder::getH(){
return h;
}
float Cylinder::getArea(){
return 2*Circle::getArea() + 2*3.14*r*h;
}
float Cylinder::getVolume()
{
return (Circle::getArea() * h);
}
//重载运算符函数
ostream & operator<<(ostream &output, Cylinder &c)
{
output << "Center: (" << + c.getPX() << ", " << c.getPY() << ") , r: " << c.getR() << " h: " << c.getH();
output << "\narea= " << c.getArea() << "\t volume= " << c.getVolume();
return output;
}
int main()
{
Point p1(1,1); //实例化一个点(1,1)
Circle c1(1, 1, 2); //实例化圆, 圆心(1,1) r=2
Cylinder cy1(1, 1, 2, 4); //实例化圆柱,底面圆心(1,1) 半径 2 高度 4
cout << "cy1 Info: " << cy1<< endl;
cy1.setPoint(2,2); //修改圆柱底面圆心为(2,2)
cy1.setR(3); //修改圆柱底面半径为 3
cy1.setH(5); //修改圆柱高为 5
cout << "new cylinder Info: " << cy1 << endl;
Point &pRef=cy1; //定义基类点的引用 输出圆柱中Point类的信息
cout << "pRef as point: " << pRef << endl;
Circle &cRef=cy1; //定义派生类 圆的引用 输出圆柱中Circle类的信息
cout << "cRef as point: " << cRef;
return 0;
}
area= 75.36 volume= 50.24
new cylinder Info: Center: (2, 2) , r: 3 h: 5
area= 150.72 volume= 141.3
pRef as point: (2, 2)
cRef as point: Center: (2, 2) , r: 3 area= 28.26
利用虚函数实现动态多态性
虚函数的作用
通过指针调用,同一个语句pt->display();,可以调用不同派生层次中的 display 函数,只须在调用前临时给指针变量 pt 赋予不同的值(使之指向不同的类对象)即可。
典例: 声明两个类Student和Graduate有同样的方法display输出信息,通过指针访问不同类,输出不同信息。
这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况。#include
name: zhangsan
score: 80
num: 1002
name: lisi
score: 95
pt为Student类型的指针,即使指向grad1,也只能访问grad1基类数据,无法访问Graduate成员数据。
用虚函数就能顺利地解决这个问题。下面对程序作一点修改,在 Student 类中声明display函数时,在最左面加一个关键字virtual,即virtual void display();
用同一种调用形式pt->display(),而且pt是同一个基类指针,可以调用同一类族中不同类的虚函数。这就是多态性,对同一消息,不同对象有不同的响应方式。
虚析构函数
#include
pt是指向基类的指针变量,指向new开辟的动态存储空间,因此程序运行只执行基类的析构函数,如果希望能执行派生类 circle 的析构函数,需要将基类的析构函数声明为虚析构函数,如:virtual ~Point();
纯虚函数与抽象类
virtual float getArea() const{
return 0;
}
virtual float getArea() const = 0;
virtual 函数类型 函数名 (参数表列) = 0;
注意:
在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。 抽象类
应用实例
在一个典型例子中介绍了以Point 为基类的“点一圆一圆柱体”类的层次结构。现在程序中使用虚函数和抽象基类,并且类的层次结构的顶层是抽象基类 Shape(形状) Point(点)Circle(圆),Cylinder(圆柱体)都是 Shape 类的直接派生类和间接派生类。
//声明抽象基类Shape
class Shape
{
public:
virtual float getArea() const {return 0.0;} //虚函数 求面积
virtual float getVolume() const {return 0.0;} //虚函数
virtual void shapeName() const = 0; //纯虚函数
};
函数具体自定义实现参考上述一个典型例子的程序,该程序主要在原程序上进行对虚函数的重新定义//声明Point类
class Point:public Shape
{
public:
//函数具体自定义实现参考上述一个典型例子程序
//构造函数
Point(float tx, float ty):x(tx),y(ty){}
//功能函数声明
void setPoint(float tx, float ty);
float getPX () const {return x;};
float getPY () const {return y;};
//对虚函数进行重新定义
virtual void shapeName() const {cout <<"Point: ";}
//重载输出函数 做友元 输出点坐标
friend ostream & operator<<(ostream &output, const Point &pt);
protected:
float x,y;
};
/*************Point 类函数定义 **************/
void Point::setPoint(float tx, float ty){
x = tx;
y = ty;
}
//重载输出函数 输出点坐标
ostream & operator<<(ostream &output, const Point &pt)
{
output << "(" << + pt.x<< ", " << pt.y << ")";
return output;
}
函数具体自定义实现参考上述一个典型例子的程序,该程序主要在原程序上进行对getArea和shapeName虚函数的重新定义//构造圆类
class Circle:public Point
{
public:
//构造函数
Circle(float tx, float ty, float tr):Point(tx,ty),r(tr){}
//功能函数声明
void setR(float tr); //设定半径
float getR() const; //获取半径的值
//对虚函数进行再定义
virtual float getArea() const;
virtual void shapeName() const {cout <<"Circle:";}
//重载输出函数 做友元 输出圆信息
friend ostream & operator<<(ostream &output, const Circle &c);
protected:
float r;
};
/*************派生类Circle 功能函数定义**************/
void Circle::setR(float tr){
r = tr;
}
float Circle::getR() const{
return r;
}
float Circle::getArea() const{
return 3.14*r*r;
}
//重载输出函数 输出点圆的信息
ostream & operator<<(ostream &output, const Circle &c)
{
output << "(" << + c.x << ", " << c.y<< ") , r: " << c.r;
return output;
}
//圆柱体类
class Cylinder:public Circle
{
public:
//圆柱构造函数
Cylinder(float tx, float ty, float tr, float th): Circle(tx, ty, tr),h(th){}
//功能函数声明
void setH(float th);
float getH();
//虚函数重定义
virtual float getArea() const; //重载虚函数
virtual float getVolume() const;
virtual void shapeName() const {cout <<"Cylinder:";}
//重载输出函数 做友元 输出圆柱体信息
friend ostream & operator<<(ostream &output, const Cylinder &c);
protected:
float h;
};
/*************派生圆柱体类功能函数实现*************/
void Cylinder::setH(float th){
h = th;
}
float Cylinder::getH(){
return h;
}
float Cylinder::getArea() const{
return 2*Circle::getArea() + 2*3.14*r*h;
}
float Cylinder::getVolume() const{
return (Circle::getArea() * h);
}
//重载运算符函数
ostream & operator<<(ostream &output, const Cylinder &c)
{
output << "(" << + c.x << ", " << c.y << ") , r: " << c.r << " h: " << c.h;
return output;
}
int main()
{
Point p1(3.2,4.5); //实例化一个点(3.2,4.5)
Circle c1(2.4, 1.2, 5.6); //实例化圆, 圆心(2.4, 1.2) r = 5.6
Cylinder cy1(3.5, 6.4, 5.2, 10.5); //实例化圆柱,底面圆心(3.5, 6.4) 半径 5.2 高度 10.5
p1.shapeName(); //用对象名建立静态关联
cout << p1 << endl; //输出点的信息
c1.shapeName();
cout << c1 << endl;
cy1.shapeName();
cout << cy1 << endl << endl;
Shape *pt; //定义基类指针
pt = &p1; //使指针指向point对象
pt->shapeName(); //用指针建立动态关联
cout << "x=" << p1.getPX() << ", y=" << p1.getPY() << endl;
cout << "area=" << pt->getArea() << "\nvolume=" << pt->getVolume() <<endl<< endl << endl;
pt = &c1; //使指针指向point对象
pt->shapeName(); //用指针建立动态关联
cout << "x=" << c1.getPX() << ", y=" << c1.getPY() << endl;
cout << "area=" << pt->getArea() << "\nvolume=" << pt->getVolume() <<endl<< endl << endl;
pt = &cy1; //使指针指向point对象
pt->shapeName(); //用指针建立动态关联
cout << "x=" << cy1.getPX() << ", y=" << cy1.getPY() << endl;
cout << "area=" << pt->getArea() << "\nvolume=" << pt->getVolume() <<endl<< endl << endl;
return 0;
}
首先先分别定义了Point类对象跑,Circle类对象从和Cylinder 类对象cy1。然后分别通过对象名p1,c1和cy1调用了shapeName 函数,这属于静态关联。
后续定义一个指向基类Shape对象的指针变量pt,使它先后指向3个派生类对象point,Circle和cylinder,然后通过指针调用各函数,如pt->shapeName(),pt ->getArea()等。这时是通过动态关联分别确定应该调用哪个函数,分别输出不同类对象的信息。
结果输出:
Circle:(2.4, 1.2) , r: 5.6
Cylinder:(3.5, 6.4) , r: 5.2 h: 10.5
area=0
volume=0
area=98.4704
volume=0
area=512.699
volume=891.509