跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 多态:从概念到虚函数表底层原理

C++ 多态是面向对象编程的核心特性,允许同一接口在不同对象上表现不同行为。实现需满足基类指针或引用调用虚函数且完成重写。运行时多态依赖虚函数表机制,通过动态绑定在运行时确定函数地址。理解虚函数表指针、协变、析构函数处理及 override/final 关键字,有助于掌握底层原理并避免内存泄漏等常见陷阱。

Pythonist发布于 2026/3/22更新于 2026/7/2932 浏览
C++ 多态:从概念到虚函数表底层原理

多态

在 C++ 的世界里,多态是面向对象编程的灵魂之一。它让同一个接口在不同对象上表现出不同的行为,从而大大提升了代码的复用性、扩展性与灵活性。本文将带你深入理解多态的核心原理,从概念、实现条件、虚函数、重写规则,到虚函数表与动态绑定机制,逐步揭开多态背后的运行逻辑。

一、多态的概念

通俗来说,就是'多种形态'。多态分为编译时多态(静态多态)和运行时多态(动态多态)。编译时多态主要指函数重载和模板,通过参数匹配在编译期完成;运行时多态则是重点,即调用同一函数时,根据传入对象的不同类型执行不同行为。比如买票,普通人全价,学生优惠,军人优先;再比如动物叫,猫叫喵,狗叫汪。

二、多态的定义及实现

多态发生在继承关系下,基类指针或引用调用虚函数产生不同行为。例如 Student 继承 Person,Person 买票全价,Student 买票半价。

2.1 多态的构成条件
  1. 必须是基类的指针或者引用调用虚函数。
  2. 被调用的函数必须是虚函数,并且完成了虚函数重写/覆盖。

解释:只有基类的指针或引用才能既指向基类对象又指向派生类对象;派生类必须对基类的虚函数完成重写,否则无法体现不同形态。

2.1.2 虚函数

类成员函数前加 virtual 修饰即为虚函数。非成员函数不能加。

class Person {
public:
    virtual void BuyTicket() { cout << "买票全价" << endl; }
};

class Student : public Person {
public:
    virtual void BuyTicket() { cout << "买票半价" << endl; }
};

这样,调用同一函数就能产生不同行为。

2.1.3 虚函数的重写/覆盖

派生类中有跟基类完全相同的虚函数(返回值、名字、参数列表相同),称为重写。注意:派生类重写时不加 virtual 关键字也可以构成重写(因为继承了虚属性),但规范写法建议加上。

#include <iostream>
using namespace std;

class Person {
public:
    virtual void BuyTicket() { cout <<  << endl; }
};

  :  Person {
:
    { cout <<  << endl; }
};

{ p.(); }

{
    Person p; (p);
    Student s; (s);
}

{
    ();
     ;
}
"买票全价"
class
Student
public
public
virtual void BuyTicket()
"买票半价"
void fun(Person& p)
BuyTicket
void test()
fun
fun
int main()
test
return
0

输出显示,虽然 fun 函数接收的是 Person 引用,但实际行为由 p 指向的对象决定。

2.1.4 多态场景的一个选择题

下面程序输出结果是什么?

class A {
public:
    virtual void func(int val = 1) { cout << "A->" << val << endl; }
    virtual void test() { func(); }
};

class B : public A {
public:
    void func(int val = 0) { cout << "B->" << val << endl; }
};

int main(int argc, char* argv[]) {
    B* p = new B; p->test();
    return 0;
}

这道题有些难度。对于虚函数重写,本质是重写实现(函数体)。但在默认参数问题上容易踩坑。

在 p->test() 时,this 指针实际上是 A* 类型。func 函数是否构成重写?由于参数列表不同(默认值不同导致签名差异),实际上并未构成严格的重写,而是隐藏。因此调用 test() 时,使用的是 A::func,默认参数为 1。

所以输出应该是 B->1。

2.1.5 虚函数重写的一些其他问题
  • 协变:派生类重写基类虚函数时,若返回类型为基类对象的指针或引用,派生类可返回派生类对象的指针或引用。
  • 析构函数的重写:基类析构函数设为虚函数后,派生类析构函数无论是否加 virtual 都构成重写。这是为了防止通过基类指针删除派生类对象时只调用基类析构函数导致内存泄漏。
class A {
public:
    virtual ~A() { cout << "~A()" << endl; }
};

class B : public A {
public:
    ~B() { cout << "~B()->delete:" << _p << endl; delete _p; }
protected:
    int* _p = new int[10];
};

int main() {
    A* p1 = new A; A* p2 = new B;
    delete p1; delete p2; // 确保 p2 能正确调用 B 的析构函数
    return 0;
}
2.1.6 override 和 final 关键字

C++11 提供了 override 帮助检测是否重写成功,避免拼写错误导致未重写。final 用于禁止后续类继续重写该函数。

2.1.7 重载/重写/隐藏的对比

重载发生在同一作用域,重写发生在继承体系,隐藏发生在同名函数但不满足重写条件时。

三、纯虚函数和抽象类

虚函数后写 =0 为纯虚函数。包含纯虚函数的类叫抽象类,不能实例化。强制派生类重写。

class Car {
public:
    virtual void Drive() = 0;
};

class Benz : public Car {
public:
    virtual void Drive() { cout << "Benz 舒适" << endl; }
};

int main() {
    Car* pBenz = new Benz; pBenz->Drive();
    return 0;
}

四、多态的原理

4.1 虚函数表指针

含有虚函数的类对象中至少有一个虚函数表指针(vptr)。虚函数表存储虚函数地址。

class Base {
public:
    virtual void Func1() { cout << "Func1()" << endl; }
protected:
    int _b = 1; char _ch = 'x';
};

int main() {
    Base b; cout << sizeof(b) << endl; // 通常大于成员变量之和,含 vptr
    return 0;
}

32 位系统下通常是 12 字节(4 字节 vptr + 4 字节 int + 4 字节 char + padding)。

4.2 多态的原理
4.2.1 多态是如何实现的

底层不再是编译时确定地址,而是运行时通过 vptr 找到 vtable,再查表获取函数地址。

4.2.2 动态绑定与静态绑定
  • 静态绑定:不满足多态条件(如普通函数调用),编译期确定地址。
  • 动态绑定:满足多态条件(指针/引用 + 虚函数),运行时查表确定地址。
4.2.3 虚函数表
  • 同类型对象共用同一张虚表。
  • 派生类虚表包含基类虚函数地址(被重写则覆盖)、派生类新增虚函数地址。
  • 虚表通常存在常量区,末尾可能有 0x00000000 标记(编译器优化)。

总结

多态让'同一接口'在'不同对象'上拥有'不同实现',是让 C++ 面向对象思想真正落地的关键。

目录

  1. 多态
  2. 一、多态的概念
  3. 二、多态的定义及实现
  4. 2.1 多态的构成条件
  5. 2.1.2 虚函数
  6. 2.1.3 虚函数的重写/覆盖
  7. 2.1.4 多态场景的一个选择题
  8. 2.1.5 虚函数重写的一些其他问题
  9. 2.1.6 override 和 final 关键字
  10. 2.1.7 重载/重写/隐藏的对比
  11. 三、纯虚函数和抽象类
  12. 四、多态的原理
  13. 4.1 虚函数表指针
  14. 4.2 多态的原理
  15. 4.2.1 多态是如何实现的
  16. 4.2.2 动态绑定与静态绑定
  17. 4.2.3 虚函数表
  18. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 图像畸变矫正原理及 MATLAB 与 FPGA 实现
  • C++ 搜索引擎项目:日志宏实现与 Server 主逻辑详解
  • Linux 下 libwebkit2gtk-4.1-0 安装实战:从零部署 GTK Web 渲染引擎
  • 大模型(LLM)面试常见问题与解答指南
  • Linux 文件 I/O 本质与内核视角:从 fopen 到 open
  • GitHub 日榜精选:AI Agent、Web 分析与开发工具
  • C++ muduo 库核心组件:事件循环详解
  • Jenkins 配置实战:Python Web UI 自动化测试定时构建与报告生成
  • Android 离线语音识别:使用 Whisper 实现无网络语音转文字
  • C++ 初步学习(需 C 语言基础)
  • 基于 GLM-4.6V-Flash-WEB 构建生产级智能客服系统实战
  • Whisper 模型版本及下载链接
  • Git 分支管理与合并策略实战指南
  • C++ 继承机制详解:概念、访问权限与默认成员函数
  • OpenClaw 飞书机器人配置指南:多渠道 AI 助手集成
  • 即梦 AI 基础操作指南:从绘画到视频生成
  • Jenkins X 结合 AI 重构云原生持续交付体系
  • AionUi:首个开源运行时生成式 UI 框架
  • LLM 解码方式详解:贪心、束搜索与采样策略
  • Python Playwright 在线及离线安装方法

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online