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

C++ 多态机制详解:概念、实现与原理

C++ 多态是面向对象编程的核心特性,允许不同对象对同一消息做出响应。通过虚函数和继承实现运行时绑定,需满足继承关系、虚函数及指针引用调用条件。涉及虚函数表(vtable)原理,单继承与多继承结构不同。抽象类包含纯虚函数不可实例化。C++11 引入 override 和 final 关键字增强安全性。静态成员函数不能为虚函数,构造函数不能为虚函数,析构函数建议设为虚函数。菱形继承存在二义性需虚继承解决。

RefactorPro发布于 2026/3/23更新于 2026/10/1882 浏览
C++ 多态机制详解:概念、实现与原理

1. 多态的概念

1.1 概念

通俗来讲,多态就是多种形态。具体一些就是当去完成某个行为的时候,不同的对象去完成时会产生出不同的状态。

2. 多态的定义与实现

2.1 多态的构成条件

多态是在不同继承关系的类对象中,调用同一函数产生不同的行为。例如 Student 继承了 Person,Person 对象买票全价,Student 对象买票半价。在继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数。
  2. 被调用的函数必须是虚函数,且派生类对基类的虚函数进行重写。
#include <iostream>
using namespace std;

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

class Child : public Adult {
public:
    virtual void BuyTicket() {
        cout << "买票----半价" << endl;
    }
};

void Func(Adult& a) {
    a.BuyTicket();
}

int main() {
    Adult a;
    Child c;
    Func(a);
    Func(c);
    return 0;
}

2.2 虚函数

概念:被 virtual 修饰的类成员函数称为虚函数。

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

2.3 虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类的虚函数返回值类型、函数名、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

2.3.1 虚函数重写的两个例外
2.3.1.1 协变

基类与派生类函数的返回值不同,基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时,允许重写。

2.3.1.2 析构函数的重写

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加 virtual 关键字,都与基类的析构函数构成重写。虽然基类与派生类析构函数名字不同,但编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理为 destructor。

#include <iostream>
using namespace std;

class Person {
public:
    virtual ~Person() {
        cout << "~Person()" << endl;
    }
};

class Student : public Person {
    virtual ~Student() {
        cout << "~Student" << endl;
    }
};

int main() {
    Person* p1 = new Person;
    Person* p2 = new Student;
    delete p1;
    delete p2;
    return 0;
}

2.4 C++11 override 和 final

C++ 对函数重写的要求比较严格,有些情况下由于疏忽可能导致函数名字次序写反而无法构成重载,这种错误在编译期间不会报出。因此 C++11 提供了 override 和 final 两个关键字,帮助用户检测是否重写。

2.4.1 final 关键字

修饰虚函数,表示该虚函数不能再被重写。

#include <iostream>
using namespace std;

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

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

int main() {
    return 0;
}

final 也可以修饰类,表示该类不能被继承。

#include <iostream>
using namespace std;

class Car final {
public:
    Car() { }
};

class Benz : public Car { }; // 编译报错

int main() {
    return 0;
}
2.4.2 override 关键字

检查派生类虚函数是否重写了基类某个虚函数,如果没有重写则编译报错。

#include <iostream>
using namespace std;

class Car {
public:
    // virtual void drive() { }
};

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

int main() {
    return 0;
}

2.5 重载、重写、隐藏的对比

  • 重载:两个函数在同一个作用域,函数名相同/参数不同。
  • 重写(覆盖):两个函数分别在基类与派生类的作用域,函数名/参数/返回值都必须相同(满足三同,协变除外),两个函数必须是虚函数。
  • 重定义(隐藏):两个函数分别在基类与派生类的作用域,函数名相同,不构成重写即为重定义。

3. 抽象类

3.1 概念

在虚函数的后面加上 = 0,则这个函数被称为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能够实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

#include <iostream>
using namespace std;

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

class Benz : public Car {
public:
};

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

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

3.2 接口继承与实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

4. 多态的原理

4.1 虚函数表

含有虚函数的类中至少都有一个虚函数表指针(vptr),因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。一个对象中的虚函数指针通常放在对象的前面(有些平台可能会放到对象的最后面,这跟平台有关系)。

#include <iostream>
using namespace std;

class Base {
public:
    virtual void Func1() {
        cout << "Func1" << endl;
    }
private:
    int _b = 1;
};

int main() {
    Base b;
    cout << sizeof(b) << endl;
    return 0;
}

4.2 多态的原理

多态的实现依赖于动态绑定。通过基类指针或引用调用虚函数时,程序会在运行时根据实际指向的对象类型查找对应的虚函数表,从而调用正确的函数版本。

4.3 动态绑定与静态绑定

  • 静态绑定:编译期确定调用哪个函数,如普通函数调用。
  • 动态绑定:运行期确定调用哪个函数,如虚函数调用。

5. 单继承与多继承关系的虚函数表

5.1 单继承中的虚函数表

单继承中,派生类通常会共享基类的虚函数表,并添加自己的虚函数到表中。

#include <iostream>
using namespace std;

class Base {
public:
    virtual void Func1() { cout << "Base::Func1()" << endl; }
    virtual void Func2() { cout << "Base::Func2()" << endl; }
private:
    int _a = 1;
};

class Derive : public Base {
public:
    virtual void Func1() { cout << "Derive::Func1()" << endl; }
    virtual void Func3() { cout << "Derive::Func3()" << endl; }
    virtual void Func4() { cout << "Derive::Func4()" << endl; }
private:
    int _b = 2;
};

// 打印对象虚基表,对象虚基表本质是一个函数指针数组
typedef void(*Vfptr)();
void PrintVfptr(Vfptr* vft) {
    for (size_t i = 0; i < 4; i++) {
        cout << vft[i] << "->";
        vft[i]();
    }
}

int main() {
    Base b;
    Derive d;
    Vfptr* ptr = (Vfptr*)(*(int*)(&d));
    PrintVfptr(ptr);
    return 0;
}

5.2 多继承中的虚函数表

多继承中,每个基类都有独立的虚函数表。派生类会拥有多个虚函数表指针,分别对应不同的基类。

#include <iostream>
using namespace std;

typedef void (*Vfptr)();

class Base1 {
public:
    virtual void func1() { cout << "Base1::func1()" << endl; }
    virtual void func2() { cout << "Base1::func2()" << endl; }
private:
    int _b1;
};

class Base2 {
public:
    virtual void func1() { cout << "Derive:func1()" << endl; }
    virtual void func3() { cout << "Derive:func3()" << endl; }
private:
    int _d1;
};

class Derive : public Base1, public Base2 {
    virtual void func1() { cout << "Derive:func1()" << endl; }
    virtual void func3() { cout << "Derive:func3()" << endl; }
private:
    int _d1;
};

void PrintTable(Vfptr Vtable[]) {
    cout << "虚表地址>" << Vtable << endl;
    for (size_t i = 0; Vtable[i] != nullptr; i++) {
        cout << "第" << i << "个虚函数地址:0X" << Vtable[i] << endl;
        Vtable[i]();
    }
    cout << endl;
}

int main() {
    Derive d;
    /* 取出 d 对象的头 4 个字节,就是虚表的指针 */
    Vfptr* vTableb1 = (Vfptr*)(*(int*)&d);
    PrintTable(vTableb1);
    /* 强转成 char *,char* 类型的指针每次解引用跳过一个字节 */
    Vfptr* vTableb2 = (Vfptr*)(*(int*)((char*)&d + sizeof(Base1)));
    PrintTable(vTableb2);
    return 0;
}

6. 多态相关的问题

  • 什么是多态? 通俗来说,就是多种形态,具体一些就是去完成某个行为,当不同的对象去完成的时候会产生出不同的状态。

  • 什么是重载、重写(覆盖)、重定义(隐藏)?

    • 重载:两个函数在同一个作用域,函数名相同/参数不同。
    • 重写(覆盖):两个函数分别在基类与派生类的作用域,要满足三同(函数名/参数/返回值都必须相同,协变除外),两个函数都必须是虚函数。
    • 重定义(隐藏):两个函数分别在基类和派生类的作用域,函数名相同,两个基类和派生类的同名函数(不构成重写就是重定义)。
  • inline(内联函数)可以是虚函数吗? 可以,不过编译器如果忽略 inline 属性,那么这个函数就不再是 inline 函数,而是会把虚函数表放到虚函数中。

  • 静态成员函数可以是虚函数吗? 不能,因为静态成员函数没有 this 指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

  • 构造函数可以是虚函数吗? 不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

  • 析构函数可以是虚函数吗? 可以,并且最好把基类的析构函数定义成虚函数。

  • 什么场景下析构函数是虚函数? 如果基类的析构函数为虚函数,此时派生类的析构函数只要定义,无论是否加 virtual 关键字,都与基类的析构函数构成重写。

  • 对象是访问普通函数快还是虚函数快?

    1. 首先如果是普通对象,是一样快的。
    2. 如果是指针对象或者引用对象,则调用普通函数快一些,因为构成了多态,运行时调用虚函数需要到虚函数表中去查找。
  • 虚函数表是在什么阶段生成的? 虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

  • C++ 菱形继承的问题,虚继承的原理? 菱形继承造成了数据冗余和二义性。

目录

  1. 1. 多态的概念
  2. 1.1 概念
  3. 2. 多态的定义与实现
  4. 2.1 多态的构成条件
  5. 2.2 虚函数
  6. 2.3 虚函数的重写
  7. 2.3.1 虚函数重写的两个例外
  8. 2.3.1.1 协变
  9. 2.3.1.2 析构函数的重写
  10. 2.4 C++11 override 和 final
  11. 2.4.1 final 关键字
  12. 2.4.2 override 关键字
  13. 2.5 重载、重写、隐藏的对比
  14. 3. 抽象类
  15. 3.1 概念
  16. 3.2 接口继承与实现继承
  17. 4. 多态的原理
  18. 4.1 虚函数表
  19. 4.2 多态的原理
  20. 4.3 动态绑定与静态绑定
  21. 5. 单继承与多继承关系的虚函数表
  22. 5.1 单继承中的虚函数表
  23. 5.2 多继承中的虚函数表
  24. 6. 多态相关的问题

更多推荐文章

查看全部
  • SpringBoot 原理详解:配置、Bean 管理与自动配置
  • 多模态大模型垂直微调实战:Qwen3-VL-4B-Thinking 与 Llama Factory
  • AMD 显卡 llama.cpp Vulkan 后端兼容与优化指南
  • 昇腾 Ascend C 算子开发:从语法基础到全链路编程实践
  • 从零搭建你的第一个 AI Agent
  • Python 开发 MongoDB 数据库 MCP Server 实战指南
  • 万方 AIGC 检测未通过的处理方案与工具对比
  • 人大金仓 KingBase 更换授权文件(Linux 和 Windows)
  • Java 线程池:核心原理、参数配置与实战应用
  • Python PySide6 跨平台桌面应用开发实战
  • AI 大模型时代程序员的职业挑战与应对策略
  • Xilinx FPGA 外部 SPI Flash 启动时钟频率配置
  • 英伟达 GTC 2026 发布 Rubin 架构与新一代推理芯片,AI 智能体时代开启
  • GitLab 认证失败解决:使用 Personal Access Token 替代密码
  • C++二叉搜索树:从插入到删除,以及Key/Value模型实现
  • Java 异常处理:核心机制与实战
  • GitHub Copilot 配合 Figma MCP 还原设计稿生成前端代码
  • 三大扩散模型性能对比:Z-Image-Turbo、ComfyUI 与 Stable Diffusion
  • 为什么在人工智能时代选择学习 Python 编程语言
  • 昆仑万维 Skywork-R1V3 开源:38B 多模态推理模型与高考数学表现

相关免费在线工具

  • 加密/解密文本

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