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

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石 * 摘要 * 目录 * 一、概念 * 二、 性能分析 * 三、key结构非递归模拟实现 * 1. 二叉搜索树的插入 * 2. 二叉搜索树的查找 * 3. 二叉搜索树的删除 * 4. 二叉搜索树的中序遍历 * 四、key结构递归的模拟实现 * 1. 递归与非递归二叉搜索树核心操作的对比 * 2. 递归插入 * 3. 递归查找 * 4. 递归删除 * 总结 摘要 二叉搜索树(BST)是一种重要的数据结构,它通过"左子树所有节点值小于根节点,右子树所有节点值大于根节点"的特性实现高效的元素组织。本文详细解析了BST的核心概念、性能特点,并分别通过非递归和递归两种方式完整实现了插入、查找、删除等关键操作,深入探讨了指针引用在递归实现中的巧妙应用,以及两种实现方式在时间复杂度、空间复杂度和适用场景上的差异。 目录

By Ne0inhk
【C++】 —— 笔试刷题day_28

【C++】 —— 笔试刷题day_28

一、游游的重组偶数 题目解析 这道题,有q组数据,每一次输入一个正整数x,让我们将这个数进行重排,变成一个偶数,然后返回(如果x本身就是一个偶数那可以直接返回x); 如果不存在合法解,就是x通过重排后,无法变成一个偶数,就输出-1; 算法思路 这道题,总体来说还是比较简单的; 对于正整数x,我们可以把它当作一个字符串进行输入;(如果按照整数输入,我们还要将这个数x的每一位变换成对应数组) 我们知道,如果一个数是偶数,那最低位一定是一个偶数,这样我们只需判断字符串的最后一位即可知道这个数是否是偶数;如果这个数是偶数,那就直接输出即可;如果最后一位不是偶数,那就从第一位开始向后找,找到一位是偶数,然后把它交换到最后一位;然后输出即可;如果遍历完这个字符串,还没找到一位是偶数的,那就表示这个数x通过重拍无法变成偶数,输出-1即可。 题目解析 #include<iostream>usingnamespace std; string func(){ string str; cin >>

By Ne0inhk
C++ 多线程同步之互斥锁(mutex)实战

C++ 多线程同步之互斥锁(mutex)实战

C++ 多线程同步之互斥锁(mutex)实战 💡 学习目标:掌握 C++ 标准库中互斥锁的基本用法,理解多线程同步的核心原理,能够解决多线程环境下的资源竞争问题。 💡 学习重点:std::mutex 与 std::lock_guard 的使用、死锁的产生原因及规避方法、实际场景中的同步案例实现。 48.1 多线程同步的必要性 在多线程编程中,当多个线程同时访问共享资源时,会出现资源竞争问题。 例如两个线程同时对同一个变量进行读写操作,会导致最终结果与预期不符。 这种问题被称为线程安全问题,而解决该问题的核心就是线程同步。 ⚠️ 注意事项:线程不同步会引发数据竞争,造成程序运行结果不可预测,甚至导致程序崩溃。 举个简单的反例,两个线程同时对全局变量 count 进行自增操作: #include<iostream>#include<thread>usingnamespace std;int count

By Ne0inhk
(最新原创毕设)Java上门帮厨管理系统/03.01白嫖源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案

(最新原创毕设)Java上门帮厨管理系统/03.01白嫖源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案

摘  要 随着现代生活节奏的加快和人们对便捷、高质量餐饮服务需求的增加,上门帮厨作为一种新兴的服务模式逐渐受到欢迎。然而,传统的上门帮厨管理方式依赖于电话预约和手工记录,不仅效率低下,而且难以满足用户对服务质量透明度和个性化的需求。为此,本文提出了一个基于Spring Boot框架的临沂上门帮厨管理系统。该系统旨在通过信息化手段优化厨师与用户之间的互动流程,提高服务效率,增强用户体验,并为管理者提供有效的运营支持。 基于Spring Boot的临沂上门帮厨管理系统集成了多种功能模块,以满足不同用户群体的需求。普通用户可以通过注册登录进入系统,浏览首页展示的轮播图、菜品资讯、菜品信息推荐等信息,并进行相关操作。系统提供了菜品资讯的查看、点赞、收藏和评论功能,以及菜品信息的详情查看、评分、预约等功能。用户还可以在线提交问题反馈,查看个人账户信息并进行修改。 厨师用户可以查看订单详情,进行订单审核和回复,提交佣金提现申请,并查看提现记录。这些功能模块的设计充分考虑了厨师的实际需求,旨在帮助他们更好地管理和提升自己的服务水平。 管理员负责整个系统的运维工作,包括新注册用户的审核、菜品信

By Ne0inhk