一、承继与多态

#include <iostream>
// 基类 A
class A {
public:
    virtual void draw() {
        std::cout << "A::draw()" << std::endl;
    }
};
// 承继自 A 的子类 B
class B : public A {
public:
    virtual void draw() {
        std::cout << "B::draw()" << std::endl;
    }
};
// 承继自 B 的子类 C
class C : public B {
public:
    virtual void draw() {
        std::cout << "C::draw()" << std::endl;
    }
};
  • A 是基类,B 承继 A,C 承继 B,A、B、C 都有各自的虚函数 draw
  • 关于数据部分,承继则是把数据直接承继下来,承继了数据的大小
  • 关于函数来说,承继的则是调用权
  • 父类有虚函数,子类一定有虚函数
int main() {
    // 创立一个 C 目标
    C c;
    // 调用 C 的虚函数 draw
    c.draw();
    return 0;
}

二、动态绑定

2.1 动态绑定方式调用

int main() {
    // 创立一个 C 目标指针 
    C* pc = new C(); 
    // 经过指针调用 C 的虚函数 draw 
    pc->draw(); 
    // 释放内存 
    delete pc;
}

2.2 什么是动态绑定

动态绑定是 C++ 中完成多态性的一种机制,也称为运行时多态(Run-time Polymorphism)。它可以在运行时依据目标的实践类型来动态绑定相应的函数,以完成不同目标之间的多态性。

具体来说,当运用一个指向基类的指针或引证调用一个虚函数时,程序会在运行时依据指针或引证所指向的目标的实践类型(即派生类类型)来调用相应的虚函数。这种机制可以让程序在不知道目标实践类型的情况下,依然可以正确地调用相应的函数。

动态绑定是经过虚函数和虚表来完成的。每个类都有一个虚表,其间存放着该类的虚函数的地址。当程序调用一个虚函数时,会依据目标的实践类型在虚表中查找相应的函数地址,并进行调用。这便是动态绑定的完成原理。

总归,动态绑定是 C++ 中完成多态性的一个重要机制,它让程序可以更加灵敏和可扩展,使得代码具有更好的可维护性和可重用性。

三、虚指针和虚表

虚指针和虚表是完成动态多态的两个重要机制。

3.1. 虚指针(virtual pointer,简记:vptr)

虚指针是一个指向虚表的指针,它是一个目标(通常是类的目标)中的躲藏成员,用于支持动态多态。当一个类被声明为具有虚函数时,每个目标都会在其布局中包括一个虚指针。虚指针是在目标的构造函数中初始化的,它指向一个虚表。

3.2. 虚表(virtual table,简记:vtbl)

虚表是一个存储了该类中所有虚函数地址的数组。每个类都有自己的虚表,当一个类被声明为具有虚函数时,编译器会主动生成一个虚表。在运用虚函数时,实践调用的函数地址是经过虚表查找得到的。虚表通常是只读的,并且是全局唯一的。

经过运用虚指针和虚表,C++ 可以支持运行时多态,即可以依据目标的实践类型在运行时确定要调用哪个函数,这是 C++ 面向目标编程的重要特性之一。

虚表的完成机制是编译器将虚函数地址存储在一个数组中,然后将该数组的地址存储在虚表指针中。当调用虚函数时,程序会经过虚指针找到目标所属类的虚表,然后依据函数在虚表中的位置找到实践要调用的函数。