多态:按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着发出同样的消息被不同类型的对象接收导致完全不同的行为。
一、多态的一些知识点
1、联编(binding),也称绑定
联编:确定一个具有多态的程序语句在执行的时候究竟调用哪个函数。
联编有两种方式:动态联编 和 静态联编
静态联编:在编译时就能决定调用的是哪个函数(编译时的多态性)
主要靠函数重载和操作重载实现
动态联编:(动态束定) 在程序运行过程中动态确定所要操作的函数(运行时的多态)
主要靠虚函数实现
注:switch 和 if 是动态联编很好的例子
2、虚函数的定义及发生条件
1、虚函数的定义:虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
格式:virtual 返回类型 函数名(参数列表);
注意:
(1)在基类的成员函数前面加上virtual关键字,所有子类的相关函数全都自动加上virtual关键字
(2)虚函数必须是基类、子类中同名、同参、同返回值的成员函数
(3)内联函数、构造函数、静态成员函数不可以定义为虚函数
2、动态联编(虚函数)实现的三个条件:
(1) 要有public继承
(2)子类要有虚函数重写
(3)要有父类指针指向子类对象
3、虚函数实现机制
动态联编是通过虚函数表实现的,每个含有虚函数的类都会有一个虚函数表用于存储该类中虚函数的指针(入口地址);此外编译器为每个对象生成一个vfptr指针,指向对应类的虚函数表。
(1)vfptr指针的分步初始化:
vfptr指针的分步初始化:
为子类构造对象时:
(1)首先调用父类的构造函数,此时vfptr指针会指向父类的虚函数表
(2)执行完父类的构造函数后,执行子类构造函数的内容,vfptr指针指向子类的虚函数表
//vfptr指针的验证:
class Point
{
public:
virtual void fun();
void display();
private:
int x;
int y;
};
class Point2
{
public:
void fun();
void display();
private:
int x;
int y;
};
int main(void)
{
Point p1;
Point2 p2;
cout << sizeof(p1) << endl; //12
cout << sizeof(p2) << endl; //8
}
(2)虚函数的内存映像:
4、函数重写、函数重载、函数隐藏
1、函数重载:不同的函数使用相同的函数名
注:函数重载的条件:函数名相同、参数列表不同、返回值不做要求
2、函数重写:虚函数重写
注:用于类中同名、同参、同返回值的虚函数的重写,相同方法不同实现
3、函数隐藏:父、子类中同名、同参、同返回值的普通成员函数,在子类中重写函数的实现
注:此时子类调用此方法会屏蔽父类中的相同方法;若执意调用父类的方法,写明作用域即可
5、虚析构函数:在虚构函数前面加上关键字virtual进行说明则该函数成为虚析构函数;
注:如果一个类的虚构函数说明为虚析构函数,则其派生的所有子类的析构函数也被说明为析构函数。
目的:使用delete运算符删除一个对象时,能保证析构函数被正确的执行;
class Point
{
public:
Point();
virtual ~Point();
private:
int x;
int y;
};
class Point3D:public Point
{
public:
Point3D();
~Point3D();
private:
int x;
int y;
};
int main(void)
{
Point *pp = new Point3D;
delete pp;
//此时delete执行前会先触发Point3D的析构函数,这正是虚析构函数带来的好处
//若没有把析构函数说明为虚析构函数,此时就会触发Point的析构函数,造成释放不完整
return 0;
}
二、纯虚函数与抽象类
1、纯虚函数:
纯虚函数:在基类中不能明确给出虚函数的实现时,可将其声明为纯虚函数,留待子类具体实现。
作用:为子类留一个一致的接口;
格式:virtual void fun() = 0;
2、抽象类
抽象类:含有纯虚函数的类称为抽象类
注:抽象类只能当作基类使用,它不可以实例化对象,抽象类派生的子类必须对纯虚函数进行重写,否则子类仍为抽象类
*抽象类的作用:
(1)用作基类
(2)用作指针或引用的基类型