虚函数和多态
一.运算符重载
不能重载的运算符: 成员运算符“.”
作用域运算符“::”
条件运算符“?:”
指针运算符 *
编译预处理命令的开始符号 #
运算符重载的两种形式:成员函数或者友元函数
在C++程序设计中,多态性(polymorphism)是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的: 向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。
从系统实现的角度看,多态性分为两类: 静态多态性和动态多态性。函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。动态多态性是在程序运行过程中才动态地确定操作所针对的对象,又称运行时的多态性,动态多态性是通过虚函数(virtual function)实现的。多态性是“一个接口,多种方法”。
函数重载:参数类型不同,或参数个数不同,才能构成重载,只有函数的返回类型不同是不能构成重载的。另外,还要注意函数重载和默认参数可能产生的二义性。
1、静态多态性-函数的重载
举个例子
#include <iostream>
#include <string>
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)
{
x=a;
y=b;
}
void Point::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
{
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.1415926*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
{
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*Circle::getRadius()*Circle::getRadius();}
float Cylinder::volume() const
{return Circle::area()*height;}
ostream& operator<<(ostream& output,const Cylinder& cy)
{
output<<"Center=["<<cy.x<<","<<cy.y<<"],r="<<cy.Circle::getRadius()
<<",h="<<cy.height<<"\narea="<<cy.area()<<",volume="<<cy.volume()<<endl;
return output;
}
int main()
{
Cylinder cy1(3.5,6.4,5.2,10);
cout<<"\noriginal cylinder:\nx="<<cy1.getX( )<<",
y= "<<cy1.getY( )<<", r="<<cy1.getRadius( )<<",
h="<<cy1.getHeight( )<<"\narea="<<cy1.area()<<",volume="<<cy1.volume()<<endl;
cy1.setHeight(15);
cy1.setRadius(7.5);
cy1.setPoint(5,5);
cout<<"new cylinder:\n"<<cy1;
Point& pRef=cy1;
cout<<"\npRef as a Point:"<<pRef;
Circle& cRef=cy1;
cout<<"\ncRef as a Circle:"<<cRef;
return 1;
}
2、动态多态性-虚函数
1)相关概念
在类的继承层次结构中,在不同的层次中可以出现名字相同、参数个数和类型都相同而功能不同的函数。编译系统按照同名覆盖的原则决定调用的对象。
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
由虚函数实现的动态多态性就是: 同一类族中不同类的对象,对同一函数调用作出不同的响应。虚函数的使用方法是:
(1) 在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能。在类外定义虚函数时,不必再加virtual。
(2) 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。派生类重新声明该虚函数时,可以加virtual,也可以不加,习惯上在每一层声明该函数时都加virtual,使程序更清晰。
如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
(3) 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
(4) 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
有时在基类中定义的非虚函数会在派生类中被重新定义,如果用基类指针调用该成员函数,则系统会调用对象中基类部分的成员函数;如果用派生类指针调用该成员函数,则系统会调用派生类对象中的成员函数,这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。
函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者是横向重载,后者可以理解为纵向重载。但与重载不同的是: 同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)。
确定调用的具体对象的过程称为关联(binding)。此指把一个函数名与一个类对象捆绑在一起,建立关联。一般地说,关联指把一个标识符和一个存储地址联系起来。
函数重载和通过对象名调用的虚函数,在编译时即可确定其调用的虚函数属于哪一个类,其过程称为静态关联(static binding),由于是在运行前进行关联的,故又称为早期关联(early binding)。
在运行阶段把虚函数和类对象“绑定”在一起的,此过程称为动态关联(dynamic binding)。这种多态性是动态的多态性,即运行阶段的多态性。由于动态关联是在编译以后的运行阶段进行的,因此也称为滞后关联(late binding)。
注意:
(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义,它只能用于类的继承层次结构中。
(2) 一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。
什么情况下声明为虚函数呢?
(1) 首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。
(2) 应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。
(3)有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。
使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtual function table,简称vtable),它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联时的时间开销很少,因此多态性是高效的。
在定义了virtual虚函数后,当传来子类的指针给基类指针时,在调用成员函数时,如果子类有该成员函数,则优先调用子类成员函数(覆盖),如果子类没有该函数,才调用父类的。
2)虚析构函数
如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,系统会只执行基类的析构函数,而不执行派生类的析构函数。
解决的方法是将基类的析构函数声明为虚函数,这样由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。
构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。
3)纯虚函数
纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是
virtual 函数类型 函数名 (参数表列) =0;
virtual float area( ) const =0;//纯虚函数
注意: ①纯虚函数没有函数体;②最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”; ③这是一个声明语句,最后应有分号。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,不能用来定义对象。如果在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用,这个派生类就不是抽象类,而是可以用来定义对象的具体类(concrete class)。凡是包含纯虚函数的类都是抽象类(因为纯虚函数是不能被调用的),包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。
虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。
不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类,通常称为抽象基类(abstract base class)。
#include <iostream>
#include <string>
using namespace std;
class Shape
{
public:
virtual float area() const {return 0.0;}
virtual float volume() const {return 0.0;} //虚函数
virtual void shapeName() const=0; //纯虚函数
};
class Point:public Shape
{
public:
Point(float=0,float=0);
void setPoint(float,float);
float getX( ) const {return x;}
float getY( ) const {return y;}
virtual void shapeName() const {cout<<"Point:";}
friend ostream& operator<<(ostream&,const Point&);
protected:
float x,y;
};
Point::Point(float a,float b)
{x=a;y=b;}
void Point::setPoint(float a,float b)
{x=a;y=b;}
ostream& operator<<(ostream& output,const Point& p)
{
output<<"["<<p.x<<","<<p.y<<"]";
return output;
}
class Circle:public Point
{
public:
Circle(float x=0,float y=0,float r=0);
void setRadius(float);
float getRadius( ) const;
virtual float area( ) const;
virtual void shapeName() const {cout<<"Circle:";}//对虚函数进行再定义
friend ostream& operator<<(ostream&,const Circle&);
protected:
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<<"["<<c.x<<","<<c.y<<"], r="<<c.radius;
return output;
}
class Cylinder:public Circle
{
public:
Cylinder (float x=0,float y=0,float r=0,float h=0);
void setHeight(float);
virtual float area( ) const;
virtual float volume( ) const;
virtual void shapeName( ) const {cout<<"Cylinder:";}//对虚函数进行再定义
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::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<<"["<<cy.x<<","<<cy.y<<"], r="<<cy.radius<<",
h="<<cy.height;
return output;
}
int main( )
{Point point(3.2,4.5);//建立Point类对象point
Circle circle(2.4,1.2,5.6); //建立Circle类对象circle
Cylinder cylinder(3.5,6.4,5.2,10.5); //建立Cylinder类对象cylinder
point.shapeName(); //静态关联
cout<<point<<endl;
circle.shapeName(); //静态关联
cout<<circle<<endl;
cylinder.shapeName(); //静态关联
cout<<cylinder<<endl<<endl;
Shape *pt; //定义基类指针
pt=&point; //指针指向Point类对象
pt->shapeName( ); //动态关联
cout<<"x="<<point.getX( )<<",y="<<point.getY( )<<"\narea="<<pt->area( )<<"\nvolume="<<pt->volume()<<"\n\n";
pt=&circle; //指针指向Circle类对象
cout<<"x="<<circle.getX( )<<",y="<<circle.getY( )<<"\narea="<<pt->area( )<<"\nvolume="<<pt->volume( )<<"\n\n";
pt=&cylinder; //指针指向Cylinder类对象
pt->shapeName( ); //动态关联
cout<<"x="<<cylinder.getX( )<<",y="<<cylinder.getY( )<<"\narea="<<pt->area( )<<"\nvolume="<<pt->volume( )<<"\n\n";
return 0;
}