C++的核心--多态
目录引言一、多态的概念二、多态的定义及实现一构成条件二虚函数的深入理解三虚函数的重写覆盖三、抽象类一概念二接口继承和实现继承四、多态的原理一虚函数表二动态绑定与静态绑定五、单继承和多继承关系中的虚函数表一单继承中的虚函数表二多继承中的虚函数表六、C11 override 和 final一final二override七、重载、覆盖重写、隐藏重定义的对比八、结语引言在 C 编程世界里多态是一个强大且迷人的特性。它允许我们以统一的接口来处理不同类型的对象实现 “一种接口多种形态”。这不仅提升了代码的灵活性与可维护性还极大地增强了程序的扩展性。今天就让我们深入探究 C 多态的奥秘。一、多态的概念多态通俗来讲就是同一行为在不同对象上表现出不同的状态。就像现实生活中买票这个行为普通人买票是全价学生买票是半价军人买票可能有优先特权。在 C 中多态使得我们可以用相同的函数调用根据对象类型的不同执行不同的操作。二、多态的定义及实现一构成条件1. 虚函数被 virtual 关键字修饰的类成员函数。--子类可以不加virtual推荐都写class Person { public: virtual void BuyTicket() { std::cout 买票 - 全价 std::endl; } };2. 虚函数覆盖派生类中定义与基类虚函数返回值类型、函数名字、参数列表完全相同的虚函数。class Student : public Person { public: virtual void BuyTicket() { std::cout 买票 - 半价 std::endl; } };3. 通过基类指针或引用调用虚函数不可用子类指针或引用调用虚函数- 使用父类指针或引用可以指向不同的子类对象通过相同的父类接口来调用不同子类实现的方法从而体现多态性。如果仅使用子类指针或引用只能调用子类自身定义或重写的方法无法通过统一的父类接口来实现多种不同的行为也就无法充分体现多态的特性。不可用父类对象父类对象调用的方法是在父类中定义的方法不会调用子类重写后的方法。多态的核心是根据对象的实际类型来决定调用哪个类中重写的方法而父类对象的类型是固定的不能根据不同的情况表现出不同的行为无法体现多态性。class Person { public: virtual void BuyTicket() { std::cout 买票 - 全价 std::endl; } }; class Student : public Person { public: virtual void BuyTicket() { std::cout 买票 - 半价 std::endl; } }; void Func(Person people) { people.BuyTicket(); } int main() { Person Mike; Student Johnson; Func(Mike); Func(Johnson); return 0; }在上述代码中 Func 函数通过基类 Person 的引用调用 BuyTicket 虚函数根据传入对象是 Person 还是 Student 会分别执行对应的 BuyTicket 函数版本实现多态行为。二虚函数的深入理解虚函数是实现多态的关键。当类中包含虚函数时编译器会为该类生成一个虚函数表虚表不同于继承每个对象中会包含一个指向虚函数表的指针 vptr 。在调用虚函数时程序通过对象的 vptr 找到虚函数表再从表中获取对应函数的地址进行调用。class Base { public: virtual void Func1() { std::cout Base::Func1 std::endl; } virtual void Func2() { std::cout Base::Func2 std::endl; } private: int _b 1; };对于包含虚函数的 Base 类对象其大小除了成员变量占用的空间外还会包含一个 vptr 的大小通常在 32 位系统下为 4 字节64 位系统下为 8 字节。三虚函数的重写覆盖派生类重写基类虚函数时需满足函数签名返回值类型、函数名、参数列表完全相同。不过存在两个例外1.协变基类与派生类虚函数返回值类型不同即基类虚函数返回基类对象的指针或引用派生类虚函数返回派生类对象的指针或引用。例如class A {}; class B : public A {}; class Person { public: virtual A* f() { return new A; } }; class Student : public Person { public: virtual B* f() { return new B; } };1.析构函数的重写如果基类的析构函数为虚函数派生类析构函数只要定义无论是否加 virtual 关键字都与基类的析构函数构成重写。尽管函数名不同基类 ~Person 派生类 ~Student 但编译器会对析构函数名称做特殊处理统一成 destructor。class Person { public: virtual ~Person() { std::cout ~Person() std::endl; } }; class Student : public Person { public: virtual ~Student() { std::cout ~Student() std::endl; } };三、抽象类一概念包含纯虚函数在虚函数声明后面加上 0 的类称为抽象类也叫接口类。抽象类不能实例化对象派生类继承抽象类后只有重写纯虚函数才能实例化对象。纯虚函数规范了派生类必须实现的接口。class Car { public: virtual void Drive() 0; }; class Benz : public Car { public: virtual void Drive() { std::cout Benz - 舒适 std::endl; } }; class BMW : public Car { public: virtual void Drive() { std::cout BMW - 操控 std::endl; } }; void Test() { Car* pBenz new Benz; pBenz-Drive(); Car* pBMW new BMW; pBMW-Drive(); }二接口继承和实现继承普通函数的继承是实现继承派生类继承基类函数的实现可以直接使用函数。虚函数的继承是接口继承派生类继承基类虚函数的接口目的是为了重写以达成多态。所以如果不实现多态就不要把函数定义成虚函数。四、多态的原理一虚函数表当类中有虚函数时编译器会为类创建虚函数表。以简单的单继承为例class Base { public: virtual void Func1() { std::cout Base::Func1 std::endl; } private: int _b 1; }; class Derive : public Base { public: virtual void Func1() { std::cout Derive::Func1 std::endl; } private: int _d 2; };在上述代码中 Base 类对象包含 vptr 和成员变量 _b Derive 类对象包含从 Base 继承的部分 vptr 和 _b 以及自身的成员变量 _d 。 Derive 重写了 Func1 其虚函数表中 Func1 的地址是 Derive::Func1 的地址而从 Base 继承的虚函数如还有其他未重写虚函数会将基类虚函数表中的对应函数地址拷贝过来。1. 派生类对象d中也有一个虚表指针d对象由两部分构成一部分是父类继承下来的成员虚表指针也就是存在部分的另一部分是自己的成员。2. 基类b对象和派生类d对象虚表是不一样的这里我们发现Func1完成了重写所以d的虚表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法覆盖是原理层的叫法。3. 另外Func2继承下来后是虚函数所以放进了虚表Func3也继承下来了但是不是虚函数所以不会放进虚表。4. 虚函数表本质是一个存虚函数指针的指针数组一般情况这个数组最后面放了一个nullptr。5. 总结一下派生类的虚表生成a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。6. 这里还有一个我们很容易混淆的问题虚函数存在哪的虚表存在哪的 答虚函数存在虚表虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针不是虚函数虚函数和普通函数一样的都是存在代码段的只是他的指针又存到了虚表中。另外对象中存的不是虚表存的是虚表指针。那么虚表存在哪里答案是常量区二动态绑定与静态绑定1. 静态绑定又称前期绑定在程序编译期间确定程序的行为例如函数重载编译时就能确定调用哪个函数版本。2. 动态绑定又称后期绑定在程序运行期间根据具体对象的类型确定调用的具体函数多态就是通过动态绑定实现的。五、单继承和多继承关系中的虚函数表一单继承中的虚函数表class Base { public: virtual void func1() {std::cout Base::func1 std::endl;} virtual void func2() {std::cout Base::func2 std::endl;} private: int _b; }; class Derive : public Base { public: virtual void func1() {std::cout Derive::func1 std::endl;} virtual void func3() {std::cout Derive::func3 std::endl;} virtual void func4() {std::cout Derive::func4 std::endl;} private: int _d; };在单继承中派生类会拷贝基类虚函数表内容并重写被覆盖的虚函数地址新增的虚函数按声明顺序添加到虚函数表后面。观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数也可以认为是他的一个小bug。那么我们如何查看d的虚表呢下面我们使用代码打印出虚表中的函数class Base { public: virtual void func1() { std::cout Base::func1 std::endl; } virtual void func2() { std::cout Base::func2 std::endl; } private: int _b; }; class Derive : public Base { public: virtual void func1() { std::cout Derive::func1 std::endl; } virtual void func3() { std::cout Derive::func3 std::endl; } virtual void func4() { std::cout Derive::func4 std::endl; } private: int _d; }; typedef void(*VFPTR) (); void PrintVTable(VFPTR vTable[]) { // 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数 cout 虚表地址 vTable endl; for (int i 0; vTable[i] ! nullptr; i) { printf( 第%d个虚函数地址 :0X%x,-, i, vTable[i]); VFPTR f vTable[i]; f(); } cout endl; } int main() { Base b; Derive d; VFPTR * vTableb (VFPTR*)(*(int*)b); PrintVTable(vTableb); VFPTR* vTabled (VFPTR*)(*(int*)d); PrintVTable(vTabled); return 0; }二多继承中的虚函数表class Base1 { public: virtual void func1() { std::cout Base1::func1 std::endl; } virtual void func2() { std::cout Base1::func2 std::endl; } private: int b1; }; class Base2 { public: virtual void func1() { std::cout Base2::func1 std::endl; } virtual void func2() { std::cout Base2::func2 std::endl; } private: int b2; }; class Derive : public Base1, public Base2 { public: virtual void func1() { std::cout Derive::func1 std::endl; } virtual void func3() { std::cout Derive::func3 std::endl; } private: int d1; };多继承时派生类会有多个虚函数表分别对应各个基类未重写的虚函数放在第一个继承基类部分的虚函数表中重写的虚函数会更新对应虚函数表中的函数地址。但是我们观察到六、C11 override 和 final一finalC98禁止继承的方式私有构造函数禁止外部创建对象核心逻辑构造函数私有化 → 外部无法通过new/ 栈方式直接创建对象类内提供静态方法作为唯一创建入口控制对象创建规则。示例#include iostream using namespace std; class SingleObj { private: // 1. 私有构造函数外部不能直接创建 SingleObj() { cout 对象创建仅类内可调用 endl; } public: // 2. 静态方法对外提供唯一创建入口 static SingleObj* createObj() { return new SingleObj(); // 类内可访问私有构造 } // 测试方法 void show() { cout 对象可用 endl; } }; int main() { // 错误构造函数私有外部无法直接创建 // SingleObj obj; // SingleObj* p new SingleObj(); // 正确通过静态方法创建 SingleObj* obj SingleObj::createObj(); obj-show(); return 0; }私有析构函数禁止外部销毁对象核心逻辑析构函数私有化 → 外部无法直接delete对象避免内存泄漏 / 非法销毁类内提供成员方法作为唯一销毁入口控制对象释放规则。示例#include iostream using namespace std; class SafeObj { private: // 1. 私有构造仅类内创建 SafeObj() { cout 对象创建 endl; } // 2. 私有析构仅类内销毁 ~SafeObj() { cout 对象销毁 endl; } public: // 创建入口 static SafeObj* create() { return new SafeObj(); } // 销毁入口类内可访问私有析构 void destroy() { delete this; // 仅通过该方法释放对象 } }; int main() { SafeObj* obj SafeObj::create(); // 错误析构私有外部不能delete // delete obj; // 正确通过类内方法销毁 obj-destroy(); return 0; }C11禁止继承的方式final修饰虚函数表示该虚函数不能再被重写同时也修饰类表示类不能被继承。修饰虚函数表示该虚函数不能再被重写class Car { public: virtual void Drive() final {} }; class Benz : public Car { public: // 编译报错Drive不能被重写 virtual void Drive() { std::cout Benz - 舒适 std::endl; } };二overrideoverride 用于检查派生类虚函数是否重写了基类某个虚函数如果没有重写则编译报错。例如class Car { public: virtual void Drive() {} }; class Benz : public Car { public: virtual void Drive() override { std::cout Benz - 舒适 std::endl; } };七、重载、覆盖重写、隐藏重定义的对比类别作用域条件重载两个函数在同一作用域。函数名相同参数不同个数、类型、顺序。覆盖重写两个函数分别在基类和派生类的作用域。函数名、参数、返回值都必须相同协变除外且函数必须是虚函数。隐藏重定义两个函数分别在基类和派生类的作用域。函数名相同基类和派生类的同名函数不构成重写就是重定义。八、结语C 多态是一个复杂而精妙的特性从概念到实现从原理到应用都有着丰富的内涵。掌握多态能让我们编写出更加灵活、可维护和高效的代码。在实际编程中合理运用多态结合虚函数、抽象类等知识可以更好地应对各种复杂的业务需求。希望通过这篇博客大家能对 C 多态有更深入的理解和掌握。

相关新闻

从此告别拖延! 降AI率工具 千笔·专业降AIGC智能体 VS 文途AI,MBA专属神器

从此告别拖延! 降AI率工具 千笔·专业降AIGC智能体 VS 文途AI,MBA专属神器

在AI技术迅速发展的今天,越来越多的学生、研究人员和职场人士开始借助AI工具进行论文写作与内容创作。然而,随着学术审核标准的不断升级,AI生成内容的识别能力显著提升,论文中的“AI率”问题逐渐成为影响学术成果的关键障碍。无论…

2026/5/17 9:18:51 阅读更多 →
人工智能+AI的微信小程序的协同过滤算法的校园点餐订餐系统

人工智能+AI的微信小程序的协同过滤算法的校园点餐订餐系统

目录协同过滤算法概述数据收集与处理算法实现步骤微信小程序集成性能优化示例代码(Python)测试与迭代扩展功能项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作协同过滤算法概述 …

2026/5/17 9:18:50 阅读更多 →
写作压力小了,AI论文平台千笔 VS 文途AI更贴合继续教育需求!

写作压力小了,AI论文平台千笔 VS 文途AI更贴合继续教育需求!

随着人工智能技术的迅猛迭代与普及,AI辅助写作工具已逐步渗透到高校学术写作场景中,成为专科生、本科生、研究生完成毕业论文不可或缺的辅助手段。越来越多面临毕业论文压力的学生,开始依赖各类AI工具简化写作流程、提升创作效率。但与此同时…

2026/5/17 9:18:48 阅读更多 →

最新新闻

MetaCodable宏编程入门:快速掌握Swift Codable高级用法

MetaCodable宏编程入门:快速掌握Swift Codable高级用法

MetaCodable宏编程入门:快速掌握Swift Codable高级用法 【免费下载链接】MetaCodable Supercharge Swifts Codable implementations with macros meta-programming. 项目地址: https://gitcode.com/gh_mirrors/me/MetaCodable 想要提升Swift开发效率&#xf…

2026/7/5 15:48:39 阅读更多 →
【信息科学与工程学】【数据中心】【容灾备份】第三十一篇 云数据中心各类CPU计算型业务跨数据中心容灾设计方案

【信息科学与工程学】【数据中心】【容灾备份】第三十一篇 云数据中心各类CPU计算型业务跨数据中心容灾设计方案

一、云数据中心各类CPU计算型业务跨数据中心指标 1. Web应用服务 设计领域 设计子类 特征/函数 参数/指标 用途说明 数据中心内设计 数据中心间设计 网络设计​ 数据中心内网络 1. 负载均衡网络 2. 应用层网络 3. 数据库网络 4. 缓存网络 5. 管理网络 1. 带宽:>…

2026/7/5 15:44:38 阅读更多 →
K-Means 聚类的目标函数:簇内误差平方和

K-Means 聚类的目标函数:簇内误差平方和

1. 什么是 K-Means? K-Means 是一种无监督、迭代式的聚类算法: 给定数据集 {x₁, x₂, …, xₙ} 与预设簇数 K,算法把样本划分为 K 个不相交的簇 C₁, C₂, …, Cₖ,使得同一簇内样本尽可能相似,不同簇间样本尽可能远离…

2026/7/5 15:44:38 阅读更多 →
【信息科学与工程学】计算机科学与自动化——第三十八篇 质量工程 02 云数据中心质量工程

【信息科学与工程学】计算机科学与自动化——第三十八篇 质量工程 02 云数据中心质量工程

云数据中心质量工程体系(规划-评估-测试-验证-交付) 编码 阶段 层级 核心领域 子领域 质量属性/活动 关键交付物/指标 核心方法/工具 评估标准 挑战与风险 1 核心理念 战略层 质量哲学 可靠性即产品 将数据中心可靠性、性能、安全作为可销售、可承诺的服务产品…

2026/7/5 15:42:38 阅读更多 →
net 跨平台也是一句谎言

net 跨平台也是一句谎言

以前很热炒跨平台,主要是由于硅谷挑战微软霸主地位的热情,但是冷静下来后,跨平台往往不是那么一回事。假设你有个软件,所谓的跨平台,你只需要为第二个平台上重新编译一次就行了,这样很难么? c语…

2026/7/5 15:40:38 阅读更多 →
终极指南:如何用CSUR程序化生成系统打造真实城市道路网络

终极指南:如何用CSUR程序化生成系统打造真实城市道路网络

终极指南:如何用CSUR程序化生成系统打造真实城市道路网络 【免费下载链接】CSUR Offline procedural generation of realistic road environments in Cities: Skylines 项目地址: https://gitcode.com/gh_mirrors/cs/CSUR Cities: Skylines Urban Road (CSUR…

2026/7/5 15:38:37 阅读更多 →

日新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻