跳到主要内容

虚函数和虚表详细总结

一、虚函数 (Virtual Functions)

1. 定义

虚函数是基类中使用 virtual 关键字声明的成员函数,支持动态多态性。通过基类指针或引用调用虚函数时,会根据实际对象类型选择调用相应的函数实现。

2. 声明和定义

  • 虚函数的声明

    class Base 
    {
    public:
    virtual void show(); // 声明虚函数
    };
  • 在派生类中重写虚函数

    class Derived : public Base 
    {
    public:
    void show() override; // 使用 override 关键字可选
    };

3. 动态绑定

通过基类指针或引用调用虚函数时,C++ 编译器会进行动态绑定,决定在运行时调用哪个版本的函数。

Base* b = new Derived();
b->show(); // 输出:Derived class show() called.
//动态绑定

4. 纯虚函数

  • 定义:没有实现的虚函数,用于定义接口,使用 = 0 声明。
class AbstractBase 
{
public:
virtual void show() = 0; // 纯虚函数
};

包含纯虚函数的类称为抽象类,无法实例化。

二、虚表 (Vtable)

1. 定义

虚表是由编译器为每个包含虚函数的类生成的结构,包含该类所有虚函数的指针。

2. 虚表的结构

  • 每个包含虚函数的类都有一个虚表,当调用的类为虚类时,会访问这个表,寻找具体实现。
  • 如果子类未重写某个虚函数,则虚表中会指向父类的实现。
  • 如果子类重写了某个虚函数,则虚表中会指向该类的实现.

3. 虚表的示例

class Base 
{
public:
virtual void show() { std::cout << "Base class show()" << std::endl; }
};

class Derived : public Base {
// No override
};

int main() {
Base* b = new Derived();
b->show(); // Calls Base::show()
delete b;
return 0;
}

4. 内存布局

  • Base 的虚表:
    [0] -> Base::show()
  • Derived 的虚表(未重写的情况下):
    [0] -> Base::show() // 指向父类的实现

三、虚指针 (vptr)

  • 每个对象实例中若包含虚函数,则会有一个虚指针,指向该对象所属类的虚表。
  • 虚指针通常是对象的第一个成员。

虚指针为普通的指针,32位下4个字节,64位下8个字节。

四、虚函数的特点

  1. 动态多态性:通过基类指针或引用调用子类的实现,在运行时决定函数调用。

  2. 基类指针和引用:允许使用基类类型来引用和操作派生类对象。

  3. 封装和保护:基类不需要了解具体的派生类实现。

  4. *!构造与析构的:构造时不调用派生类的虚函数,析构时确保派生类析构函数被调用。

  5. 性能开销

    • 空间开销:每个类需要存储虚表和每个对象需要一个虚指针。
    • 时间开销:调用虚函数时需要通过虚表查找函数地址,相比于普通函数调用有额外的开销。

四、虚函数的使用场景

  1. 实现接口:通过纯虚函数定义接口类。

  2. 多态行为:在需要不同对象间以相同方式处理时(如图形、游戏对象等)。

  3. 框架设计:在库或框架设计中,通过虚函数提供扩展点,让用户自定义行为。

五、问题

1.哪些函数不能是虚函数

  • 构造函数:无法被声明为虚函数。
  • 内联函数:不支持虚函数的特性。
  • 静态函数:由于不依赖于对象实例,无法是虚函数。
  • 友元函数:不属于类的成员,不支持虚函数。