众所周知,C++虚函数是一大难点,也是面试过程中必考部分。此次,从虚函数的相关概念、虚函数表、纯虚函数、再到虚继承等等跟虚函数相关部分,做一个比较细致的整理和复习。
- 虚函数
- OOP的核心思想是多态性(polymorphism)。把具有继承关系的多个类型称为多态类型。引用或指针的静态类型与动态类型不同这一事实正是C++实现多态性的根本。
- C++ 的多态实现即是通过虚函数。在C++中,基类将类型相关的函数与派生类不做改变直接继承的函数区别对待。对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明为虚函数(virtual function)。
- C++在使用基类的引用或指针调用一个虚函数成员函数时会执行动态绑定。因为只有直到运行时才能知道调用了那个版本的虚函数,所以所有的虚函数必须有定义。
- 动态绑定只有当通过指针或引用调用虚函数时才会发生。
- 一旦某个函数被声明为虚函数,则在所有派生类中它都是虚函数。所以在派生类中可以再一次使用virtual指出,也可以不用。
- 如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。换句话说,如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类的函数版本也是如此。此时,传入派生类函数的将是基类函数定义的默认实参。
-
在某些情况下,我们希望对虚函数的调用不进行动态绑定,而是强迫其执行虚函数的某个特定版本。
-
//强行调用基类中定义的函数版本而不管baseP的动态类型到底是什么 double price = basePtr->Base::net_price();
通常情况下,只有成员函数(或友元)中的代码才需要使用作用域运算符来回避虚函数的机制。
抽象基类
- 纯虚函数:一个纯虚函数无须定义。通过在函数体的位置(即在声明语句的分号之前)书写 =0 将一个虚函数说明为纯虚函数。其中 =0 只能出现在类内部的虚函数声明语句处。
- 值得注意的是,我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部,不能在类的内部为一个 =0 的函数提供函数体。
- 含有纯虚函数的类是抽象基类。
-
含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类。抽象基类负责定义接口,而后续的类可以覆盖接口。我们不能(直接)创建一个抽象基类的对象。
-
//Base 声明了纯虚函数,而 Derive将覆盖该函数 Base b; //错误,不能定义Base的对象 Derive d; //正确,Derive中没有纯虚函数
-
虚函数表指针和虚函数表
- 对于每一个定义了虚函数的类,编译器会为其创建一个虚函数表,该虚函数表被所有的类对象所共享,即它不是跟着对象走的,而是相当于静态成员变量,是跟着类走的。
- 虚函数表指针vptr,每一个类的对象都有一个虚函数表指针,该指针指向类的虚函数表的位置。为了实现多态,当一个对象调用某个虚函数时,实际上是根据该虚函数指针vptr所指向的虚函数表vtable里找到相应的函数指针并调用之。
- 关于vptr在对象内存布局中的存放位置,一般都是放在内存布局的最前面,当然,也可能有其他实现方式。
-
基类定义如下所示:
-
class Base{ public: Base() :a(0), b(0), c('