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

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

C++ 多态指不同对象调用同一函数产生不同行为。实现需继承、虚函数及指针或引用调用。核心涉及虚函数重写、协变规则、析构函数处理及 override/final 关键字。底层通过虚函数表和虚函数指针实现动态绑定。抽象类包含纯虚函数不可实例化。单继承与多继承下虚函数表结构不同。静态成员函数、构造函数不能为虚函数,析构函数建议设为虚函数。

DebugKing发布于 2026/3/22更新于 2026/5/26 浏览
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;
    }
};

/* 
 * 多态的条件
 * 1. 必须要有继承关系
 * 2. 必须要有虚函数 (父类的虚函数和子类的虚函数,要求三同 (函数名,参数名,返回值)
 */
void Func(Adult& a) {
    a.BuyTicket();
}

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

2.2 虚函数

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

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

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++ 对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来 debug 会得不偿失,因此: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;
}
#include <iostream>
using namespace std;
// C++11 的方法:final 修饰的类叫最终类,不能继承
class Car final {
public:
    // C++98 的方法:父类的构造函数私有
    // 子类的构造无法生成和实现,导致子类对象无法实例化
    Car() { }
};
class Benz : public Car {
public:
};

int main() {
    Benz b;
    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 虚函数表

4.1.1 代码示例
#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;
}

通过测试可以发现对象是 8 字节。除了 _b 成员,还多了一个 _vfptr 放在对象的前面 (有些平台可能会放到对象的最后面,这个跟平台有关系),对象中的这个指针叫做虚函数指针表 (v 代表 virtual,f 代表 function)。一个含有虚函数的类中至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

4.1.2 代码示例
#include <iostream>
using namespace std;
class Base {
public:
    virtual void Func1() {
        cout << "Base:Func1()" << endl;
    }
    virtual void Func2() {
        cout << "Base:Func2()" << endl;
    }
    virtual void Func3() {
        cout << "Base:Func3()" << endl;
    }
private:
    int _b = 1;
};
class Derive : public Base {
public:
    virtual void Func1() {
        cout << "Derive:Func1()" << endl;
    }
    virtual void Func2() {
        cout << "Derive:Func2()" << endl;
    }
private:
    int _d = 2;
};

int main() {
    Base b;
    Derive d;
    return 0;
}

4.2 多态的原理

#include <iostream>
using namespace std;
class Person {
public:
    virtual void BuyTicket() {
        cout << "买票 - 全价" << endl;
    }
};
class Student : public Person {
public:
    virtual void BuyTicket() {
        cout << "买票 - 半价" << endl;
    }
};

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

int main() {
    Person Mike;
    Func(Mike);
    Student Johnson;
    Func(Johnson);
    return 0;
}

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;
    /* 
     * (*(int*)(&d)) 强制类型转换为指针类型并且解引用,那么每次在访问的时候只访问四个字节的数据
     * 取出 d 对象的头 4 个字节,就是虚表的指针,虚表的本质是存了一个虚函数的指针数组,这个数组最后面放了一个 Nullptr
     */
    Vfptr* vTableb1 = (Vfptr*)(*(int*)&d);
    PrintTable(vTableb1);
    /* 
     * 强转成 char *,char*类型的指针每次解引用跳过一个字节,
     */
    Vfptr* vTableb2 = (Vfptr*)(*(int*)((char*)&d + sizeof(Base1)));
    PrintTable(vTableb2);
    return 0;
}

6. 多态相关的问题

什么是多态?

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

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

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

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.1.1 代码示例
  20. 4.1.2 代码示例
  21. 4.2 多态的原理
  22. 4.3 动态绑定与静态绑定
  23. 5. 单继承与多继承关系的虚函数表
  24. 5.1 单继承中的虚函数表
  25. 5.2 多继承中的虚函数表
  26. 6. 多态相关的问题
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Python 3.14.0 安装与使用指南
  • 基于Java的供应链预测性分析引擎构建
  • 2026 年 AI 漫剧工具排行榜:11 款软件横向对比与功能解析
  • FPGA 商用级 ISP:动态坏点校正 DPCC 的滑窗架构与并行判决实现
  • 2026年AI编程工具全景图:GitHub Copilot vs Cursor vs Codeium,我如何选择?
  • MySQL 9.6.0 Windows 安装配置与避坑指南
  • 动态环境下多无人机协同路径规划与防撞的 Matlab 实现
  • FPGA面试题汇总整理(一)
  • OpenClaw 本地 AI 代理技术架构与实战部署详解
  • 数据结构复习:链表详解与 Java LinkedList 应用
  • webdav-server 轻量级 WebDAV 服务器部署与配置指南
  • AWS SAP-C02 專業架構師認證介紹
  • Redis 压缩列表、Listpack 及哈希表扩容原理
  • 希尔排序算法详解:原理、实现与优化
  • 基于 Protege、Neo4j 与 ECharts 的教育领域知识图谱可视化实战
  • 微信小程序跳转外部链接:WebView 与复制链接方案
  • VS Code GitHub 扩展登录报错:尚未完成授权此扩展使用 GitHub 的操作
  • Ubuntu 系统禁用蓝牙自动启动的方法
  • Stable Diffusion v1.5 实战:电商海报与创意图像生成指南
  • C++ STL list 容器实现详解

相关免费在线工具

  • 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