目录
什么是多态性
向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)
从系统实现的角度来看,总共分为两类:静态多态性和动态多态性
静态多态性:函数重载,运算符重载(实质上也是函数重载)
动态多态性:不在编译时确定调用哪个函数,而是在程序运行过程中才动态地确定操作所针对的对象
一个典型的例子
//Point 声明文件
using namespace std;
class Point
{
public:
Point(float ,float);
void setPoint(float ,float);
float getX() const;
float getY() const;
friend ostream& operator<<(ostream&,const Point& );
protected:
float x,y;//这里使用了protected,使得子类可以使用父类的数据成员
};
//Point 实现文件
#include<iostream>
#include"point.h"
using namespace std;
//float x,y;
Point::Point(float a,float b):x(a),y(b){}
void Point::setPoint(float a,float b)
{
this->x = a;
this->y = b;
}
float Point::getX() const
{
return this->x;
}
float Point::getY() const
{
return this->y;
}
ostream &operator<<(ostream& output,const Point& point)
{
output<<"点("<<point.x<<","<<point.y<<")"<<endl;;
return output;
}
//Circle声明文件
#include"point.h"
using namespace std;
class Circle:public Point
{
public:
Circle(float = 0,float = 0,float = 0);
void setRadius(float );
float getRadius() const;
float area() const;
friend ostream& operator<<(ostream&,const Circle&);
protected:
float radius;
};
//Circle实现文件
#include<iostream>
#include"circle.h"
using namespace std;
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){};
void Circle::setRadius(float r)
{
this->radius = r;
}
float Circle::getRadius() const
{
return this->radius;
}
float Circle::area() const
{
return 3.14*this->radius*this->radius;
}
ostream &operator<<( ostream& output,const Circle& circle)
{
output<<"点("<<circle.x<<","<<circle.y<<")"<<" "<<"radius = "<<circle.radius<<" "<<"area"<<circle.area()<<endl;
return output;
}
//Cylinder声明文件
#include"circle.h"
using namespace std;
class Cylinder:public Circle
{
public:
Cylinder(float ,float ,float ,float);
void setHeight(float);
float getHeight() const;
float area() const;
float volume() const;
friend ostream& operator <<(ostream&,const Cylinder&);
protected:
float height;
};
//Cylinder实现文件
#include<iostream>
#include"cylinder.h"
//float height;
using namespace std;
Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){};
void Cylinder::setHeight(float h)
{
this->height = h;
}
float Cylinder::getHeight() const
{
return this->height;
}
float Cylinder::area() const
{
return 2*Circle::area() + 2 * 3.14 * this->radius * this->height;
}
float Cylinder::volume() const
{
return this->height*Circle::area();
}
ostream& operator <<(ostream& output,const Cylinder& cylinder )
{
output<<"点("<<cylinder.x<<","<<cylinder.y<<")"<<" "<<"radius = ";
output<<cylinder.radius<<" "<<"area="<<cylinder.area()<<" "<<"height:"<<cylinder.height;
output<<" "<<"volume:"<<cylinder.volume()<<endl;
return output;
}
//主函数文件
#include<iostream>
#include"cylinder.h"
using namespace std;
int main()
{
float x = 3;
float y = 5;
float radius = 8;
float height = 10;
class Point point(x,y);
cout<<point;
point.setPoint(8,9);
cout<<point<<endl;
class Circle circle(x,y,radius);
cout<<circle;
cout<<"-------------"<<endl;
class Cylinder cylinder(x ,y ,radius ,height);
cout<<cylinder;
cylinder.setHeight(8);
cout<<cylinder;
class Point &pointRef = cylinder;
cout<<pointRef;
class Circle &circleRef = cylinder;
cout<<circleRef;
system("pause");
return 0;
}
利用虚函数实现动态多态性
虚函数的作用
多态是一种泛型编程思想,基类的指针或引用调用派生类的函数
虚函数是实现这个编程思想的语法基础
C++中使用虚函数解决动态多态的问题。
虚函数的作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用基类或派生类中的同名函数
每个基类有多个派生类,每个派生类有产生新的派生类,形成了同一基类的类族
定义一个基类指针pt,可以调用同一类族中不同类的虚函数——>多态性
由虚函数实现动态多态性就是:同一类族中不同类的对象,对同一函数的调用做出不同的响应
虚函数的使用方法:
1)、在基类中用virtual声明成员函数为虚函数。在类外定义虚函数时,不必再加virtual
2)、在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体
当一个成员函数被声明为虚函数后,其派生类的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰
如果在派生类中没有基类函数的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
3)、定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象
4)、通过该指针变量调用虚函数,此时调用的就是指针变量指向的对象的同名函数
以前的函数重载时同一层次的同名函数的问题——>横向重载
现在虚函数处理的是不同派生层次的同名函数的问题——>纵向重载
但与重载不同的是:同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或参数类型不同)
静态关联和动态关联
关联:确定调用的具体对象的过程
静态关联:在编译时即可确定其调用的虚函数属于哪一个类,函数重载属于静态关联
动态关联:在运行阶段把虚函数和类对象“绑定”在一起
什么情况下应当声明虚函数
使用虚函数时注意到地方:
1)、只能用virtual声明类的成员函数,把它作为虚函数,而不能将类外的普通函数声明为虚函数
2)、一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数
3)、虚函数允许子类重写,不允许重载。(其实和上一条是一样的)
关于重载、重写、隐藏的区别
Overload(重载):在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。Override(覆盖或重写):是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
注:重写基类虚函数的时候,会自动转换这个函数为virtual函数,不管有没有加virtual,因此重写的时候不加virtual也是可以的,不过为了易读性,还是加上比较好。Overwrite(重写):隐藏,是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
根据什么考虑是否把一个成员函数声明为虚函数?
1)、首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改功能,一般应该将它声明为虚函数。
2)、如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到作为基类而把类中的所有成员成员函数都声明为虚函数。
3)、应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果通过基类指针或引用去访问,则应当声明为虚函数。
4)、有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。
重要:
使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的,因此,多态性是高效的。
虚析构函数
为什么要用虚析构函数?
在使用new运算符建立临时对象(class Point *ptr = new Circle(x,y,radius);),若基类有析构函数,并且定义了一个指向该基类的指针变量。当用delete运算符撤销对象时,系统只会执行基类的析构函数,而不执行派生类的析构函数。如果这时派生类中的析构函数中有关于内存释放的操作,将会造成内存泄露。
cout<<"----虚析构函数---------"<<endl;
class Point *ptr = new Circle(x,y,radius);
delete ptr;
执行结果:系统只会执行基类Point的析构函数,而不执行派生类Circle的析构函数
把基类的析构函数定义为虚函数
virtual ~Point();
执行结果: 系统执行了基类Point的析构函数,也执行派生类Circle的析构函数
重要:如果将基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数(即使派生类的析构函数与基类的析构函数的名字不相同)
专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。
构造函数不能声明为虚函数。
纯虚函数与抽象类
纯虚函数
在类Poin中没有用函数area(),但是Circle和Cylinder都用到了,可以在Point中声明函数area()是纯虚函数。
virtual float area() const = 0;
纯虚函数是在声明虚函数的时候被“初始化”为0的函数。
纯虚函数的一般形式:
virtual 函数类型 函数名(参数表列) = 0;
注意:
1)、纯虚函数没有函数体;
2)、最后面的“=0”并不表示函数返回值为0,它只是起形式上的作用,告诉编译系统“这是纯虚函数”;
3)、这是一个声明语句,最后应有分号。
纯虚函数只有函数的名字而不具备函数的功能,不能被调用。它只是通知编译系统:“在这里声明一个虚函数,留待派生类中定义”。在派生类中对此函数提供定义后,它才具备函数的功能,才可以被调用。
纯虚函数的作用:在基类中为派生类保留一个函数的名字,以便派生类根据需要对他进行定义。如果在基类中没有保留函数的名字,则无法实现多态性。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数的定义,则该虚函数在派生类中仍然为纯虚函数。
抽象类
凡是包含纯虚函数的类就是抽象类;
全是纯虚函数的类就是接口类;
因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的(抽象类无法进行实例化,接口类更加不能实例化了)。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。
如果在抽象类所派生的新类中对基类的所有纯虚函数进行了定义,那么这些函数被赋予了功能,可以被调用。这个派生类就不是抽象类,而是可以用来定会对象的具体类。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
一个综合的例子
//类shape声明文件+实现文件(这个地方为了作对比,具体的对比在我的代码注释中)
using namespace std;
class Shape
{
public:
//这个地方为什么不用接口类呢?
/*
一旦这个地方声明了接口类,就需要在Point中定义所有的纯虚函数;
只要有一个纯虚函数没有被定义,那么就属于抽象类,应为抽象类不能被实例化(但是我们要实例化Point类);
所以就造成了矛盾。
那我们就把Shape声明为接口类,就在Point把所有纯虚函数给定义了,但是又得想一下,Point是没有area和volume的。
如果这个地方定义为接口类,就得在Point中定义area和volume的返回值为0;
*/
//virtual float area() const =0;
//virtual float volume() const =0;
virtual float area() const {return 0.0;}
virtual float volume() const {return 0.0;}
virtual void shapeName() const = 0;
virtual ~Shape(){cout<<"executing Shape destructor"<<endl;}
};
//类Point声明文件
#include"shape.h"
using namespace std;
class Point:public Shape
{
public:
Point(float ,float);
void setPoint(float ,float);
float getX() const;
float getY() const;
friend ostream& operator<<(ostream&,const Point& );
//virtual float area() const;
//virtual float volume() const;
virtual void shapeName() const;
virtual ~Point();
protected:
float x,y;
};
//类Point实现文件
#include<iostream>
#include"point.h"
using namespace std;
//float x,y;
Point::Point(float a,float b):x(a),y(b){}
void Point::setPoint(float a,float b)
{
this->x = a;
this->y = b;
}
float Point::getX() const
{
return this->x;
}
float Point::getY() const
{
return this->y;
}
ostream &operator<<(ostream& output,const Point& point)
{
output<<"点("<<point.x<<","<<point.y<<")"<<endl;;
return output;
}
Point::~Point()
{
cout<<"executing Point destructor"<<endl;
}
void Point::shapeName() const
{
cout<<"Class Point"<<endl;
}
//如果Shape必须为接口类,这个地方必须定义
//如果Shape为抽象类(area和volume不是纯虚函数),这个地方可以删除
//float Point::area() const
//{
// return 0.0;
//}
//float Point::volume() const
//{
//return 0.0;
//}
//类Circle声明文件
#include"point.h"
using namespace std;
class Circle:public Point
{
public:
Circle(float = 0,float = 0,float = 0);
void setRadius(float );
float getRadius() const;
friend ostream& operator<<(ostream&,const Circle&);
virtual ~Circle();
virtual float area() const;
virtual void shapeName() const;
protected:
float radius;
};
//类Circle实现文件
#include<iostream>
#include"circle.h"
using namespace std;
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){};
void Circle::setRadius(float r)
{
this->radius = r;
}
float Circle::getRadius() const
{
return this->radius;
}
ostream &operator<<( ostream& output,const Circle& circle)
{
output<<"点("<<circle.x<<","<<circle.y<<")"<<" "<<"radius = "<<circle.radius<<" "<<"area"<<circle.area()<<endl;
return output;
}
Circle::~Circle()
{
cout<<"executing Circle destructor"<<endl;
}
float Circle::area() const
{
return 3.14*this->radius*this->radius;
}
void Circle::shapeName() const
{
cout<<"Class Circle"<<endl;
}
//类Cylinder声明文件
#include"circle.h"
using namespace std;
class Cylinder:public Circle
{
public:
Cylinder(float ,float ,float ,float);
void setHeight(float);
float getHeight() const;
friend ostream& operator <<(ostream&,const Cylinder&);
virtual ~Cylinder();
virtual float area() const;
virtual float volume() const;
virtual void shapeName() const;
protected:
float height;
};
//类Cylinder实现文件
#include<iostream>
#include"cylinder.h"
//float height;
using namespace std;
Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){};
void Cylinder::setHeight(float h)
{
this->height = h;
}
float Cylinder::getHeight() const
{
return this->height;
}
ostream& operator <<(ostream& output,const Cylinder& cylinder )
{
output<<"点("<<cylinder.x<<","<<cylinder.y<<")"<<" "<<"radius = ";
output<<cylinder.radius<<" "<<"area="<<cylinder.area()<<" "<<"height:"<<cylinder.height;
output<<" "<<"volume:"<<cylinder.volume()<<endl;
return output;
}
Cylinder::~Cylinder()
{
cout<<"executing Cylinder destructor"<<endl;
}
float Cylinder::area() const
{
return 2*Circle::area() + 2 * 3.14 * this->radius * this->height;
}
float Cylinder::volume() const
{
return this->height*Circle::area();
}
void Cylinder::shapeName() const
{
cout<<"Class Cylinder"<<endl;
}
//主函数文件
#include<iostream>
#include"cylinder.h"
using namespace std;
int main()
{
float x = 3;
float y = 5;
float radius = 8;
float height = 10;
class Shape *ptr = new Point(x,y);
ptr->shapeName();
cout<<ptr->area()<<endl;
class Point point(x,y);
cout<<point;
point.setPoint(8,9);
cout<<point<<endl;
class Circle circle(x,y,radius);
ptr = new Circle(x,y,radius);
cout<<ptr->area()<<endl;;
cout<<circle;
cout<<"-------------"<<endl;
class Cylinder cylinder(x ,y ,radius ,height);
cout<<cylinder;
cylinder.setHeight(8);
//注意这个地方应该用new 类名(参数),这样和后面的delete呼应
//如果用ptr = &cylinder,容易出错,因为最好接收一个临时变量
ptr = new Cylinder(x ,y ,radius ,height);
cout<<ptr->volume()<<endl;
cout<<cylinder;
class Point &pointRef = cylinder;
cout<<pointRef;
class Circle &circleRef = cylinder;
cout<<circleRef;
delete ptr;
system("pause");
return 0;
}
纯虚函数是在抽象基类中声明的,只是在抽象基类中他才称为纯虚函数,在其派生类中虽然继承了该函数,但除非再次用“=0”把他声明为纯虚函数,它就不是也不能称为纯虚函数