多态性与虚函数
面向对象理论中的3个术语:对象、方法和消息。对象(object):不言而喻,它是构成系统的基本单位,有属性和行为两个要素,在C++中,每个对象都是由数据和函数这两部分组成的,数据即是对象的属性,行为称之为方法(method),方法是对数据的操作,通常由函数实现。调用对象中的函数就是向该对象传送一个消息(message),所谓“消息”,其实就是一个命令。例如:
stud.display();
就是向对象stud发出的一个“消息”,通知它执行其中的display“方法”(即display函数)。即:stud是对象,display()是方法,语句“stud.display();”是消息。
1.多态性(polymorphism)
多态性定义:由继承而产生的相关的不同的类,向其对象发送同一个消息,不同的对象接收到后会产生不同的行为(即方法)。
多态性分为两类:静态多态性和动态多态性。函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性有称为编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。动态多态性是在程序运行过程中才动态地确定操作所针对的对象,故称之为运行时的多态性。动态多态性是通过虚函数实现的。
关于静态多态性和动态多态性,请看下面的例子:
定义3个类:点、圆和圆柱
- #include <iostream.h>
- //定义Point基类
- class Point
- {
- public:
- Point(float=0, float=0);
- void display();
- friend ostream & operator <<(ostream &, const Point &);
- protected:
- float x, y;
- };
- Point::Point(float a, float b)
- {
- x=a; y=b;
- }
- ostream & operator <<(ostream &output, const Point &p)
- {
- output<<"["<<p.x<<","<<p.y<<"]"<<endl;
- return output;
- }
- void Point::display()
- {
- cout<<"["<<x<<","<<y<<"]"<<endl;
- }
- //定义Circle基类
- class Circle: public Point
- {
- public:
- Circle(float=0, float=0, float=0);
- float area ( ) const;
- void display();
- friend ostream & operator <<(ostream &, const Circle &);
- protected:
- float radius;
- };
- Circle::Circle(float a,float b,float r):Point(a,b),radius(r){ }
- 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;
- }
- void Circle::display()
- {
- cout<<"Center=["<<x<<","<<y<<"], r="<<radius<<", area="<<area( )<<endl;
- }
- //定义圆柱体类
- class Cylinder: public Circle
- {
- public:
- Cylinder (float=0,float=0,float=0,float=0);
- float area() const;//计算圆表面积,和Circle类中的area重名
- float volume() const;
- void display();
- 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){}
- 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;
- }
- void Cylinder::display()
- {
- cout<<"Center=["<<x<<","<<y<<"], r="<<radius<<", h="
- <<height<<"\narea="<<area( )<<", volume="<<volume( )<<endl;
- }
#include <iostream.h>
//定义Point基类
class Point
{
public:
Point(float=0, float=0);
void display();
friend ostream & operator <<(ostream &, const Point &);
protected:
float x, y;
};
Point::Point(float a, float b)
{
x=a; y=b;
}
ostream & operator <<(ostream &output, const Point &p)
{
output<<"["<<p.x<<","<<p.y<<"]"<<endl;
return output;
}
void Point::display()
{
cout<<"["<<x<<","<<y<<"]"<<endl;
}
//定义Circle基类
class Circle: public Point
{
public:
Circle(float=0, float=0, float=0);
float area ( ) const;
void display();
friend ostream & operator <<(ostream &, const Circle &);
protected:
float radius;
};
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){ }
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;
}
void Circle::display()
{
cout<<"Center=["<<x<<","<<y<<"], r="<<radius<<", area="<<area( )<<endl;
}
//定义圆柱体类
class Cylinder: public Circle
{
public:
Cylinder (float=0,float=0,float=0,float=0);
float area() const;//计算圆表面积,和Circle类中的area重名
float volume() const;
void display();
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){}
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;
}
void Cylinder::display()
{
cout<<"Center=["<<x<<","<<y<<"], r="<<radius<<", h="
<<height<<"\narea="<<area( )<<", volume="<<volume( )<<endl;
}
主函数(1),静态关联
- int main()
- {
- Cylinder cy1;
- cout<<"A Cylinder:\n"<<cy1; //用重载运算符“<<”输出cy1的数据
- Point &p=cy1; //将圆柱cy1赋值给点,p是Point类对象的引用变量
- cout<<"\np as a Point:\n"<<p; //p作为一个“点”输出
- Circle &c=cy1; //将圆柱cy1赋值给圆,c是Circle类对象的引用变量
- cout<<"\nc as a Circle:\n"<<c<<endl; //c作为一个“圆”输出
- return 0;
- }
int main()
{
Cylinder cy1;
cout<<"A Cylinder:\n"<<cy1; //用重载运算符“<<”输出cy1的数据
Point &p=cy1; //将圆柱cy1赋值给点,p是Point类对象的引用变量
cout<<"\np as a Point:\n"<<p; //p作为一个“点”输出
Circle &c=cy1; //将圆柱cy1赋值给圆,c是Circle类对象的引用变量
cout<<"\nc as a Circle:\n"<<c<<endl; //c作为一个“圆”输出
return 0;
}
由该主函数可知:1. 圆柱对象cy1可以直接赋值给其基类的对象;2. Circle类和Cylinder类中都有一个area( )函数,之所以在Cylinder中用area( )能直接调用Cylinder::area( )而没有调用Circle:: area( )是因为“同名覆盖”的缘故,默认Cylinder中的area( )覆盖了基类中的area( )函数(如果不想覆盖,可以用纯虚函数,能够对基类函数重新定义,但是哪个效果更高还不好说)。3. 三个类中都包含了同名的重载运算符“<<”函数,但是他们的第二个参数类型互不相同,所以不能看做是同名覆盖,实际上,是属于静态关联。“<<”运算符之所以能准确地调用不同类中的重载函数,是因为系统在编译时就已经确定了调用对象。
主函数(2),动态关联:
- int main( )
- {
- Point p1(9,9);
- Circle c1(6,6,8);
- Cylinder cy1(5,5,15,7.5);
- Point *pt=&p1;
- pt->display();
- pt=&c1;
- pt->display();
- pt=&cy1;
- pt->display();
- return 0;
- }
int main( )
{
Point p1(9,9);
Circle c1(6,6,8);
Cylinder cy1(5,5,15,7.5);
Point *pt=&p1;
pt->display();
pt=&c1;
pt->display();
pt=&cy1;
pt->display();
return 0;
}
首先应该明确一点:定义为指向Point基类对象的指针,当改变方向,指向派生类对象后,它仅指向派生类对象中基类的部分对象(例如当pt=&c1后,调用pt->display()相当于调用pt->Point::display()),所以上面调用的display()函数只能输出基类对象的值(即:定义为Point类型的指针pt根本就无法指向派生类增加的数据或函数,例如pt-> Circle::display()就会出错,提示“'Circle' : is not a memberof 'Point'”)。
要想pt能指向Circle::display(),就必须用虚成员函数来实现,即把基类中的display()函数声明为virtual类型。基类中的display()函数声明为了virtual类型,代表了它可以在派生类中被重新定义,为它赋予新功能(所以可以基类中的虚函数的函数体可以为空,或者写成纯虚函数的形式),注意是“重新定义”而不是“共存”,即此时Circle中定义的display()函数不再看做是增加的部分,而是看做基类的部分,所以直接用pt->display()或者pt->Point::display()就可以调用Circle类中定义的display()函数了,写成pt-> Circle::display()反而会出错。
2. 虚函数
上面已经说了,C++的动态多态性是通过虚函数来实现的。“虚成员函数”简称“虚函数”,C++不允许在类外声明虚函数。“虚函数允许派生类取代基类所提供的实现。编译器确保当对象为派生类时,取代者(译注:即派生类的实现)总是被调用,即使对象是使用基类指针访问而不是派生类的指针。”
上面的例子,写成虚函数的形式:
- class
- {
- …
- virtual void display(){} //声明为空的虚函数
- }
- int mai()
- {
- Point p1(9,9);
- Circle c1(6,6,8);
- Cylinder cy1(5,5,15,7.5);
- Point *pt=&p1;
- pt->display(); //错误,因为Point类中的display()被定义为空,没有输出功能
- pt=&c1;
- pt->display(); //直接调用就可以输出圆的内容了
- ……
- }
class
{
…
virtual void display(){} //声明为空的虚函数
}
int mai()
{
Point p1(9,9);
Circle c1(6,6,8);
Cylinder cy1(5,5,15,7.5);
Point *pt=&p1;
pt->display(); //错误,因为Point类中的display()被定义为空,没有输出功能
pt=&c1;
pt->display(); //直接调用就可以输出圆的内容了
……
}
也可以写成纯虚函数的形式:
- class
- {
- …
- virtual void display() =0; //声明为纯虚函数
- }
- int mai()
- {
- Point p1(9,9); //错误,包含纯虚函数的类被成为抽象类,不能被初始化
- Circle c1(6,6,8);
- Cylinder cy1(5,5,15,7.5);
- Point *pt=&p1;
- pt->display();
- pt=&c1;
- pt->display(); //直接调用就可以了
- ……
- }
class
{
…
virtual void display() =0; //声明为纯虚函数
}
int mai()
{
Point p1(9,9); //错误,包含纯虚函数的类被成为抽象类,不能被初始化
Circle c1(6,6,8);
Cylinder cy1(5,5,15,7.5);
Point *pt=&p1;
pt->display();
pt=&c1;
pt->display(); //直接调用就可以了
……
}
因为纯虚函数“徒有其名,而无其实”,所以包含纯虚函数的类都只作为基类,相当于提供一种基本的类型,它的不能被初始化,这种类被称为“抽象基类”,它总是被调用的。
例如,可以给点、圆和圆柱体定义一个抽象基类Shape(形状):
- class Shape
- {
- public:
- virtual float area() const { return 0.0; } //虚函数
- virtual float volume() const { return 0.0; } //虚函数
- virtual void shapeName() const =0; //纯虚函数
- };
class Shape
{
public:
virtual float area() const { return 0.0; } //虚函数
virtual float volume() const { return 0.0; } //虚函数
virtual void shapeName() const =0; //纯虚函数
};
3. 虚析构函数
如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。例如:
- #include <iostream.h>
- //定义Point基类
- class Point
- {
- public:
- Point( ){ }; //定义构造函数
- ~Point() { cout<<"Point OK!"<<endl; } //析构函数
- };
- class Circle: public Point
- {
- public:
- Circle( ){ };
- ~Circle() { cout<<"Circle OK!"<<endl; }
- protected:
- float radius;
- };
- int main( )
- {
- Point *p=new Circle;
- delete p;
- return 0;
- }
#include <iostream.h>
//定义Point基类
class Point
{
public:
Point( ){ }; //定义构造函数
~Point() { cout<<"Point OK!"<<endl; } //析构函数
};
class Circle: public Point
{
public:
Circle( ){ };
~Circle() { cout<<"Circle OK!"<<endl; }
protected:
float radius;
};
int main( )
{
Point *p=new Circle;
delete p;
return 0;
}
希望用delete释放p所指的空间,但运行结果却为:
Point OK!
表示只执行了基类Piont的析构函数,而没有执行派生类Circle的析构函数。如果希望能执行派生类中的析构函数,可以将基类的析构函数声明为虚函数,如
virtual ~ Point() { cout<<"PointOK!"<<endl; }
如果将基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也自动成为虚函数,即使它们名字不同。可见原理和格式与上面所说的虚函数是一样的。运行结果为:
Point OK!
Circle OK!
专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的析构函数,以保证在撤销动态存储空间时能得到正确的处理。