C++ 多态:面向对象的动态行为核心机制

C++ 多态:面向对象的动态行为核心机制

C++ 多态:面向对象的动态行为核心机制

在这里插入图片描述

💡 学习目标:掌握多态的概念与分类,理解虚函数的作用原理,能够熟练使用多态实现程序的动态行为扩展。
💡 学习重点:静态多态与动态多态的区别、虚函数的定义与使用、纯虚函数与抽象类、多态的实战应用场景。

一、多态的概念与分类

结论:多态是 C++ 面向对象三大特性之一,指同一行为在不同对象上表现出不同的形态,核心是“一个接口,多种实现”。

多态主要分为两大类,二者的实现原理和触发时机截然不同:

  1. 静态多态:编译阶段确定调用关系,也叫编译时多态,实现方式包括函数重载运算符重载
  2. 动态多态:运行阶段确定调用关系,也叫运行时多态,实现方式是虚函数 + 基类指针/引用

生活中的多态示例:同样是“动物叫”这个行为,猫的叫声是“喵喵喵”,狗的叫声是“汪汪汪”,不同动物对象表现出不同的行为形态。

二、静态多态:编译时确定的多态性

💡 静态多态的调用关系在编译阶段就已确定,编译器会根据参数列表的差异匹配对应的函数。

2.1 函数重载实现静态多态

这是最常见的静态多态形式,同一作用域内的同名函数,通过参数的类型或数量区分。

#include<iostream>usingnamespace std;// 静态多态:函数重载voidprint(int a){ cout <<"整数:"<< a << endl;}voidprint(double b){ cout <<"浮点数:"<< b << endl;}voidprint(string c){ cout <<"字符串:"<< c << endl;}intmain(){// 编译阶段确定调用哪个 print 函数print(10);print(3.14);print("C++ Static Polymorphism");return0;}

2.2 运算符重载实现静态多态

通过重载运算符,让自定义类型支持内置运算符的操作,本质也是编译时多态。

#include<iostream>usingnamespace std;classPoint{public:int x, y;Point(int x =0,int y =0):x(x),y(y){}// 重载 + 运算符,实现点的坐标相加 Point operator+(const Point& p){returnPoint(this->x + p.x,this->y + p.y);}};intmain(){ Point p1(1,2),p2(3,4);// 编译阶段确定调用重载的 + 运算符 Point p3 = p1 + p2; cout <<"p3.x = "<< p3.x <<", p3.y = "<< p3.y << endl;return0;}

三、动态多态:运行时确定的多态性

💡 动态多态是面向对象编程的核心,其关键是虚函数基类指针/引用指向派生类对象,调用关系在程序运行时才确定。

3.1 动态多态的实现条件

实现动态多态必须同时满足三个条件:

  1. 存在继承关系,且通常是公有继承
  2. 基类中定义虚函数,派生类重写该虚函数
  3. 使用基类的指针或引用指向派生类对象

3.2 虚函数的定义与使用

虚函数的定义语法是在基类的成员函数前加 virtual 关键字,派生类重写时可以省略 virtual,但建议保留以增强可读性。

3.2.1 基础案例:动物叫的多态实现
#include<iostream>#include<string>usingnamespace std;// 基类:动物classAnimal{public:// 虚函数:动物叫virtualvoidbark(){ cout <<"动物发出叫声"<< endl;}};// 派生类:猫classCat:publicAnimal{public:// 重写基类的虚函数voidbark()override{// override 关键字显式声明重写,建议添加 cout <<"猫:喵喵喵"<< endl;}};// 派生类:狗classDog:publicAnimal{public:// 重写基类的虚函数voidbark()override{ cout <<"狗:汪汪汪"<< endl;}};intmain(){// 基类指针指向派生类对象 Animal *animal1 =newCat(); Animal *animal2 =newDog();// 运行时确定调用哪个类的 bark 函数 animal1->bark();// 输出猫的叫声 animal2->bark();// 输出狗的叫声// 释放内存delete animal1;delete animal2;return0;}
3.2.2 运行结果
猫:喵喵喵 狗:汪汪汪 

⚠️ 注意事项

  1. override 关键字用于显式标记派生类对基类虚函数的重写,编译器会检查重写的合法性,建议添加。
  2. 如果基类指针指向基类对象,则调用基类的虚函数;指向派生类对象,则调用派生类重写的函数。
  3. 虚函数的重写要求函数名、参数列表、返回值类型完全一致,否则会被视为派生类的新函数,而非重写。

3.3 虚析构函数:解决派生类资源泄漏问题

当基类指针指向派生类对象并通过 delete 释放时,如果基类析构函数不是虚函数,会导致派生类的析构函数无法被调用,从而引发内存泄漏。

解决方案:将基类的析构函数声明为虚析构函数

3.3.1 问题代码演示(非虚析构)
#include<iostream>usingnamespace std;classBase{public:Base(){ cout <<"Base 构造函数"<< endl;}~Base(){ cout <<"Base 析构函数"<< endl;}// 非虚析构};classDerived:publicBase{public:Derived(){ cout <<"Derived 构造函数"<< endl;}~Derived(){ cout <<"Derived 析构函数"<< endl;}// 无法被调用};intmain(){ Base *p =newDerived();delete p;// 仅调用基类析构函数,派生类析构未调用return0;}
3.3.2 运行结果(存在问题)
Base 构造函数 Derived 构造函数 Base 析构函数 
3.3.3 解决代码(虚析构函数)
#include<iostream>usingnamespace std;classBase{public:Base(){ cout <<"Base 构造函数"<< endl;}virtual~Base(){ cout <<"Base 析构函数"<< endl;}// 虚析构函数};classDerived:publicBase{public:Derived(){ cout <<"Derived 构造函数"<< endl;}~Derived()override{ cout <<"Derived 析构函数"<< endl;}// 重写虚析构};intmain(){ Base *p =newDerived();delete p;// 先调用派生类析构,再调用基类析构return0;}
3.3.4 运行结果(正确释放)
Base 构造函数 Derived 构造函数 Derived 析构函数 Base 析构函数 

四、纯虚函数与抽象类

💡 纯虚函数是没有函数体的虚函数,包含纯虚函数的类称为抽象类,抽象类无法实例化对象,只能作为基类被继承。

4.1 纯虚函数的定义语法

virtual 返回值类型 函数名(参数列表)=0;

4.2 抽象类的特性

  1. 抽象类不能创建对象,只能定义指针或引用。
  2. 派生类必须重写抽象类的所有纯虚函数,否则派生类也会成为抽象类。
  3. 抽象类的核心作用是定义接口规范,强制派生类实现具体功能。

4.3 代码演示:图形面积计算的抽象类

#include<iostream>usingnamespace std;// 抽象类:图形classShape{public:// 纯虚函数:计算面积virtualdoublegetArea()=0;// 纯虚函数:计算周长virtualdoublegetPerimeter()=0;};// 派生类:矩形classRectangle:publicShape{private:double width, height;public:Rectangle(double w,double h):width(w),height(h){}// 必须重写所有纯虚函数doublegetArea()override{return width * height;}doublegetPerimeter()override{return2*(width + height);}};// 派生类:圆形classCircle:publicShape{private:double radius;constdouble PI =3.1415926;public:Circle(double r):radius(r){}doublegetArea()override{return PI * radius * radius;}doublegetPerimeter()override{return2* PI * radius;}};// 通用函数:打印图形信息voidprintShapeInfo(Shape *shape){ cout <<"面积:"<< shape->getArea()<< endl; cout <<"周长:"<< shape->getPerimeter()<< endl;}intmain(){// 抽象类不能实例化对象// Shape s; // 编译错误 Shape *rect =newRectangle(5,3); Shape *circle =newCircle(4); cout <<"矩形信息:"<< endl;printShapeInfo(rect); cout <<"----------------"<< endl; cout <<"圆形信息:"<< endl;printShapeInfo(circle);delete rect;delete circle;return0;}

4.4 运行结果

矩形信息: 面积:15 周长:16 ---------------- 圆形信息: 面积:50.2655 周长:25.1327 

五、多态的实战案例:计算器的动态扩展

💡 需求:设计一个支持多种运算的计算器,利用多态特性,让计算器可以动态扩展新的运算类型,无需修改原有代码(符合开闭原则)。

5.1 需求分析

  1. 定义抽象基类 Operation,包含纯虚函数 calculate,用于计算结果。
  2. 派生类分别实现加法 Add、减法 Sub、乘法 Mul、除法 Div
  3. 设计计算器类 Calculator,接收 Operation 指针,调用计算方法。
  4. 新增运算类型时,只需新增派生类,无需修改计算器核心代码。

5.2 完整代码实现

#include<iostream>#include<stdexcept>usingnamespace std;// 抽象基类:运算classOperation{public:double num1, num2;voidsetNum(double n1,double n2){ num1 = n1; num2 = n2;}// 纯虚函数:计算virtualdoublecalculate()=0;};// 加法运算classAdd:publicOperation{public:doublecalculate()override{return num1 + num2;}};// 减法运算classSub:publicOperation{public:doublecalculate()override{return num1 - num2;}};// 乘法运算classMul:publicOperation{public:doublecalculate()override{return num1 * num2;}};// 除法运算classDiv:publicOperation{public:doublecalculate()override{if(num2 ==0){throwinvalid_argument("除数不能为 0");}return num1 / num2;}};// 计算器类classCalculator{private: Operation *op;public:Calculator(Operation *operation):op(operation){}doublecompute(double n1,double n2){ op->setNum(n1, n2);return op->calculate();}~Calculator(){delete op;}};intmain(){try{// 加法计算 Calculator addCalc(newAdd()); cout <<"10 + 5 = "<< addCalc.compute(10,5)<< endl;// 减法计算 Calculator subCalc(newSub()); cout <<"10 - 5 = "<< subCalc.compute(10,5)<< endl;// 乘法计算 Calculator mulCalc(newMul()); cout <<"10 * 5 = "<< mulCalc.compute(10,5)<< endl;// 除法计算 Calculator divCalc(newDiv()); cout <<"10 / 5 = "<< divCalc.compute(10,5)<< endl;// 测试除数为 0 cout <<"10 / 0 = "<< divCalc.compute(10,0)<< endl;}catch(const exception& e){ cout <<"错误:"<< e.what()<< endl;}return0;}

5.3 运行结果

10 + 5 = 15 10 - 5 = 5 10 * 5 = 50 10 / 5 = 2 错误:除数不能为 0 

六、多态的核心原理:虚函数表

💡 C++ 动态多态的底层实现依赖虚函数表(vtable)虚函数指针(vptr),理解其原理能帮助我们更好地使用多态。

6.1 虚函数表的工作机制

  1. 当类中包含虚函数时,编译器会为该类生成一个虚函数表,表中存储的是虚函数的地址。
  2. 每个对象的内存布局中,会有一个隐藏的虚函数指针 vptr,指向所属类的虚函数表。
  3. 当基类指针指向派生类对象时,vptr 会指向派生类的虚函数表。
  4. 程序运行时,通过 vptr 找到虚函数表,再根据表中的地址调用对应的函数,从而实现动态多态。

6.2 核心特点

  • 虚函数表属于,所有对象共享同一个虚函数表,节省内存空间。
  • 虚函数指针属于对象,每个对象都有自己的 vptr,指向对应类的虚函数表。
  • 派生类的虚函数表会继承基类的虚函数表,并重写被覆盖的虚函数地址。

七、本章总结

✅ 多态分为静态多态和动态多态,静态多态编译时确定,动态多态运行时确定。
✅ 动态多态的实现条件是:继承关系 + 虚函数重写 + 基类指针/引用指向派生类对象。
✅ 虚析构函数可以解决基类指针释放派生类对象时的资源泄漏问题。
✅ 抽象类包含纯虚函数,无法实例化,用于定义接口规范,强制派生类实现具体功能。
✅ 多态的核心优势是代码复用、功能扩展、符合开闭原则,是大型项目设计的核心机制。

Read more

基于SpringBoot的企业考勤管理系统设计与实现

基于SpringBoot的企业考勤管理系统设计与实现

基于SpringBoot的企业考勤管理系统设计与实现 🌟 你好,我是 励志成为糕手 ! 🌌 在代码的宇宙中,我是那个追逐优雅与性能的星际旅人。 ✨ 每一行代码都是我种下的星光,在逻辑的土壤里生长成璀璨的银河; 🛠️ 每一个算法都是我绘制的星图,指引着数据流动的最短路径; 🔍 每一次调试都是星际对话,用耐心和智慧解开宇宙的谜题。 🚀 准备好开始我们的星际编码之旅了吗? 目录 * 基于SpringBoot的企业考勤管理系统设计与实现 * 摘要 * 系统架构设计 * 整体架构概览 * 核心业务流程 * 数据库设计 * 实体关系模型 * 数据表结构设计 * 核心代码实现 * 实体类设计 * 业务逻辑层实现 * 控制器层实现 * 系统功能特性 * 出勤状态管理 * 月度统计功能 * 技术选型对比 * 系统部署与配置 * 环境配置 * 项目依赖管理 * 系统性能优化 * 数据库优化策略 * 缓存策

By Ne0inhk
Flutter for OpenHarmony:Flutter 三方库 riverbloc — 融合 Bloc 与 Riverpod 的架构实践(适配鸿蒙 HarmonyOS Next ohos)

Flutter for OpenHarmony:Flutter 三方库 riverbloc — 融合 Bloc 与 Riverpod 的架构实践(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 前言 在鸿蒙(OpenHarmony)中大型项目中,开发者常在 Bloc 的严谨性与 Riverpod 的灵活性之间权衡。riverbloc 作为桥接库,允许将 Bloc 作为 Provider 管理,兼具了事件溯源与全局依赖注入的优势,是构建可维护业务中枢的理想选择。 一、核心价值 1.1 基础概念 riverbloc 引入了 BlocProvider 系列函数,使 Bloc 融入 Riverpod 的依赖树。 State 输出 ref.watch ref.read.add(Event) Riverpod ProviderContainer riverbloc 桥接层 触发业务逻辑

By Ne0inhk
用 Rust 打造二维码艺术大师:从想法到实现

用 Rust 打造二维码艺术大师:从想法到实现

二维码已经渗透到我们生活的方方面面,从支付到网站链接,几乎无处不在。但你有没有想过,二维码是怎么生成的?这些黑白方块也可以变得有趣和美观?今天我就来分享一下我用 Rust 实现的一个小项目:二维码艺术生成器(qr-artist)。 项目起源 这个想法源于一个简单的需求:如何让二维码既实用又美观?普通的黑白二维码虽然功能强大,但看起来有些单调。我想,能不能让二维码变得更有艺术感,比如用彩色像素来呈现? 技术选型 我选择了 Rust 作为开发语言,因为它在系统编程方面的优秀表现和内存安全特性。项目中主要使用了以下几个库: 1. qrcode - 用于生成二维码数据 2. image - 用于图像处理和保存 3. clap - 用于构建命令行界面 这些库都很成熟且文档完善,让我能够专注于核心功能的实现。 核心实现 1. 基础二维码生成 项目的核心是将 URL 转换为二维码数据,然后将其渲染为图像: // 创建二维码let code =QrCode::new(

By Ne0inhk

Go语言的主流框架和解决超高并发的三高微服务框架对比分析

在Go语言生态中,主流的Web框架和应对“三高”(高并发、高可用、高可扩展)场景的微服务框架,经过多年的发展已经非常清晰。简单来说,Gin 是目前应用最广泛的通用Web框架,而像 go-zero、Kratos、KiteX 等则是专为“三高”微服务架构设计的“全家桶”式解决方案。 下面为你详细拆解这两大类框架。 一、主流通用Web框架:轻量、灵活、高性能 这类框架主要解决API构建、路由和中间件管理等Web层问题,是构建单体应用或微服务API层的良好基础。 Gin:目前的“默认选项”,性能高、社区庞大、中间件丰富,极易上手。如果你刚开始接触Go或项目需求明确,选择Gin会非常稳妥。 Fiber:受Express.js启发,语法对Node.js开发者很友好。它基于fasthttp构建,在性能基准测试中表现极为出色。适合追求极致性能、且不介意与标准库net/http不完全兼容的场景。 Echo:一个成熟且平衡的框架,

By Ne0inhk