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

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

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

Pythonist发布于 2026/3/22更新于 2026/10/269 浏览
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; }
};

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

void fun(Person& p) { p.BuyTicket(); }

void test() {
    Person p; fun(p);
    Student s; fun(s);
}

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. 总结

更多推荐文章

查看全部
  • Python 爬虫:爬取搜狐视频网站视频信息与播放数据
  • 大模型面试核心知识点整理:基础、微调、推理与评测
  • STEP3-VL-10B 部署案例:Supervisor 自动管理 WebUI 服务,3 步启用图文对话
  • llama.cpp 本地部署常见故障排查与优化方案
  • OpenClaw:AI 直接操控电脑的开源工具
  • 前端状态管理方案对比与选型建议
  • Spring MVC 快速入门(下篇):响应处理与报文设置
  • Flutter for OpenHarmony:基于 flutter_rust_bridge 的跨语言高性能计算实践
  • Rust 安装与环境配置教程
  • 使用 Python 脚本实现微信公众号文章自动化发布
  • 2026-03-02 AI 前沿、通信与安全行业日报及关联分析
  • Python 利用 Turtle 模块绘制爱心树
  • JVM 运行时数据区域详解与内存结构分析
  • 基于 SpringBoot 与 Vue 的校园组团平台信息管理系统设计
  • OpenClaw 开源 AI 智能体框架更新与核心功能解析
  • VS Code 配置 AI Agent Skills 指南
  • ESP-SparkBot 开源 AI 桌面机器人 ESP32-S3 核心方案解析
  • 宇树 G1 机器人有线与无线连接配置指南
  • Spring Bean 作用域与生命周期详解:多例模式下的管理边界
  • 鸿蒙金融理财全栈项目:基础架构、数据安全与用户体验

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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