一、多态
1、定义:相同的对象收到不同的消息或者不同的对象收到相同的消息时,产生的不同的动作。
2、静多态(早绑定):在编译之前就知道要用哪个函数
3、动多态(晚绑定):是利用虚函数实现了运行时的多态,也就是说在系统编译的时候并不知道程序将要调用哪一个函数,只有在运行到这里的时候才能确定接下来会跳转到哪一个函数的栈帧。
动多态的前提:以封装和继承为基础,至少两个类(父、子)
面向对象三大特征:封装、继承、多态
二、代码演示
要求:
1、Shape类,成员函数:calcArea(),构造、析构
2、Rect类,成员函数:calcArea(),构造、析构, 数据成员:m_dWidth,m_dHeight
3、Circle类,成员函数:calcArea(),构造、析构,数据成员:m_dR
Shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include<iostream>
#include<string>
using namespace std;
class Shape
{
public:
Shape();
~Shape();
double calcArea();
};
#endif
Shape.cpp
#include"Shape.h"
Shape::Shape()
{
cout<<"Shape()"<<endl;
}
Shape::~Shape()
{
cout<<"~Shape()"<<endl;
}
double Shape::calcArea()
{
cout<<"Shape::calcArea()"<<endl;
return 0;
}
Rect.h
#include"Shape.h"
class Rect:public Shape
{
public:
Rect(double width,double height);
~Rect();
double calcArea();
protected:
double m_dWidth;
double m_dHeight;
};
Rect.cpp
#include"Rect.h"
Rect::Rect(double width,double height)
{
cout<<"Rect()"<<endl;
m_dHeight = height;
m_dWidth = width;
}
Rect::~Rect()
{
cout<<"~Rect()"<<endl;
}
double Rect::calcArea()
{
cout<<"Rect::calcArea"<<endl;
return m_dHeight*m_dWidth;
}
Circle.h
#include"Shape.h"
#include"Coordinate.h"
class Circle:public Shape
{
public:
Circle(double r);
~Circle();
double calcArea();
protected:
double m_dR;
};
Circle.cpp
#include"Circle.h"
Circle::Circle(double r)
{
cout<<"Circle()"<<endl;
m_dR = r;
}
Circle::~Circle()
{
cout<<"~Circle()"<<endl;
}
double Circle::calcArea()
{
cout<<"Circle::calcArea()"<<endl;
return 3,14*m_dR*m_dR;
}
main.cpp
#include"Circle.h"
#include"Rect.h"
#include<stdlib.h>
int main()
{
Shape *shape1 = new Rect(3,6);
Shape *shape2 = new Circle(5);
shape1->calcArea();//调用的是父类的calcArea()
shape2->calcArea();
delete shape1;
shape1 = NULL;
delete shape2;
shape2 = NULL;//都只执行了父类的析构函数
return 0;
}
运行结果:
用父类的指针指向在堆内申请的子类的对象,Rect类和Circle类以及父类都含有calcArea()函数,用实例化出的子类的对象去调用calcArea(),结果打印出的是父类的calcArea()函数,无法调用子类的calcArea()函数;在销毁指针时,也只调用了父类的析构函数,而没有调用子类的析构函数。
如何解决?--->在父类的析构函数和calcArea()函数前分别加上virtual,其他函数不做修改(子类中的析构函数和calcArea()函数前不加virtual也可以,virtual会被继承下去)
虚函数的特性可以被继承,当子类中定义的函数与父类中虚函数的声明相同时,该函数也是虚函数
Shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include<iostream>
#include<string>
using namespace std;
class Shape
{
public:
Shape();
virtual ~Shape();
virtual double calcArea();
};
#endif
运行结果:
加上virtual关键字之后,实例化出的子类的对象可以正常调用自己的calcArea()函数。
总结:
1、virtual 可以修饰某一个类的普通的成员函数也可以修饰某一个类的析构函数
2、virtual在函数中的使用限制
普通函数不能是虚函数,会导致编译错误
内联函数inline不能是虚函数,会正常编译 但是inline会被忽略掉 。例如inline virtual int eat(),会忽略掉inline关键字,使之成为虚函数 。
静态成员函数不能是虚函数,会导致编译错误
构造函数不能是虚函数,会导致编译错误
3、虚析构函数是为了避免使用父类指针释放子类对象时造成的内存泄漏
4、覆盖与隐藏区别
覆盖指的是子类覆盖父类函数(被覆盖),特征是:
(1)分别位于子类和父类中
(2)函数名字与参数都相同
(3)父类的函数是虚函数(virtual)
隐藏指的是子类隐藏了父类的函数(还存在),具有以下特征:
(1)子类的函数与父类的名称相同,但是参数不同,父类函数被隐藏
(2)子类函数与父类函数的名称相同,参数也相同,但是父类函数没有virtual,父类函数被隐藏
5、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
6、最好将基类的析构函数声明为虚函数。(因为如果派生类不从堆上申请内存,那么即便是没有虚析构函数,也是无所谓的 ;但最好还是加上,因为我们并不知道未来子类的构造函数是否会从堆上申请内存)