跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++

C++ 虚函数与纯虚函数:深入理解多态机制

综述由AI生成C++ 虚函数与纯虚函数是实现多态的核心机制。详细讲解了虚函数的声明语法、重写规则及运行时绑定原理,对比了纯虚函数与抽象类的特性。内容涵盖虚函数表(vtable)与虚函数指针(vptr)的底层工作机制,重点分析了虚析构函数对防止内存泄漏的重要性,并列举了函数签名不匹配、构造/析构中调用虚函数等常见陷阱及其解决方案。最后通过员工薪资计算系统的实战案例,展示了如何利用虚函数构建可扩展的多态架构。

活在当下发布于 2026/3/24更新于 2026/5/85 浏览
C++ 虚函数与纯虚函数:深入理解多态机制

在这里插入图片描述

在 C++ 中,多态是面向对象编程的三大特性之一,而虚函数则是实现这一特性的关键。本文将带你从底层原理到实际应用,彻底搞懂虚函数与纯虚函数的区别、虚函数表的工作机制以及常见陷阱。

一、虚函数的本质与定义

虚函数是 C++ 实现动态多态的核心。通过在基类成员函数前添加 virtual 关键字,允许派生类重写该函数,并在运行时根据对象的实际类型调用对应版本。

1. 声明语法

虚函数的声明必须在基类中进行,语法格式如下:

class BaseClass {
public:
    virtual void functionName(Parameters) {
        // Function body
    }
};

2. 核心特性

  1. 运行时绑定:函数调用关系在程序运行时确定,而非编译时。
  2. 重写规则:派生类重写的函数必须与基类虚函数的函数名、参数列表、返回值类型完全一致(协变类型除外)。
  3. 继承性:基类声明虚函数后,派生类中重写的函数自动成为虚函数,无需重复添加 virtual 关键字(建议保留以增强可读性)。

3. 基础使用案例

#include <iostream>
#include <string>
using namespace std;

// 基类:交通工具
class Vehicle {
public:
    // 虚函数:行驶
    virtual void run() {
        cout << "交通工具正在行驶" << endl;
    }
};

// 派生类:汽车
class Car :  Vehicle {
:
    
    {
        cout <<  << endl;
    }
};


  :  Vehicle {
:
    
    {
        cout <<  << endl;
    }
};

{
    
    Vehicle *v1 =  ();
    Vehicle *v2 =  ();

    
    v1->();
    v2->();

    
     v1;
     v2;
     ;
}
public
public
// 重写基类虚函数
void run() override
"汽车在公路上飞驰"
// 派生类:飞机
class
Plane
public
public
// 重写基类虚函数
void run() override
"飞机在蓝天上翱翔"
int main()
// 基类指针指向派生类对象
new
Car
new
Plane
// 运行时绑定:调用对应派生类的 run 函数
run
run
// 释放内存
delete
delete
return
0

运行结果:

汽车在公路上飞驰
飞机在蓝天上翱翔

注意事项

  • override 关键字用于检测重写的合法性,若函数签名不匹配,编译器会直接报错,建议强制使用。
  • 虚函数不能是 static 静态函数,因为静态函数属于类,不属于对象,无法实现运行时绑定。

二、纯虚函数与抽象类

纯虚函数是没有函数体的虚函数,用于定义接口规范。包含纯虚函数的类称为抽象类,抽象类无法实例化对象,只能作为基类被继承。

1. 声明语法

在虚函数声明的末尾添加 = 0,即可将其定义为纯虚函数:

class AbstractBase {
public:
    virtual void pureVirtualFunc() = 0;
};

2. 核心特性

  1. 无法实例化:不能直接创建抽象类的对象,只能定义指针或引用。
  2. 强制重写:派生类必须重写抽象类的所有纯虚函数,否则派生类也会成为抽象类。
  3. 接口作用:抽象类只定义函数的接口,不实现具体功能,功能的实现由派生类完成。

3. 实战案例:图形绘制系统

#include <iostream>
#include <string>
using namespace std;

// 抽象类:图形(包含纯虚函数)
class Shape {
public:
    string color;
    
    // 纯虚函数:绘制图形
    virtual void draw() = 0;
    
    // 纯虚函数:计算面积
    virtual double getArea() = 0;
    
    // 普通成员函数:设置颜色
    void setColor(string c) {
        color = c;
    }
};

// 派生类:三角形
class Triangle : public Shape {
private:
    double base;   // 底
    double height; // 高
public:
    Triangle(double b, double h) : base(b), height(h) {}

    // 必须重写所有纯虚函数
    void draw() override {
        cout << "绘制一个" << color << "的三角形" << endl;
    }
    
    double getArea() override {
        return 0.5 * base * height;
    }
};

// 派生类:正方形
class Square : public Shape {
private:
    double side; // 边长
public:
    Square(double s) : side(s) {}

    void draw() override {
        cout << "绘制一个" << color << "的正方形" << endl;
    }
    
    double getArea() override {
        return side * side;
    }
};

int main() {
    // 抽象类不能实例化对象
    // Shape s; // 编译错误
    
    // 抽象类指针指向派生类对象
    Shape *shape1 = new Triangle(10, 5);
    Shape *shape2 = new Square(8);
    
    shape1->setColor("红色");
    shape2->setColor("蓝色");
    
    shape1->draw();
    cout << "三角形面积:" << shape1->getArea() << endl;
    
    shape2->draw();
    cout << "正方形面积:" << shape2->getArea() << endl;
    
    delete shape1;
    delete shape2;
    return 0;
}

运行结果:

绘制一个红色的三角形
三角形面积:25
绘制一个蓝色的正方形
正方形面积:64

三、虚函数与纯虚函数的核心区别

特性虚函数纯虚函数
函数体有函数体,可提供默认实现无函数体,仅定义接口
类的性质基类可以实例化对象包含纯虚函数的类是抽象类,无法实例化
派生类要求派生类可重写,也可不重写派生类必须重写所有纯虚函数
使用场景基类需要提供默认功能实现基类仅定义接口,功能由派生类实现

四、虚函数表的底层工作机制

C++ 动态多态的底层实现依赖虚函数表(vtable) 和虚函数指针(vptr),理解其原理能帮助我们规避开发中的隐藏陷阱。

1. 基本概念

  • 虚函数表(vtable):当类中包含虚函数时,编译器会为该类生成一个全局的虚函数表。表中存储的是类中所有虚函数的地址。
  • 虚函数指针(vptr):每个对象的内存布局中,会包含一个隐藏的虚函数指针。该指针指向所属类的虚函数表。
  • 继承与重写:派生类的虚函数表会继承基类的虚函数表。如果派生类重写了某个虚函数,会用新的函数地址覆盖虚函数表中对应的位置。

2. 工作流程

  1. 程序编译时,编译器为每个包含虚函数的类生成虚函数表。
  2. 当创建对象时,编译器自动为对象添加虚函数指针 vptr,并让其指向所属类的虚函数表。
  3. 程序运行时,通过基类指针或引用调用虚函数时,会先通过 vptr 找到虚函数表。
  4. 根据虚函数表中的函数地址,调用对应类的函数版本,实现动态多态。

3. 内存布局示例

以 Vehicle 基类和 Car 派生类为例,其虚函数表的内存布局如下:

  • Vehicle 类的虚函数表:&Vehicle::run
  • Car 类的虚函数表:&Car::run(覆盖基类的函数地址)

当 Vehicle* v = new Car() 时,v 指向的对象的 vptr 会指向 Car 类的虚函数表,调用 v->run() 时,实际执行的是 Car::run。

关键注意点

  • 虚函数表属于类,所有对象共享同一个虚函数表,节省内存空间。
  • 虚函数指针属于对象,每个对象都有独立的 vptr,占用 4 字节(32 位系统)或 8 字节(64 位系统)内存。

五、虚析构函数:解决派生类资源泄漏问题

当基类指针指向派生类对象并通过 delete 释放时,如果基类析构函数不是虚函数,会导致派生类的析构函数无法被调用,从而引发内存泄漏。

1. 问题场景演示(非虚析构函数)

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base 构造函数被调用" << endl;
    }
    ~Base() {
        cout << "Base 析构函数被调用" << endl;
    }
    // 非虚析构
};

class Derived : public Base {
private:
    int* data; // 动态分配的内存
public:
    Derived() {
        data = new int[10];
        cout << "Derived 构造函数被调用" << endl;
    }
    ~Derived() {
        delete[] data;
        cout << "Derived 析构函数被调用" << endl;
    }
};

int main() {
    Base *p = new Derived();
    delete p; // 仅调用基类析构函数,派生类析构未调用
    return 0;
}

运行结果(存在内存泄漏):

Base 构造函数被调用
Derived 构造函数被调用
Base 析构函数被调用

问题分析:Derived 类中动态分配的 data 数组未被释放,导致内存泄漏。

2. 解决方案:虚析构函数

将基类的析构函数声明为虚函数,即可实现派生类析构函数的正确调用:

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base 构造函数被调用" << endl;
    }
    virtual ~Base() {
        cout << "Base 析构函数被调用" << endl;
    }
    // 虚析构
};

class Derived : public Base {
private:
    int* data;
public:
    Derived() {
        data = new int[10];
        cout << "Derived 构造函数被调用" << endl;
    }
    ~Derived() override {
        delete[] data;
        cout << "Derived 析构函数被调用" << endl;
    }
};

int main() {
    Base *p = new Derived();
    delete p; // 先调用派生类析构,再调用基类析构
    return 0;
}

运行结果(资源正确释放):

Base 构造函数被调用
Derived 构造函数被调用
Derived 析构函数被调用
Base 析构函数被调用

开发规范:只要类中包含虚函数,就应该将析构函数声明为虚析构函数,避免内存泄漏。

六、虚函数的常见陷阱与解决方案

1. 函数签名不匹配导致重写失败

问题:派生类重写的函数与基类虚函数的参数列表或返回值类型不一致,导致无法触发多态。 解决方案:严格保证函数签名一致,使用 override 关键字检测重写合法性。

2. 构造函数和析构函数中调用虚函数

问题:构造函数和析构函数执行时,对象的类型是当前类的类型,而非派生类类型,此时调用虚函数无法实现多态。 解决方案:避免在构造函数和析构函数中调用虚函数,若需要调用,直接使用普通函数。

3. 忽视虚函数的性能开销

问题:虚函数调用需要通过虚函数表间接寻址,比普通函数调用多一层开销,在高性能场景下可能影响效率。 解决方案:在对性能要求极高的场景,尽量减少虚函数的使用;可以通过模板等静态多态方式替代。

七、实战案例:基于虚函数的员工薪资计算系统

需求设计一个员工薪资计算系统,支持普通员工、技术员工、管理人员三种角色,不同角色的薪资计算规则不同,要求利用虚函数实现动态多态,新增角色时无需修改原有代码。

1. 需求分析

  1. 抽象基类 Employee:包含纯虚函数 calculateSalary,用于计算薪资。
  2. 派生类 RegularEmployee:普通员工,薪资 = 基本工资。
  3. 派生类 TechEmployee:技术员工,薪资 = 基本工资 + 技术补贴。
  4. 派生类 Manager:管理人员,薪资 = 基本工资 + 管理补贴。

2. 完整代码实现

#include <iostream>
#include <string>
using namespace std;

// 抽象基类:员工
class Employee {
protected:
    string name;
    double baseSalary; // 基本工资
public:
    Employee(string n, double bs) : name(n), baseSalary(bs) {}

    // 纯虚函数:计算薪资
    virtual double calculateSalary() = 0;

    // 虚析构函数
    virtual ~Employee() {}

    // 普通函数:获取姓名
    string getName() {
        return name;
    }
};

// 派生类:普通员工
class RegularEmployee : public Employee {
public:
    RegularEmployee(string n, double bs) : Employee(n, bs) {}
    
    double calculateSalary() override {
        return baseSalary;
    }
};

// 派生类:技术员工
class TechEmployee : public Employee {
private:
    double techAllowance; // 技术补贴
public:
    TechEmployee(string n, double bs, double ta) : Employee(n, bs), techAllowance(ta) {}
    
    double calculateSalary() override {
        return baseSalary + techAllowance;
    }
};

// 派生类:管理人员
class Manager : public Employee {
private:
    double manageAllowance; // 管理补贴
public:
    Manager(string n, double bs, double ma) : Employee(n, bs), manageAllowance(ma) {}
    
    double calculateSalary() override {
        return baseSalary + manageAllowance;
    }
};

// 通用函数:打印员工薪资
void printSalary(Employee *emp) {
    cout << "员工 " << emp->getName() << " 的薪资为:" << emp->calculateSalary() << " 元" << endl;
}

int main() {
    Employee *emp1 = new RegularEmployee("张三", 5000);
    Employee *emp2 = new TechEmployee("李四", 6000, 2000);
    Employee *emp3 = new Manager("王五", 8000, 3000);
    
    printSalary(emp1);
    printSalary(emp2);
    printSalary(emp3);
    
    delete emp1;
    delete emp2;
    delete emp3;
    return 0;
}

3. 运行结果

员工 张三 的薪资为:5000 元
员工 李四 的薪资为:8000 元
员工 王五 的薪资为:11000 元

八、本章总结

  • 虚函数通过 virtual 关键字声明,支持派生类重写,实现运行时多态;纯虚函数无函数体,用于定义接口,包含纯虚函数的类是抽象类。
  • 虚函数的底层实现依赖虚函数表和虚函数指针,虚函数表存储虚函数地址,虚函数指针指向虚函数表。
  • 虚析构函数是解决派生类资源泄漏的关键,只要类中包含虚函数,就应该将析构函数声明为虚函数。
  • 虚函数的核心优势是支持代码扩展,符合开闭原则,是大型 C++ 项目设计的核心机制。

目录

  1. 一、虚函数的本质与定义
  2. 1. 声明语法
  3. 2. 核心特性
  4. 3. 基础使用案例
  5. 二、纯虚函数与抽象类
  6. 1. 声明语法
  7. 2. 核心特性
  8. 3. 实战案例:图形绘制系统
  9. 三、虚函数与纯虚函数的核心区别
  10. 四、虚函数表的底层工作机制
  11. 1. 基本概念
  12. 2. 工作流程
  13. 3. 内存布局示例
  14. 五、虚析构函数:解决派生类资源泄漏问题
  15. 1. 问题场景演示(非虚析构函数)
  16. 2. 解决方案:虚析构函数
  17. 六、虚函数的常见陷阱与解决方案
  18. 1. 函数签名不匹配导致重写失败
  19. 2. 构造函数和析构函数中调用虚函数
  20. 3. 忽视虚函数的性能开销
  21. 七、实战案例:基于虚函数的员工薪资计算系统
  22. 1. 需求分析
  23. 2. 完整代码实现
  24. 3. 运行结果
  25. 八、本章总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 基于大数据爬虫+Hadoop+Python 的月季销售数据可视化系统开题报告
  • 分隔链表算法详解:双虚拟头节点拆解合并法
  • Flood Fill 算法实战:从图像渲染到岛屿问题
  • Llama 3.1 开源发布:LLM 新里程碑与部署指南
  • Python 基础语法
  • 基于 Whisper 的多语种交互异常检测框架实战
  • Web 自动化测试入门指南:从概念到 Selenium 实战
  • 从零实现Vivado下载与初始设置:FPGA开发第一步
  • Z-Image-Turbo LoRA 教程:自定义负面提示词与后端策略
  • OpenClaw 安装与飞书机器人配置实战指南
  • 前端设计与布局常用术语中英对照速查表
  • XGBoost Python 机器学习实战教程与参数详解
  • 2025 主流 AI 编程工具定价对比:Cursor、Windsurf、Kiro、Zed 与 VS Code
  • Python Pandas 安装踩坑与解决方案
  • MySQL DQL 全面解析
  • Python 绘图工具详解:使用 Matplotlib、Seaborn 和 Pyecharts 绘制散点图
  • Stable Diffusion 人脸修复插件 ADetailer 使用教程
  • 机器人身体结构与人体仿生学:人形机器人躯干系统
  • IT 行业转型趋势:为何网络安全成为青年首选赛道
  • SQL Server 中生成雪花 ID(Snowflake ID) 的方法

相关免费在线工具

  • 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

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online