C++---多态(一个接口多种实现)

C++的多态(Polymorphism)是面向对象编程(OOP)的三大核心特性之一(另外两个是封装和继承),其核心思想是一个接口,多种实现,即同一操作作用于不同对象时,可产生不同的执行结果。多态让代码更灵活、可扩展,是构建复杂系统的重要工具。

一、多态的分类

C++的多态分为两类:静态多态(编译时多态)和动态多态(运行时多态),二者的核心区别在于“确定调用哪个函数的时机”——前者在编译期确定,后者在运行期确定。

在这里插入图片描述

1. 静态多态(编译时多态)

静态多态是通过函数重载运算符重载实现的,编译器在编译阶段根据函数的参数列表(类型、数量、顺序)或运算符的操作数类型,确定具体要调用的函数。

示例:函数重载实现静态多态

#include <iostream>
using namespace std;// 重载:同一作用域内,函数名相同,参数列表不同
int add(int a, int b) {return a + b;
}double add(double a, double b) {  // 参数类型不同return a + b;
}int add(int a, int b, int c) {  // 参数数量不同return a + b + c;
}int main() {cout << add(1, 2) << endl;       // 调用int add(int, int)cout << add(1.5, 2.5) << endl;   // 调用double add(double, double)cout << add(1, 2, 3) << endl;    // 调用int add(int, int, int)return 0;
}

编译器在编译时会根据实参的类型和数量,自动匹配到对应的重载函数,这就是静态多态的体现。

2. 动态多态(运行时多态)

动态多态是C++多态的核心,它通过继承+虚函数实现,函数的具体调用在程序运行时才确定,而非编译时。这种机制让基类的指针/引用可以灵活指向不同派生类对象,并调用对应派生类的实现。

核心条件

  • 必须存在继承关系(基类与派生类);
  • 基类中声明虚函数(用virtual关键字修饰);
  • 派生类重写(override)基类的虚函数(函数名、参数列表、返回值必须完全一致,协变返回类型除外);
  • 通过基类的指针或引用调用虚函数。

二、动态多态的实现原理

动态多态的核心是虚函数表(vtable)虚指针(vptr),这是编译器在背后自动实现的机制

1. 虚函数表(vtable)
  • 当一个类中声明了虚函数(或继承了虚函数),编译器会为该类生成一个虚函数表(本质是一个函数指针数组),存储该类所有虚函数的地址。
  • 若派生类重写了基类的虚函数,派生类的虚函数表中会用自己的函数地址覆盖基类对应虚函数的地址;未重写的虚函数,地址仍指向基类的实现。
2. 虚指针(vptr)
  • 每个含有虚函数的类的对象,都会隐含一个虚指针(vptr),指向该类的虚函数表(vtable)。
  • 当通过基类指针/引用调用虚函数时,程序会通过对象的vptr找到对应的vtable,再从vtable中取出函数地址并调用,这个过程在运行时完成(动态绑定)。

示例:动态多态的直观体现

#include <iostream>
using namespace std;// 基类:形状
class Shape {
public:// 虚函数:绘制virtual void draw() {  // 用virtual声明为虚函数cout << "绘制基础形状" << endl;}// 虚析构函数(避免内存泄漏)virtual ~Shape() {}
};// 派生类:圆形(继承Shape)
class Circle : public Shape {
public:// 重写基类的draw()void draw() override {  // override关键字显式声明重写(C++11)cout << "绘制圆形" << endl;}
};// 派生类:矩形(继承Shape)
class Rectangle : public Shape {
public:// 重写基类的draw()void draw() override {cout << "绘制矩形" << endl;}
};// 统一接口:接收基类引用,调用draw()
void render(Shape& shape) {shape.draw();  // 运行时根据实际对象类型,调用对应draw()
}int main() {Circle circle;Rectangle rectangle;render(circle);    // 输出:绘制圆形(实际是Circle对象)render(rectangle); // 输出:绘制矩形(实际是Rectangle对象)// 基类指针指向派生类对象Shape* shape1 = new Circle();Shape* shape2 = new Rectangle();shape1->draw();  // 输出:绘制圆形shape2->draw();  // 输出:绘制矩形delete shape1;   // 虚析构函数确保派生类析构被调用delete shape2;return 0;
}

运行机制解析

  • Shape类有虚函数draw(),编译器为其生成vtable,存储Shape::draw()的地址。
  • CircleRectangle继承Shape重写draw(),它们的vtable中,draw()的地址被替换为各自的实现(Circle::draw()Rectangle::draw())。
  • render函数接收CircleRectangle对象的引用时(本质是基类引用指向派生类对象),调用draw()时会通过对象的vptr找到对应vtable,最终执行派生类的实现——这就是运行时多态。

三、重写(Override)的细节

派生类重写基类虚函数时,必须满足以下条件(否则可能变成“隐藏”而非“重写”):

  1. 函数名、参数列表完全相同:参数的类型、数量、顺序必须一致(若参数不同,会变成派生类的新函数,隐藏基类函数)。
  2. 返回值类型相同:除非是“协变返回类型”(即基类虚函数返回基类指针/引用,派生类重写函数返回派生类指针/引用)。
    class Base {};
    class Derived : public Base {};class A {
    public:virtual Base* func() { return new Base(); }  // 基类返回Base*
    };class B : public A {
    public:Derived* func() override { return new Derived(); }  // 派生类返回Derived*(协变)
    };
    
  3. 基类函数必须是虚函数:若基类函数未用virtual修饰,派生类即使同名同参,也只是“隐藏”基类函数,而非重写(无法触发多态)。
  4. 访问权限不影响多态:即使派生类重写的函数是private,通过基类指针/引用调用时仍能正常触发(因为访问权限检查在编译期,多态调用在运行期)。

四、纯虚函数与抽象类

为了强制派生类必须实现某些功能(如“所有形状都必须能绘制”),C++引入纯虚函数抽象类

  • 纯虚函数:在虚函数声明后加=0,表示该函数没有默认实现,必须由派生类重写。
  • 抽象类:包含纯虚函数的类(或继承纯虚函数且未重写的类),不能实例化对象,只能作为基类被继承。

示例:抽象类与纯虚函数

class Shape {
public:// 纯虚函数:强制派生类实现draw()virtual void draw() = 0;  // =0表示纯虚函数virtual ~Shape() {}  // 抽象类也需要虚析构
};class Circle : public Shape {
public:void draw() override {  // 必须重写,否则Circle也是抽象类cout << "绘制圆形" << endl;}
};int main() {// Shape s;  // 错误:抽象类不能实例化Shape* shape = new Circle();  // 正确:基类指针指向派生类对象shape->draw();  // 输出:绘制圆形delete shape;return 0;
}

抽象类的核心作用是定义“接口规范”,确保派生类遵循统一的行为契约(如Shape规定“必须能绘制”,所有派生类都必须实现draw())。

五、多态的应用与优势

  1. 提高代码复用性:通过基类接口统一处理不同派生类对象(如render函数无需为每个形状单独实现)。
  2. 增强扩展性:新增派生类(如Triangle)时,无需修改现有接口代码(如render),只需实现draw()即可,符合“开闭原则”(对扩展开放,对修改关闭)。
  3. 模拟现实世界的多样性:现实中同一行为(如“绘制”)作用于不同对象(圆、矩形)会有不同结果,多态完美映射这种关系。

六、注意事项

  1. 析构函数建议声明为虚函数:当通过基类指针删除派生类对象时,若基类析构不是虚函数,会只调用基类析构而不调用派生类析构,导致内存泄漏。

    class Base {
    public:~Base() { cout << "Base析构" << endl; }  // 非虚析构(危险)
    };class Derived : public Base {
    public:~Derived() { cout << "Derived析构" << endl; }
    };int main() {Base* p = new Derived();delete p;  // 仅输出"Base析构",Derived析构未调用(内存泄漏)return 0;
    }
    

    解决:将基类析构声明为virtual ~Base() {},确保派生类析构被调用。

  2. 避免在构造/析构函数中调用虚函数:构造派生类对象时,先调用基类构造函数,此时对象的动态类型仍为基类,调用虚函数会执行基类版本;析构时同理,可能导致不符合预期的结果。

  3. 虚函数表的开销:每个含虚函数的类会增加vtable(静态开销),每个对象会增加vptr(动态内存开销,通常为4/8字节),但相比多态带来的灵活性,这种开销通常可接受。


C++的多态通过“静态多态(重载)”和“动态多态(虚函数)”实现,其中动态多态是核心,依赖虚函数表和虚指针实现运行时绑定。它让代码更灵活、可扩展,是构建大型面向对象系统的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/919691.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习深度学习】vLLM的核心优化技术详解

目录 前言 一、vLLM简介&#xff1a;为什么它如此重要&#xff1f; 二、核心技术一&#xff1a;PagedAttention — 显存管理的革命 2.1 传统注意力缓存的缺陷 2.2 分页式存储管理 三、核心技术二&#xff1a;张量并行 — 多GPU推理的基石 3.1 什么是张量并行&#xff1f…

MySQL 高级主题:索引优化、ORM 与数据库迁移

第五部分&#xff1a;索引优化1. 为什么需要索引&#xff1f;索引是提高数据库查询性能的关键数据结构&#xff0c;它类似于书籍的目录&#xff0c;可以帮助数据库快速定位到所需数据&#xff0c;而不必扫描整个表。2. 索引类型主键索引 (PRIMARY KEY): 唯一且非空&#xff0c;…

Eplan教程:网络与PLC

欢迎大家来到“Eplan带你做项目”第六个过程。在第五个过程中&#xff0c;Eplan基于实际项目的绘制&#xff08;电气设计中的电源回路以及电源分配相关回路&#xff09;重点分享分了“电机的供电和控制图纸的绘制”。本文中&#xff0c;先猜个问题&#xff0c;设计一个PLC系统&…

大模型落地全攻略:从技术实现到场景应用

大语言模型&#xff08;LLM&#xff09;的快速发展正在重塑各行各业的智能化进程&#xff0c;但其落地应用仍面临技术适配、场景融合、成本控制等多重挑战。本文将系统解析大模型落地的四大核心方向 ——微调技术、提示词工程、多模态应用和企业级解决方案&#xff0c;通过代码…

【论文】Zotero文献管理

Zotero文献管理 写论文前查找阅读大量文献&#xff0c;写论文时引用文献&#xff0c;都是一件非常麻烦的事情&#xff0c;一款合适的文献管理工具可以帮助我们更快捷地完成这些任务。zotero作为一款免费开源的工具&#xff0c;可以实现文献阅读、同步管理以及引用管理。 安装…

MsSQL 函数,实现数字转换成人民币大写

MsSQL 函数&#xff0c;实现数字转换成人民币大写-- 如果函数已存在则删除 IF OBJECT_ID(dbo.ConvertToRMBChineseNew, FN) IS NOT NULLDROP FUNCTION dbo.ConvertToRMBChineseNew GOCREATE FUNCTION dbo.ConvertToRMBChineseNew (NumberInput SQL_VARIANT -- 使用 SQL_VARIANT…

OpenHarmony深度定制:从系统到模块的全景剖析与自定义模块实战

摘要:OpenHarmony 作为面向万物互联时代的开源操作系统,其“系统-子系统-部件-模块”的四层架构设计,为开发者提供了高度可裁剪、可扩展的能力。本文将系统梳理这四层结构的职责边界与协作关系,并手把手演示如何向 OpenHarmony 新增一个可交付的自定义模块(Module),帮助…

数字社会学是干什么的?数字社会学理论与数字社会学家唐兴通讲数字社会学书籍有哪些?AI社会学人工智能社会学理论框架

在当今社会&#xff0c;传统物理空间和人际关系网络成为了许多年轻人寻找合适伴侣的重大障碍。以深圳为例&#xff0c;这座移民城市的大部分居民都来自外地&#xff0c;年轻人的人脉关系、尤其是亲戚关系大多仍在家乡。这使得深圳的单身男女在交友和婚恋方面的选择面变得狭窄&a…

数据库-MYSQL配置下载

目录 一.数据库概念 一、数据库的基本定义 二、数据库管理系统&#xff08;DBMS&#xff09; 三、数据库系统&#xff08;DBS&#xff09; 四、数据模型 五、数据库的特点 六、数据库的应用领域 二.MySql 一、开源免费&#xff0c;降低中大型项目成本 二、跨平台与兼容…

Java 中表示数据集的常用集合类

Java 中表示数据集的常用集合类 Java 集合框架提供了多种数据结构来表示和操作数据集&#xff0c;每种集合类都有其特定的用途和性能特征。以下是主要的集合类及其特点&#xff1a; 一、List 接口及其实现类 1. ArrayList 特点&#xff1a;基于动态数组实现优点&#xff1a;随机…

Django REST框架核心:GenericAPIView详解

Django REST framework (DRF) 中 GenericAPIView 的源码核心部分。 它是所有“泛型视图”的基础类&#xff0c;比如常用的 ListAPIView、RetrieveAPIView、CreateAPIView 都是继承自它。&#x1f31f; 作用继承自 APIView&#xff0c;因此仍然是一个标准的 DRF 视图。提供了常用…

深入解析HashMap的存储机制:扰动函数、哈希计算与索引定位

今天复习了一下HashMap的部分&#xff0c;写一篇博客记录一下今天学习内容虽然之前学习过&#xff0c;但由于后来没怎么使用过而且也没复习基本忘得差不多了在Java的HashMap中&#xff0c;高效存储键值对的核心在于哈希算法和索引定位。本文将结合源码逐步拆解存储流程&#xf…

【机器学习 / 深度学习】基础教程

阶段一&#xff1a;机器学习 / 深度学习基础教程定位&#xff1a;针对准备进入 AI多智能体开发 的初学者&#xff0c;打牢机器学习与深度学习的基础。一、为什么需要学习机器学习/深度学习 在进入智能体&#xff08;Agent&#xff09;开发之前&#xff0c;必须具备一定的 机器学…

ESP32应用——HTTP client(ESP-IDF框架)

目录 一、前言 二、URL 2.1 URL简介 2.2 URL示例 三、HTTP 3.1 HTTP协议概述 3.2 HTTP的工作原理 3.2.1 HTTP 请求-响应流程 3.2.2 HTTP 请求结构 3.2.3 HTTP请求方法 3.2.4 HTTP响应结构 3.2.5 HTTP状态码 四、ESP HTTP 客户端流程 五、ESP HTTP 客户端实战解析…

动学学深度学习07-现代卷积神经网络

动学学深度学习pytorch 参考地址&#xff1a;https://zh.d2l.ai/ 文章目录动学学深度学习pytorch1-第07章-现代卷积神经网络1. AlexNet1.1 AlexNet 的核心贡献是什么&#xff1f;1.2 AlexNet 与 LeNet 的主要区别有哪些&#xff1f;1.3 为什么 AlexNet 需要 GPU 训练&#xff1…

详细讲解Java中的反射和经典面试题(保姆级别)

1.1 反射的概述&#xff1a;专业的解释&#xff08;了解一下&#xff09;&#xff1a;是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意属性和方法&#xff1b;这种动态获取…

MyCAT完整实验报告

MyCAT完整实验报告 ‍ 前言 刚刚看了一下前面的那篇MyCAT的文章 感觉有一些问题 所以拿出一篇文章再说一下 单独构建了完整的实验环境 这样会全面一点 ‍ 安装MyCAT #跳过‍ 主从配置 #不多追溯 因为我们选择的主从 也可以做双主机 但我们后边再说‍ 环境搭建 一、环境规划 服务…

机器翻译论文阅读方法:顶会(ACL、EMNLP)论文解析技巧

更多内容请见: 机器翻译修炼-专栏介绍和目录 文章目录 一、论文选择:快速判断论文价值 1.1 关注核心会议与子领域 1.2 筛选标准 1.3 预读筛选 1.4 快速定位关键信息 二、精读解析 2.1 问题定义(5分钟) 2.2 方法解剖(15分钟) 2.3 实验深挖(20分钟) 2.4 批判性思考(10分…

Transformer模型实战篇

引入 基于Transformers的NLP解决方案的步骤如下&#xff1a;&#xff08;以文本分类为例&#xff09; 导入相关包&#xff0c;General&#xff0c;可以询问ai需要导什么包加载数据集&#xff0c;Data_loader&#xff0c;Datasets数据集划分&#xff0c;测试机&#xff0c;验证集…

深入(流批【牛批】框架)Flink的机制

flink本身是专注有状态的无限流处理&#xff0c;有限流处理【batch批次】是无限流处理的一中特殊情况&#xff01;应用场景实时ETL 集成流计算现有的诸多数据通道和SQL灵活的加工能力&#xff0c;对流式数据进行实时清洗、归并和结构化 处理&#xff1b;同时&#xff0c;对离线…