11、虚函数、多态、纯虚函数
- 虚函数
- 覆盖
- 调用
- 多态
- 实现多态的两个必要条件
- 多态 和 this指针
- 多态的实现:虚函数表
- 虚函数表与动态绑定
- 动态绑定
- 动态绑定对性能的影响
- 纯虚函数
- 抽象类
- 纯抽象类
虚函数
形如class 类名{
virtual 返回值 函数名(形参表) { … }
};
的成员函数,称为虚函数或方法
覆盖
如果子类的成员函数和基类的虚函数具有相同的函数签名,那么该成员函数就也是虚函数,无论其是否带有virtual关键字。
与基类的虚函数构成覆盖关系
调用
通过基类类型指针调用虚函数
- 如果基类型指针指向基类对象,则调用基类的原始版本虚函数。
- 如果基类型指针指向子类对象,则调用子类的覆盖版本虚函数。
多态
- 如果子类提供了对基类虚函数的有效覆盖,那么通过一个基类型指针( 指向子类对象 ),或者基类型引用( 引用子类对象 )调用该虚函数,实际被调用的将是子类中的覆盖版本,而非基类中的原始版本,这种现象称为多态
- 多态的重要意义在于,
- 一般情况下,调用哪个类的成员函数是由指针或引用本身的类型决定的
- 而当多态发生时,调用哪个类的成员函数是由指针或引用的实际目标对象的类型决定的
实现多态的两个必要条件
- 需要在基类中定义虚函数,子类提供覆盖版本
- 必须借助基类型指针 (指向子类对象) 或者基类型引用 (引用子类对象) 调用该虚函数
多态 和 this指针
调用虚函数的指针也可以是基类中的this指针,同样能满足多态的条件,但在构造和析构函数中除外
多态的实现:虚函数表
虚函数表与动态绑定
动态绑定
当编译器看到通过指针或引用调用虚函数的语句时,并不急于生成有关函数跳转的指令,相反编译器会用一段代码替代该语句,这段代码在运行时才能被执行,完成如下操作
- 确定指针或引用的目标对象所占内存空间
- 从目标对象所占内存空间中找到虚表指针
- 利用虚表指针找到虚函数表
- 从虚函数表中获取所调用虚函数的入口地址
- 根据入口地址,调用该函数
动态绑定对性能的影响
- 虚函数表本身会增加进程内存空间的开销
- 与普通函数调用相比,虚函数调用要多出几个步骤,会增加运行时间的开销
- 动态绑定会妨碍编译器通过内联来优化代码
- 只有在确实需要多态特性的场合才使用虚函数,否则尽量使用普通函数
纯虚函数
形如class 类名{
virtual 返回值 函数名(形参表)=0;
};
的成员函数,称为纯虚函数或抽象方法
抽象类
- 拥有纯虚函数的类称为抽象类
- 抽象类不能实例化为对象
- 抽象类的子类如果不对基类中的全部纯虚函数提供有效的覆盖,那么该子类就也是抽象类
纯抽象类
全部由纯虚函数构成的抽象类称为纯抽象类或接口
// 纯虚函数和抽象类
#include <iostream>
using namespace std;
class A{
public:
virtual void foo() = 0; // 纯虚函数
void bar(){}
};
class B : public A{
public:
void foo(){
}
};
int main(void){
// A a;
// new A;
B b;
new B;
return 0;
}