目录
- 多态性
- 多态性的定义
- 例子
- 虚函数
- 虚函数的作用
- 什么情况下使用虚函数
- 虚析构函数
- 纯虚函数和抽象类
什么是多态性
- 面对不同的对象发送同一个消息,不同的对象在接受时会产生不同的行为(方法)。也就是说,每个对象可以用自己的方式去响应共同的的消息。所谓消息就是函数的调用,不同的行为就是指不同的实现,即执行不同的函数。
举个例子 学校要开学了 向所有人发了一个鬼故事,明天开学,这时学生做出反应是 快发个朋友圈(假期余额已不足,然后吐槽学校)。然后乖乖明天去上学,你的父母收到消息 开心的不能行,老师收到消息就要准备开学后要讲的课。这就是一个消息,不同的人收到,会做出不同的反应。
静态多态性:(编译时)函数的重载 和 运算符的重载。
动态多态性:不在编译时确定调用的是哪个函数,而是在程序运行过程中才确定地确定操作所指针的对象。运行时的多态性。通过虚函数实现 virtual
例子演示
#include<iostream>
using namespace std;
class Point{
public :
Point (){
x = 0;
y = 0;
}
Point (double, double);
void setPoint(double, double);
double getX() const{return x;}
double getY() const{return y;}
friend ostream& operator << (ostream &, const Point&);
friend istream& operator >> (istream &, Point &);
protected:
double x;
double y;
};
Point::Point(double a, double b){
x = a;
y = b;
}
void Point::setPoint(double a, double b){
x = a;
y = b;
}
ostream &operator << (ostream & cout, const Point &p){
cout << "( " << p.x << ", " << p.y << " )" << endl;
}
istream &operator >> (istream & cin, Point &p){
cin >> p.x >> p.y;
}
int main (){
Point p(3.5, 6.4);
cout << "x = " << p.getX() << " , y = " << p.getY() << endl;
p.setPoint(8.5, 6.8);
cout << "p(new) : " << p << endl;
cin >> p;
cout << "p(new) : " << p << endl;
return 0;
}
进行扩展
#include<iostream>
using namespace std;
class Circle : public Point{
public :
Circle(double, double, double);
void setRadius(double);
double getRadius() const;
double area() const;
friend ostream &operator << (ostream &, const Circle &);
friend istream &operator >> (istream &, Circle &);
protected:
double radius;
};
Circle:: Circle(double a, double b, double r):Point(a,b), radius(r){}
void Circle::setRadius(double r){
radius = r;
}
double Circle::getRadius() const{return radius;}
double Circle::area() const{
return 3.14 * radius * radius;
}
istream &operator >> (istream &cin, Circle &c){
cin >> c.x >> c.y >> c.radius;
}
ostream &operator << (ostream &cout, const Circle &c){
cout << "圆心:" << "( " << c.x << ", " << c.y << " )" << endl;
cout << "Radius: " << c.radius << endl;
cout << "area : " << c.area() << endl;
}
int main (){
Circle c(3.3, 5.4, 5);
cout << c << endl;
c.setRadius(3);
c.setPoint(5,5);
cout << "new c : " << c << endl;
Point &p = c;
cout << "p = " << p << endl;
return 0;
}
有一点就是 const 这是词如果加到函数后面
double getRadius() const;
函数后面+const 不能修改类内的数据成员, 只能访问。
再扩充
#include<iostream>
using namespace std;
class Cylinder : public Circle{
public :
Cylinder(double , double, double, double);
void setHeight(double);
double getHeight() const;
double area() const;
double volume() const;
friend ostream &operator << (ostream &, const Cylinder &);
protected:
double height;
};
Cylinder::Cylinder(double a, double b, double r, double h)
: Circle(a, b, r), height(h){}
void Cylinder::setHeight(double h){
height = h;
}
double Cylinder :: area() const
{
return 2 * Circle::area() + 2*3.14 *radius*height;
}
double Cylinder :: volume() const{
return Circle::area() * height;
}
ostream &operator << (ostream & cout, const Cylinder &cy){
cout << "( " << cy.x << ", " << cy.y << " )" << endl;
cout << "radius : " << cy.radius << endl;
cout << "height : " << cy.height << endl;
cout << "area : " << cy.area() << endl;
cout << "volume : " << cy.volume() << endl;
}
int main (){
Cylinder cy(3, 4, 5, 6);
cout << cy << endl;
cy.setHeight(15);
cy.setRadius(7.5);
cy.setPoint(5,5);
cout << cy << endl;
Point &p = cy;
cout << p << endl;
Circle &q = cy;
cout << q << endl;
return 0;
}
Circle 中有area ()函数 Cylinder 中也有area()函数 当我们用 cy.area()时 ,调用的是Cylinder的函数,不是Circle中area()他们不是重载函数,因为不在一个类内(分别在基类和派生类中),这种调用属于同名覆盖。重载函数的参数个数和参数类型必须至少有一个不同,否则系统无法确定调用哪个,
再看上面的cout 输出的 我重载了 3个 cout 这三个传递的参数不同从输出结果可以看出调用哪个运算符。
上面的3个例子中 存在的静态多态性,是由重载运算符引起的。
利用虚函数实现动态多态性
(1)虚函数的作用
作用:程序中不再是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针分别调用这些同名函数。只须在调用前临时给指针变量pt赋以不同的值(指向不同类的对象)即可。
打个比方 你要去某个地方办事,如果坐公交车,必须事先确定好目的地,然后乘坐能够达到目的的公交车。如果改为乘出租车,就简单很多了,不必查行车路线,因为出租车什么地方都去,只要在上车后临时告诉司机要到哪里即可。如果想访问多个目的地,只要在到达一个目的地后再告诉下个目的地即可。这种事先确定路线的就是静态多态。而出租车临时决定行程的相当与动态多态。
虚函数的作用是允许再派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
在实际过程中 这些同名函数会创建一个指针表,然后你指针指向哪个类的对象,指针就指向这个类中的函数。
#include<iostream>
using namespace std;
class Student
{
public:
Student(int, string, double);
void display();
protected:
int num;
string name;
double score;
};
class Graduate:public Student
{
public:
Graduate(int, string, double, double);
void display();
double wage;
};
Student::Student(int n, string nam, double s){
num = n;
name =nam;
score = s;
}
Graduate::Graduate(int n, string nam, double s, double w):Student(n, nam, s){
wage = w;
}
void Student::display(){
cout << "num : " << num << endl;
cout << "name : " << name << endl;
cout << "score : " << score << endl;
}
void Graduate::display(){
cout << "num : " << num << endl;
cout << "name : " << name << endl;
cout << "score : " << score << endl;
cout << "wage : " << wage << endl;
}
int main (){
Student stud(999, "LiuMang", 99);
Graduate grad(1001, "Dalao", 100, 20000);
Student *p = &stud;
p->display();
cout << endl;
p = &grad;
p->display();
return 0;
}
输出结果
在主函数中我们定义了指向基类对象的指针变量pt,并先使pt指向stud,用pt->display()输出的是基类对象stud的全部的数据成员,然后我们将pt指向grad,再次调用pt->display()企图输出grad1中的全部的数据成员,但是只输出了grad中的基类的成员,说明它并没有调用grad中的display()函数,而只是调用了stud中的display().
假如想输出grad中的全部的数据成员,当然也可以采用这样的方法:通过对象名用display函数如,grad.display(),或者定一个指向Gradudate类对象的指针变量pt,然后使pt指向grad,再用pt->display();调用,当然可以。
但是如果该基类有多个派生类,每个派生类又产生新的派生类,形成了同基类的类族。每个派生类都有同名函数display,在程序中要调用同一类组中不同类的同名函数,就要定义多个指向多个各派生类的指针变量。这两种种方法很不方便。
我们是否可以用一种简单的方法来调用同一类族中不同类的所有的同名函数。
虚函数诞生了,他的作用就是可以实现用一个基类指针 就可以调用同一类族中所有的同名函数,但是在调用前要将指针指向你要用的类的对象。
在Student 的diplay函数前加virtual;
virtual void display(); //将基类中的函数作为虚函数
输出的结果
这时我们就调用了grad中的display函数,pt是同一个基类指针可以调用同一类族中不同类的虚函数,这就是多态性,对同一个消息,不同的对象有不同响应方式。
简单的说如果你不声明为vritual 虚函数 你指向基类的指针就只能调用派生类中 基类的数据成员和函数,如果你声明为虚函数,你就可以通过指向基类的指针 去调用派生类中的成员函数和数据成员,基类指针会自动转换为派生类指针。
将基类的某个成员函数声明为虚函数后,允许其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象从而调用其中的同名函数,
虚函数 的使用方法:
(1)类内声明需要加 virtual ,类外定义时不需要+。
(2)在派生类中重定义时,此函数的:函数名,函数类型,函数参数个数,和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体。
(3)当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数,一次在派生类重新声明该函数时,可以不+,但是建议+vritual,使程序更加清晰。
(4)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
(5)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数(指向哪个派生类就调用哪个)。
2.什么情况下声明虚函数
(1)只能用virtual声明类的成员函数,把它作为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用就是派生类对基类的函数的重定义。
(2)一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但是与该函数具有相同参数(个数和类型)和函数返回值类型的同名函数。
我们从哪些方面考虑将一个成员函数声明为虚函数呢?
(1)看成员函数的类是否为作为基类,并且改函数在派生时作用发生改变,
(2)如过成员函数被继承后不被修改,那就没有必要声明为虚函数。
(3)对成员函数的调用是通过对象名还是通过类指针或引用去访问,如果是通过类指针则声明为虚函数。
(4)有时在定义虚函数时并不定义其函数体,即函数体是空的,它的作用只是定义了一个虚函数名,具体功能留给派生类去添加
使用虚函数,系统有一定的空间开销,当一个类带有虚函数时,编译系统会为该类构造一个虚函数表,他是一个指针数组,存放每个虚函数的入口地址,系统在进行动态关联时的时间开销是很少的,因此多态性高效。