C++之虚函数

C++7小时前发布 admin 1

前言

最近学习C++,这里简单记录一下虚函数(使用virtual修饰函数)相关内容,记录于此,方便自己查阅和回顾。

正文

虚函数

虚函数是在基类中用 virtual 关键字声明的成员函数。当派生类重写该函数时,通过基类指针或引用调用它,实际执行的是派生类的函数。

virtual void func() { 
    ...
}

虚函数并非一种特殊的函数类型,它只是被赋予了运行时多态能力的普通成员函数。

  1. 代码实现无差异:虚函数体内可以包含任意合法 C++ 代码(计算、IO、异常处理等),与普通函数完全一致。

  2. 存储位置相同:虚函数的机器码同样存放在代码段(.text),编译后的指令与普通函数无异。

  3. 调用语法相同:使用 .-> 调用虚函数的语法与普通函数完全一致。

  4. 唯一区别virtual 关键字触发了编译器生成虚函数表(vtable)和虚函数表指针(vptr),将编译时静态绑定改为运行时动态绑定

class Animal {
public:
    virtual void speak() {// 基类虚函数
        cout << "Animal speaks" << endl;
    }
};
class Dog : public Animal {
public:
    void speak() override {// 派生类重写
        cout << "Dog barks" << endl;
    }
};
class Cat : public Animal {
public:
    void speak() override {// 派生类重写
        cout << "Cat meows" << endl;
    }
};

int main() {
    Animal* a1 = new Dog();
    Animal* a2 = new Cat();
    a1->speak();  // 输出: Dog barks
    a2->speak();  // 输出: Cat meows
    delete a1;
    delete a2;
    return 0;
}

底层机制

虚函数的动态绑定是通过以下机制实现:

  • vtable(虚函数表):每个包含虚函数的类对应一张表,存储该类所有虚函数的地址。派生类覆盖虚函数时,表中对应槽位被替换为派生类版本。

  • vptr(虚函数表指针):每个对象内部维护一个隐藏指针,指向其所属类的 vtable。构造时初始化,析构时更新。

  • 调用过程ptr->virtualFunc() → 通过 ptr 的 vptr 找到 vtable → 在 vtable 中查找 virtualFunc 的槽位 → 调用实际函数地址。

关键理解:vtable 是类级别的,vptr 是对象级别的。虚函数的"多态能力"完全由这两者支撑,与函数体内容无关。

易错知识点

  • 虚函数必须有函数体?

    不一定。纯虚函数= 0)可以没有实现,但普通虚函数可以有完整实现,且派生类可以选择调用基类版本(Base::func())。

  • 虚函数不能内联?

    并非绝对。当编译器能确定调用目标时(如通过对象直接调用而非指针/引用),虚函数仍可被内联。但通过基类指针/引用的调用无法内联。

  • 虚函数性能一定差?

    间接调用的开销极小(通常 < 10ns),在绝大多数场景下可忽略。只有在极端性能敏感的内循环中才需考虑。

纯虚函数

虚函数提供“默认实现”,纯虚函数定义“强制接口”。

// 纯虚函数:定义接口,无实现
virtual void func() = 0;

虚函数 vs 普通函数

对比维度普通成员函数虚函数
绑定时机编译时静态绑定运行时动态绑定
调用依据指针/引用的声明类型对象的实际类型
底层机制直接地址调用通过 vptr → vtable 间接调用
性能开销无额外开销一次指针解引用 + 一次间接调用
内联优化可被编译器内联通常无法内联(动态调用目标不确定)

小结

虚函数的本质:一个被 virtual 修饰的普通成员函数,其代码实现与普通函数完全相同,区别仅在于调用时通过 vtable 实现运行时动态绑定。

参考文章

  1. AI

  2. C++图书

相关文章

笔友城堡 - 可定义的个人主页

暂无评论

评论审核已启用。您的评论可能需要一段时间后才能被显示。

none
暂无评论...