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

C++ 多态的概念、实现与原理详解

C++ 多态指不同继承关系的类对象调用同一函数产生不同行为。实现需满足继承、虚函数及基类指针/引用调用条件。原理基于虚函数表与动态绑定。内容包含虚函数重写规则、override/final 关键字、抽象类、单/多继承虚表结构及常见问题解答。

1951018925发布于 2026/3/15更新于 2026/6/821 浏览
C++ 多态的概念、实现与原理详解

1. 多态的概念

1.1 概念

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

2. 多态的定义与实现

2.1 多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。譬如 Student 继承了 Person,Person 对象买票全价,Student 对象买票半价。那么在继承中要构成多态还有两个条件:1. 必须通过基类的指针或者引用调用虚函数;2. 被调用的函数必须是虚函数,且派生类对基类的虚函数进行重写。

#define _CRT_SECURE_NO_WARNINGS
#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 Person {
public:
    virtual void BuyTicket() {
        cout << "买票 - 全价" << endl;
    }
};

2.3 虚函数的重写

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

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

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

2.3.1.2 析构函数的重写

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

#define _CRT_SECURE_NO_WARNINGS
#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 关键字

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

#define _CRT_SECURE_NO_WARNINGS
#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;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//C++11 的方法:final 修饰的类叫最终类,不能继承
class Car final {
public:
private:
    // C++98 的方法:父类的构造函数私有
    // 子类的构造无法生成和实现,导致子类对象无法实例化
    Car() { }
};
class Benz :public Car {
public:
};
int main() {
    Benz b;
    return 0;
}
2.4.2 override 关键字

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

#define _CRT_SECURE_NO_WARNINGS
#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,则这个函数被称为纯虚函数。包含纯虚函数的类叫做抽象类 (也叫接口类),抽象类不能够实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

#define _CRT_SECURE_NO_WARNINGS
#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 代码 1
#define _CRT_SECURE_NO_WARNINGS
#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;
}

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

4.1.2 代码 2
#define _CRT_SECURE_NO_WARNINGS
#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 多态的原理

#define _CRT_SECURE_NO_WARNINGS
#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 单继承中的虚函数表

#define _CRT_SECURE_NO_WARNINGS
#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 多继承中的虚函数表

#define _CRT_SECURE_NO_WARNINGS
#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. 多态相关的问题

什么是多态。

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

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

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

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 代码 1
  20. 4.1.2 代码 2
  21. 4.2 多态的原理
  22. 4.3 动态绑定与静态绑定
  23. 5. 单继承与多继承关系的虚函数表
  24. 5.1 单继承中的虚函数表
  25. 5.2 多继承中的虚函数表
  26. 6. 多态相关的问题
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Naiz AI 技术解析:从语义到像素的视频本地化方案
  • 前端国际化最佳实践指南
  • Cloudflare WAF 防护 WordPress 方案:托管质询与规则组合应对 CC 攻击
  • 飞算 JavaAI 智能问答实战:从代码解释到单元测试生成
  • OpenClaw 开源 AI 智能体框架更新与核心功能解析
  • 10 分钟语音数据构建专业级变声模型:RVC WebUI 实践指南
  • C++ 二叉搜索树详解:增删查改与 Key/Value 场景实现
  • 前端 PDF 导出实战:JSPDF 与 HTML2Canvas 技术详解
  • GPT-4 微调 API 安全漏洞分析:绕过防护与滥用风险
  • GitHub 启用双因素身份验证(2FA)实战指南:使用 TOTP.app 配置动态验证码
  • 大模型时代程序员如何应对 AI 挑战与角色转型
  • Linux 进程间通信进阶:管道与共享内存详解
  • Python 爬虫:爬取搜狐视频网站视频信息与播放数据
  • Mac 上搭建 Docker Compose 并部署应用实战
  • C++ 构造函数初始化列表详解
  • VS Code 中的 Python 代码格式化插件
  • OpenClaw QQ 机器人接入指南
  • Stable Diffusion WebUI 本地部署指南:CUDA、cuDNN 及 PyTorch GPU 环境配置
  • ComfyUI 本地部署及 Stable Diffusion 使用指南
  • 昇腾 NPU 部署 Llama 2 模型的性能测试与优化实践

相关免费在线工具

  • 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