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

C++ 多态详解:虚函数、重写机制与底层原理

C++ 多态是面向对象编程的核心特性,主要分为编译时多态和运行时多态。运行时多态依赖虚函数、基类指针/引用以及虚函数重写实现。底层通过虚函数表(vtable)和虚函数表指针(vptr)完成动态绑定,即在运行时根据对象实际类型查找并调用对应函数。抽象类包含纯虚函数,不可实例化,强制派生类实现特定接口。掌握虚表布局与动态绑定原理,对理解 C++ 对象模型及面试考核至关重要。

星落发布于 2026/3/16更新于 2026/6/1221 浏览
C++ 多态详解:虚函数、重写机制与底层原理

1. 多态的概念

多态分为编译时多态(静态)和运行时多态(动态)。编译时多态主要体现为函数重载和模板,在编译期根据参数类型生成具体代码。本文重点讨论运行时多态(动态),即通过基类指针或引用调用虚函数,实际执行的是派生类的实现。

简单来说,多态允许同一个接口表现出不同的行为。比如买票这个行为,普通人全价,学生优惠,军人优先;再比如动物叫,猫对象传入输出喵,狗对象传入输出汪。这种'同一种操作,不同对象产生不同结果'的机制,就是运行时多态。

2. 多态的定义及实现

多态通常发生在继承体系下。要实现运行时多态,必须满足两个核心条件:

  1. 必须有继承关系:且通过基类的指针或引用来调用函数。
  2. 被调用的函数必须是虚函数:并且在派生类中完成了重写(Override)。
2.1 虚函数

虚函数是支持多态的基础。在类成员函数前加 virtual 修饰符,该函数即为虚函数。注意,非成员函数不能声明为虚函数。

class Person {
public:
    virtual void BuyTicket() {
        cout << "买票 - 全价" << endl;
    }
};
2.2 虚函数的重写/覆盖

当派生类中存在一个与基类虚函数签名完全相同(返回值类型、函数名、参数列表一致)的函数时,称为重写了基类虚函数。即使派生类没有显式添加 virtual 关键字,只要继承了基类的虚函数并进行了重写,依然构成多态,但为了代码可读性,建议显式写出 virtual。

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

class Student : public Person {
public:
    virtual void BuyTicket() {
        cout << "买票 - 打折" << endl;
    }
};

class Animal {
public:
    virtual    {}
};

  :  Animal {
:
    {
        std::cout <<  << std::endl;
    }
};

  :  Animal {
:
    {
        std::cout <<  << std::endl;
    }
};

{
    animal.();
}
}

{
    twg::Cat cat;
    twg::Dog dog;
    twg::(cat);
    twg::(dog);
     ;
}
void
talk
()
const
class
Dog
public
public
virtual void talk() const
"汪汪"
class
Cat
public
public
virtual void talk() const
"(>^ω^<) 喵"
void letsHear(const Animal& animal)
talk
int main()
letsHear
letsHear
return
0

运行上述代码,你会发现 letsHear 接收的是 Animal 引用,但实际调用的是 Cat 或 Dog 的实现,这就是多态的效果。

2.3 虚函数重写的一些其他问题
  • 协变(Covariant Return Types):派生类重写虚函数时,如果返回类型不同,但基类返回基类对象的指针或引用,派生类返回派生类对象的指针或引用,这被称为协变。这是 C++ 允许的特例,实际意义有限,了解即可。
  • 析构函数的重写:基类析构函数设为虚函数后,派生类析构函数无论是否加 virtual,都构成重写。编译器会对析构函数名称做特殊处理,统一视为 destructor,因此不需要名字完全一致。
2.4 override 和 final 关键字

C++11 引入了 override 和 final 来增强安全性。

  • override:用于派生类,明确告知编译器该函数意在重写基类虚函数。如果基类没有对应的虚函数,或者签名不匹配,编译器会报错。这能避免因为拼写错误导致的意外隐藏而非重写。
  • final:用于禁止后续类继续重写当前虚函数。
// error: 包含重写说明符'override'的方法没有重写任何基类方法
class Car {
public:
    virtual void Drive() {}
};

class Benz : public Car {
public:
    virtual void Dirve() override { // 拼写错误,Drive -> Dirve
        cout << "Benz-舒适" << endl;
    }
};

// error: 声明为'final'的函数无法被'Benz::Drive'重写
class Car {
public:
    virtual void Drive() final {};
};

class Benz : public Car {
public:
    virtual void Drive() { // 编译报错
        cout << "Benz-舒适" << endl;
    }
};
2.5 重载/重写/隐藏对比
  • 重载 (Overload):同一作用域内,函数名相同,参数列表不同。属于编译时多态。
  • 重写 (Override):派生类与基类,函数名、参数、返回值完全一致(含 const 属性)。属于运行时多态。
  • 隐藏 (Hide):派生类定义了与基类同名但参数不同的函数,此时基类函数被隐藏,不再参与多态查找。

3. 纯虚函数和抽象类

在虚函数定义后加上 = 0,该函数即为纯虚函数。包含纯虚函数的类称为抽象类。抽象类不能被实例化,只能作为基类使用。如果派生类没有重写所有纯虚函数,它依然是抽象类。

class Car {
public:
    virtual void Drive() = 0; // 纯虚函数
};

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

class BMW : public Car {
public:
    virtual void Drive() {
        cout << "BMW-操控" << endl;
    }
};

int main() {
    // 编译报错:error C2259: 'Car': 无法实例化抽象类
    // Car car; 
    
    Car* pBenz = new Benz;
    pBenz->Drive();
    
    Car* pBMW = new BMW;
    pBMW->Drive();
    
    delete pBenz;
    delete pBMW;
    return 0;
}

4. 多态的原理

理解了概念,我们来看看底层是如何实现的。关键在于虚函数表(vtable)和虚函数表指针(vptr)。

4.1 多态是如何实现的

当一个类含有虚函数时,编译器会在该类对象中插入一个指向虚函数表的指针(vptr)。每个含有虚函数的类都有一张虚函数表,表中存放了该类所有虚函数的地址。

当通过基类指针调用虚函数时,程序不会在编译期确定函数地址,而是先通过 vptr 找到对应对象的虚函数表,再从表中取出函数地址进行调用。这样,基类指针指向派生类对象时,就能找到派生类重写的函数版本。

4.2 动态绑定与静态绑定
  • 静态绑定:对于不满足多态条件的函数调用(如普通成员函数),编译器在编译期就确定了函数地址,效率较高。
  • 动态绑定:对于满足多态条件的虚函数调用,编译器生成代码在运行时通过 vptr 查找函数地址,灵活性高,但有少量性能开销。

可以通过汇编指令观察差异:动态绑定涉及两次内存访问(取 vptr,取函数地址),而静态绑定直接跳转。

4.3 虚函数表的位置

虚函数表本身存储在程序的只读数据段(常量区),而不是栈或堆上。多个同类型的对象共享同一张虚表,不同类型的对象拥有独立的虚表。

派生类的虚表由两部分组成:继承自基类的虚函数地址(可能被重写覆盖)和派生类新增的虚函数地址。通常虚表末尾会有一个结束标记(如 0x00000000),但这取决于编译器实现(VS 系列常见,GCC 可能无)。

验证代码如下:

int main() {
    int i = 0;
    static int j = 1;
    int* p1 = new int;
    const char* p2 = "xxxxxxxx";
    
    printf("栈:%p\n", &i);
    printf("静态区:%p\n", &j);
    printf("堆:%p\n", p1);
    printf("常量区:%p\n", p2);
    
    Base b;
    Derive d;
    Base* p3 = &b;
    Derive* p4 = &d;
    
    printf("Person 虚表地址:%p\n", *(int*)p3);
    printf("Student 虚表地址:%p\n", *(int*)p4);
    printf("虚函数地址:%p\n", &Base::func1);
    printf("普通函数地址:%p\n", &Base::func5);
    
    return 0;
}

在 VS 和 Linux 环境下测试,虚表地址均落在常量区。理解这一内存布局,有助于深入掌握 C++ 对象模型和多态机制。

目录

  1. 1. 多态的概念
  2. 2. 多态的定义及实现
  3. 2.1 虚函数
  4. 2.2 虚函数的重写/覆盖
  5. 2.3 虚函数重写的一些其他问题
  6. 2.4 override 和 final 关键字
  7. 2.5 重载/重写/隐藏对比
  8. 3. 纯虚函数和抽象类
  9. 4. 多态的原理
  10. 4.1 多态是如何实现的
  11. 4.2 动态绑定与静态绑定
  12. 4.3 虚函数表的位置
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 攻防世界 Web 题解:Lottery 与 ics-05 漏洞分析
  • AR 健身教练实践:基于 Rokid CXR-M SDK 的落地方案
  • MySQL 基础(3):数据库与表操作
  • Ubuntu NAT 模式下配置静态 IP 地址的方法
  • SpringBoot 结合 Redis+Caffeine 多级缓存实践解析
  • SHCTF 第三届 Web 部分题目复盘与思路
  • Google AI Studio 全指南:从入门到精通 Gemini 开发
  • 节点小宝 4.0 实测:告别网管式运维,打造极简远程工作流
  • 无人机航测内业处理:iTwin Capture Modeler 建模与土方算量
  • 无人机路径规划算法
  • GitHub Copilot Agent Skills 深度解析:构建跨项目 AI 专属工具箱
  • 二分算法实战:查找元素范围与区间计数
  • 蓝桥杯 2025 省赛 Python B 组题目解析
  • 吴恩达 LLM Agent 工作流 Prompt 设计精华解析
  • Spatial Joy 2025 全球 AR&AI 赛事参赛指南与赛道解析
  • 深入解析 WebView 的概念、功能、应用场景及优劣势
  • 多线程数据竞争解析:互斥锁与原子操作原理
  • llama.cpp 与 llama-server 安装部署指南
  • 修复 Anaconda 开始菜单快捷方式丢失及 mkmenus 报错
  • 哈希算法:冲突解决与高效查找

相关免费在线工具

  • 加密/解密文本

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