1.11 多态性与虚函数
1.11.1 多态性的概念
多态的意思是指一个事物有多种形态。C++中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象时,多态性为:向不同的对象发送同一个消息,不同的对象会在接收时产生不同的行为。函数的重载和运算符的重载都是多态现象。
多态性分为2类:静态多态性和动态多态性。函数重载和运算符重载都属于静态多态性。静态多样性的意思是:在程序编译时就能决定调用哪个函数。静态多态性是通过函数的重载实现的。动态多态性是在程序运行过程中才动态确定操作指针所针对的对象。动态多态性是通过虚函数实现的。
当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行期间调用类对象的成员,会产生二义性问题。
例 1.11.1建立一Point类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,其中增加了数据成员半径r,再以Circle类为直接基类,派生出一个圆柱体类Cylinder,其中增加数据成员高度h。要求编写程序,重载运算符“<<”和“>>”,使之能用于输出以上类对象。
#include<iostream>
using namespace std;
class Point
{
public:
Point(float x=0,float y=0);//有默认参数的构造函数
void setpoint(float,float);//设置坐标值
float getX() const {return x;}//常成员函数可以引用本类中的数据成员,但不能修改他们
float getY() const {return y;}
friend ostream & operator << (ostream &,const Point &);重载输出运算符
protected:
float x,y;
};
Point::Point(float a,float b)//Point的构造函数
{x=a;y=b;}
void setpoint(float a,float b)
{x=a;y=b;}
//重载运算符<<,使其可以输出点的坐标
ostream &operator << (ostream &output, const Point &p)
{
output<<”[“<<p.x<<”.”<<p.y<<”]”<<endl;
return output;
}
class Circle: public Point //Circle是Point类的公用派生类
{
public:
Circle(float x=0,float y=0,float r=0);//声明构造函数
void serRadius(float);//设置半径
float getRadius() const;//读取半径
float area() const;//计算圆面积
friend ostream &operator << (ostream &, const Circle &);
protected:
float radius;
};
Circle::Circle(float a,float b,float r):Point(a,b)//定义构造函数,对圆心坐标和半径进行初始化
{radius=r;}
void Cirecle::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;
};
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;//读取圆柱高度
float 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;
//Cylinder类中也定义了area函数,发生同名覆盖
cy1.setHeight(15);
cy1.setRadius(7.5);
cy1.setPoint(5,5);
cout<<”\nnew cylinder: \n”<<cy1;//用重载运算符<<输出cy1的数据
Point &Ref=cy1;//pRef是Point类对象的引用
cout<<”\npRef as a Point:”<<pRef;//pRef作为一个点输出,只是基类部分(Point)的别名
Circle &cRef=cy1; //cRef是Circle类对象的引用
cout<<”\ncRef as a Circle:”<<cRef;//cRef作为一个圆输出,只是基类部分(Circle)的别名
return 0;
}
1.11.2 虚函数
一、虚函数的作用
在类的继承层次结构中,不同的层次中可以出现名字相同,参数个数和类型都相同而功能不同的函数。如例1.11.1中Circle类的area函数和Circle派生类Cylinder中的area函数,它们名字相同,参数个数均为0,但是功能不同。会发生同名覆盖。能否调用同一个形式,既能调用派生类又能调用基类的同名函数。C++提供了虚函数来解决上述中的同名覆盖问题。
简单地说,那些被关键字virtual声明的成员函数就是虚函数。虚函数的作用是允许在派生类中重新定义与基类同名的的函数,并可以通过积累指针或引用来访问基类和派生类中的同名函数。
虚函数形式:
在基类声明函数时,在最左侧加一个关键字virtual。且当一个成员函数被声明为虚函数时,其派生类的同名成员函数自动成为虚函数。
Eg:
class Student
{public:
virtual void display();//在声明基类时,将成员函数声明为虚函数
};
class Graduate: public Student
{
public:
void display();
};
int main()
{
Student stud1;
Graduate grad1;
Student *p;//p是一个基类指针,可以调用同一类族中不同类的虚函数
p=&stud1;
(*p).display();
p=&grad1;
(*p).display();
}
用同一个基类指针变量,可以调用同一组类中的不同虚函数。本来基类指针是用来指向派生类对象中的基类部分的,无法通过基类指针调用派生类对象的中的成员函数的。虚基类突破了这一限制,在派生类的基类部分中,派生类的虚函数取代了基类原来的虚函数,因此当积累指针指向派生类对象后,调用虚函数时就调用了派生类的虚函数。虚函数的使用方法为:
① 在基类中用virtual声明成员函数为虚函数,这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便的调用。
② 在派生类中重新定义此函数,要求函数名,函数类型,函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
③ 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
④ 通过指针变量调用此虚函数,此时调用的就是指针变量所指向的对象的同名函数。
通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数。
1.11.3 静态关联与动态关联
确定调用的具体对象的过程称为关联,在这里指的是把一个函数名与一个类对象均绑在一起。函数重载和通过对象名调用的虚函数在编译时即可确定其调用的虚函数属于那一个类,其过程称为静态关联。如果是通过指针调用虚函数,则是在运行阶段把把虚函数和类对象捆绑在一起,因此称为动态关联。
注意:①只能用virtual声明类的成员函数,使它成为虚函数;②一个成员函数被声明为虚函数后,在同一类族中不能出现与之具有相同参数和返回值的同名函数。
1.11.4 纯虚函数与抽象类
一、纯虚函数
在例1.11.1中,Point类中没有area函数,但是Circle和Cylinder类中都有area函数,此时应当将area声明为虚函数,以避免二义性。。如果在Point类中加一个area函数并将其声明为虚函数的话:
virtual float area() const {return 0;}
简化为:
virtual float area() const =0;
这就将area声明为一个纯虚函数。纯虚函数是在声明虚函数时被初始化为0的函数。其一般形式为:
virtual 函数类型 函数名(参数列表) =0;
纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。需要注意的是:纯虚函数没有函数体;最后面的”=0”不表示返回0,只是起提示这是纯虚函数的作用;纯虚函数不具备函数功能,不能被调用。包含纯虚函数的类是无法建立对象的。
二、抽象类
这种不用来定义对象而只作为一种基本类型用于继承的类,成为抽象类。由于它常用作基类,又称为抽象基类。凡是包含纯虚函数的类都是抽象类。