C++ 入门11:虚函数和多态

往期回顾:

C++ 入门08:运算符重载-CSDN博客

C++ 入门09:友元函数和友元类-CSDN博客

C++ 入门10:继承和派生类-CSDN博客


 C++ 入门第十一天:虚函数和多态

一、前言

在前面的文章学习中,我们了解了类和对象的基础知识、构造函数、拷贝构造函数、静态成员、常量成员、运算符重载、友元函数、友元类以及继承和派生类。今天,我们将学习 C++ 中的虚函数和多态。多态是面向对象编程中的一个重要概念,它使得相同的操作可以作用于不同的对象,从而提高代码的灵活性和可扩展性。

二、虚函数和多态

2.1、 虚函数

(1)什么是虚函数?

虚函数是面向对象编程中一个极其重要的概念,特别是在使用如C++这样的语言进行多态性设计时。它是在基类中通过virtual关键字明确声明的函数,其主要目的是允许派生类(子类)根据自身的需要来重写(Override)这个函数的行为,而无需修改基类的代码。这种机制使得基类指针或引用在指向派生类对象时,能够根据对象的实际类型来调用相应的函数版本,从而实现了多态性。

(2)声明虚函数

在C++中,声明一个虚函数非常简单,只需在基类函数的声明前加上virtual关键字即可。

声明示例:

class Base {  
public:  // 声明一个虚函数  virtual void display() {  cout << "Displaying Base class" << endl;  }  
};  class Derived : public Base {  
public:  // 在派生类中重写虚函数  void display() override { // 注意:C++11中引入了override关键字作为可选标记,用于明确表示该函数是重写自基类的虚函数  cout << "Displaying Derived class" << endl;  }  
};

在这个例子中,Base类有一个虚函数display(),而Derived类继承自Base并重写了display()函数。通过基类指针或引用来调用display()时,如果指针或引用实际指向的是Derived类的对象,那么将调用Derived类中重写的display()函数,而不是Base类中的原始函数。这种根据对象实际类型动态调用函数的能力就是多态性的体现。

(3)虚函数的作用

  1. 实现多态:允许通过基类的引用或指针来调用派生类中的函数,从而增加代码的灵活性和可扩展性。
  2. 接口设计:在基类中定义虚函数但不实现它们(即纯虚函数),可以创建抽象基类,作为派生类的接口规范。
  3. 解耦:通过虚函数,可以降低程序各部分之间的耦合度,提高代码的可维护性和复用性。

(4)注意事项

  • 虚函数在继承体系中具有动态绑定(Dynamic Binding)的特性,即函数调用的具体实现取决于对象的实际类型,而非指针或引用的类型。
  • 构造函数不能是虚函数,因为构造函数执行时,对象的类型尚未完全确定(即对象还未完全构造出来)。

  • 析构函数可以是虚函数,并且当基类指针或引用指向派生类对象时,通过基类指针或引用删除对象时,调用的是派生类的析构函数,从而确保派生类资源的正确释放,避免内存泄漏。

运用示例:

我们定义一个基类 Shape,表示几何形状,然后定义两个派生类 CircleRectangle,表示圆形和矩形。我们在基类中声明一个虚函数 draw,并在派生类中重写它。

#include <iostream>
using namespace std;// 基类 Shape
class Shape {
public:virtual void draw() {cout << "Drawing a shape" << endl;}
};// 派生类 Circle
class Circle : public Shape {
public:void draw() override {cout << "Drawing a circle" << endl;}
};// 派生类 Rectangle
class Rectangle : public Shape {
public:void draw() override {cout << "Drawing a rectangle" << endl;}
};int main() {Shape *shape1 = new Circle();Shape *shape2 = new Rectangle();shape1->draw(); // 调用 Circle 的 draw 函数shape2->draw(); // 调用 Rectangle 的 draw 函数delete shape1;delete shape2;return 0;
}

在这个示例中,Shape 类声明了一个虚函数 drawCircleRectangle 类重写了这个函数。在 main 函数中,我们创建了指向 CircleRectangle 对象的 Shape 指针,通过这些指针调用 draw 函数,实际调用的是派生类的重写函数。

2.2、 纯虚函数和抽象类

(1)什么是纯虚函数?

纯虚函数是面向对象编程中用于定义接口的一个关键概念,特别是在使用C++等语言时。它是虚函数的一个特殊形式,但与普通的虚函数不同,纯虚函数没有函数体,即没有具体的实现代码。纯虚函数的主要目的是强制要求从该类派生的所有子类都必须提供该函数的具体实现,从而确保派生类具有特定的行为或功能。

(2)纯虚函数的声明

纯虚函数的声明方式是在函数声明的末尾加上= 0。这个= 0标记表明该函数是一个纯虚函数,它不需要(也不允许)在基类中提供实现。

声明示例:

class Base {  
public:  // 声明一个纯虚函数  virtual void pureVirtualFunction() = 0;  
};

在这个例子中,Base类中的pureVirtualFunction就是一个纯虚函数。由于它没有被实现(即没有函数体),因此Base类本身不能被实例化。任何尝试创建Base类对象的操作都将导致编译错误。

(3)什么是抽象类?

包含至少一个纯虚函数的类被称为抽象类(Abstract Class)。抽象类的主要作用是作为一个基类,用于定义一组接口(即一组纯虚函数),这些接口将由派生类通过提供具体实现来继承和实现。由于抽象类不能实例化,它通常用作基类来指导派生类的设计和实现。

(4)抽象类的特点

  1. 不能被实例化:由于抽象类至少包含一个纯虚函数,它不能用来创建对象。
  2. 强制派生类实现接口:通过定义纯虚函数,抽象类可以强制要求所有派生类都实现这些函数,从而确保派生类具有特定的接口。
  3. 作为接口规范:抽象类通常用于定义一组操作的规范,这些操作将由派生类根据具体情况来实现。

示例:

class Shape {  
public:  // 纯虚函数,定义在形状类中,但由具体形状类来实现  virtual void draw() = 0;  
};  class Circle : public Shape {  
public:  void draw() override {  // 实现绘制圆形的逻辑  cout << "Drawing a circle" << endl;  }  
};  // 使用  
Shape* shape = new Circle(); // 基类指针指向派生类对象  
shape->draw(); // 调用Circle类的draw()函数  
delete shape; // 清理资源

在这个例子中,Shape是一个抽象类,它定义了一个纯虚函数draw()Circle类继承自Shape并重写了draw()函数,提供了具体的实现。通过基类指针shape调用draw()函数时,实际上调用的是Circle类的draw()函数,这体现了多态性的特点。

2.3、 虚析构函数

在涉及到多态性时,确保对象的析构过程正确无误是极其重要的。多态性允许我们通过基类的指针或引用来操作派生类的对象,这在许多设计模式中非常有用,如工厂模式、策略模式等。然而,如果不正确地处理析构函数,就可能会引发资源泄漏或其他严重问题。

为了确保派生类对象在销毁时能够正确调用其析构函数,我们需要将基类的析构函数声明为虚函数。

(1)虚析构函数的重要性

当基类的指针或引用指向派生类对象,并通过该指针或引用删除对象时(例如,使用delete操作符在C++中),如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这意味着派生类中定义的任何资源清理代码(如动态分配的内存释放、文件关闭、网络连接关闭等)都不会被执行,从而导致资源泄漏。

为了避免这种情况,我们需要将基类的析构函数声明为虚函数。通过这样做,当通过基类指针或引用删除派生类对象时,会首先调用派生类的析构函数(如果有的话),然后依次向上调用基类链中每个基类的析构函数,直到最顶层的基类。这样确保了对象在销毁时能够正确地释放其占用的所有资源。

(2)声明虚析构函数

声明虚析构函数非常简单,只需在基类的析构函数前加上virtual关键字即可。这告诉编译器该析构函数是虚函数,需要在运行时根据对象的实际类型来确定要调用的析构函数。

声明示例:

class Base {  
public:  // 构造函数通常不声明为虚函数  Base() {  // 构造函数体  }  // 声明虚析构函数  virtual ~Base() {  // 析构函数体,可能包含资源释放代码  // 但通常保持为空,因为具体资源释放将在派生类中处理  }  // 其他成员函数...  
};  class Derived : public Base {  
public:  ~Derived() override {  // 派生类析构函数体,包含派生类特有的资源释放代码  }  // 其他成员函数...  
};  // 使用示例  
Base* ptr = new Derived();  
// ...  
delete ptr; // 正确调用Derived的析构函数,然后是Base的析构函数

在这个例子中,Base类的析构函数被声明为虚函数,而Derived类继承了Base并可能在其析构函数中添加了额外的资源释放代码。当通过基类指针ptr删除对象时,由于Base的析构函数是虚函数,所以首先会调用Derived的析构函数(如果有的话),然后调用Base的析构函数。这样就保证了无论对象的实际类型是什么,其析构过程都能正确无误地执行。

应用示例:

我们在 Shape 类中声明虚析构函数,并在派生类中定义析构函数。

#include <iostream>
using namespace std;// 基类 Shape
class Shape {
public:virtual void draw() = 0; // 纯虚函数virtual ~Shape() { // 虚析构函数cout << "Destroying Shape" << endl;}
};// 派生类 Circle
class Circle : public Shape {
public:void draw() override {cout << "Drawing a circle" << endl;}~Circle() {cout << "Destroying Circle" << endl;}
};// 派生类 Rectangle
class Rectangle : public Shape {
public:void draw() override {cout << "Drawing a rectangle" << endl;}~Rectangle() {cout << "Destroying Rectangle" << endl;}
};int main() {Shape *shape1 = new Circle();Shape *shape2 = new Rectangle();shape1->draw(); // 调用 Circle 的 draw 函数shape2->draw(); // 调用 Rectangle 的 draw 函数delete shape1; // 调用 Circle 的析构函数delete shape2; // 调用 Rectangle 的析构函数return 0;
}

在这个示例中,Shape 类声明了一个虚析构函数。在 main 函数中,当我们删除 shape1shape2 时,首先调用派生类的析构函数,然后调用基类的析构函数,从而确保资源正确释放。


以上就是 C++ 程序的虚函数和多态的基础知识点了。包括虚函数的声明和使用、纯虚函数和抽象类的概念以及虚析构函数的作用。多态是面向对象编程中的一个重要特性,它使得相同的操作可以作用于不同的对象,从而提高代码的灵活性和可扩展性。

都看到这里了,点个赞再走呗朋友~

加油吧,预祝大家变得更强!

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

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

相关文章

1.32、 基于区域卷积神经网络(R-CNN)的停车标志检测(matlab)

1、基于区域卷积神经网络(R-CNN)的停车标志检测原理及流程 基于区域卷积神经网络&#xff08;R-CNN&#xff09;的停车标志检测原理及流程如下&#xff1a; 原理&#xff1a; R-CNN 是一种用于目标检测的深度学习模型&#xff0c;其核心思想是首先在输入图像中提取出候选区域&…

优化调试体验:让PyCharm的调试过程飞起来

优化调试体验&#xff1a;让PyCharm的调试过程飞起来 PyCharm是一款功能强大的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;它提供了许多便利的调试工具来帮助开发者快速定位和解决问题。然而&#xff0c;在某些情况下&#xff0c;用户可能会遇到调试过程缓慢的…

请结合一个问题bug,帮忙梳理一下DecorView和Window之间的关系

在Android开发中&#xff0c;DecorView 和 Window 是两个核心概念&#xff0c;它们在用户界面显示和布局管理中扮演着重要角色。为了更好地理解它们之间的关系&#xff0c;并通过一个假设的bug场景来梳理它们&#xff0c;我们可以从以下几个方面进行阐述。 1. Window 的概念 …

Typescript Vue3中的defineProps接收参数,并withDefaults设置默认值

1. defineProps&#xff1a;接收父组件传递的参数 2. withDefaults&#xff1a;接收时设置默认值 这两个api都不需要引入&#xff0c;可直接在setup中使用 1. person.vue接收参数 <script setup lang"ts">import type {Persons} from /types// 接收list 可不…

springboot3 web

springboot web配置 springboot web的配置有&#xff1a; SpringMvc配置的前缀为&#xff1a;spring.mvcweb场景的通用配置为&#xff1a;spring.web文件上传的配置为&#xff1a;spring.servlet.multipart服务器相关配置为&#xff1a;server 接管SpringMVC 的三种方式 方…

【算法】无重复字符的最长子串

难度&#xff1a;中等 题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 示例&#xff1a; 示例1&#xff1a; 输入&#xff1a;s “abcabcbb” 输出&#xff1a;3 解释&#xff1a;因为无重复字符的最长子串是 “abc”&#xff0c;所以…

1.26、基于概率神经网络(PNN)的分类(matlab)

1、基于概率神经网络(PNN)的分类简介 PNN(Probabilistic Neural Network,概率神经网络)是一种基于概率论的神经网络模型,主要用于解决分类问题。PNN最早由马科夫斯基和马西金在1993年提出,是一种非常有效的分类算法。 PNN的原理可以简单概括为以下几个步骤: 数据输入层…

对LinkedList和链表的理解

一.ArrayList的缺陷 二.链表 三.链表部分相关oj面试题 四.LinkedList的模拟实现 五.LinkedList的使用 六.ArrayList和LinkedList的区别 一.ArrayList的缺陷: 1. ArrayList底层使用 数组 来存储元素&#xff0c;如果不熟悉可以来再看看&#xff1a; ArrayList与顺序表-CSDN…

2024年7月13日全国青少年信息素养大赛Python复赛小学高年级组真题

第一题 题目描述 握情况。他决定让每个人输入一个正整数 N (0≤N≤1000)&#xff0c;然后计算并输出(5*N)的值。请用 在一个神秘的王国里&#xff0c;国王希望通过一个简单的测试来评估他的子民对基 础数学运算的掌 Python 编写程序&#xff0c;程序执行后要求用户输入一个正…

Hash表(C++)

本篇将会开始介绍有关于 unordered_map 和 unordered_set 的底层原理&#xff0c;其中底层实现其实就是我们的 Hash 表&#xff0c;本篇将会讲解两种 Hash 表&#xff0c;其中一种为开放定址法&#xff0c;另一种为 hash 桶&#xff0c;在unordered_map 和 unordered_set 的底层…

智驭未来:人工智能与目标检测的深度交融

在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;如同一股不可阻挡的浪潮&#xff0c;正以前所未有的速度重塑着我们的世界。在众多AI应用领域中&#xff0c;目标检测以其独特的魅力和广泛的应用前景&#xff0c;成为了连接现实与智能世界的桥梁。本文旨在…

20240715 每日AI必读资讯

&#x1f310; 代号“ 草莓 ”&#xff0c;OpenAI 被曝研发新项目&#xff1a;将 AI 推理能力提至新高度 - OpenAI 公司被曝正在研发代号为“ 草莓 ”的全新项目&#xff0c;进一步延伸去年 11 月宣布的 Q* 项目&#xff0c;不断提高 AI 推理能力&#xff0c;让其更接近人类的…

基于Java的休闲娱乐代理售票系统

你好&#xff0c;我是专注于Java开发的码农小野&#xff01;如果你对系统开发感兴趣&#xff0c;欢迎私信交流。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术、SpringBoot框架、B/S架构 工具&#xff1a;Eclipse IDE、MySQL数据库管理工具…

牛客小白月赛98 (个人题解)(补全)

前言&#xff1a; 昨天晚上自己一个人打的小白月赛&#xff08;因为准备数学期末已经写烦了&#xff09;&#xff0c;题目难度感觉越来越简单了&#xff08;不在像以前一样根本写不了一点&#xff0c;现在看题解已经能看懂一点了&#xff09;&#xff0c;能感受到自己在不断进步…

2024年是不是闰年?

闰年的由来 闰年的概念最早可以追溯到古罗马时期的朱利叶斯凯撒。当时的罗马历法是根据太阳年来制定的&#xff0c;每年大约有365.25天。为了使日历与季节保持同步&#xff0c;人们需要定期插入一个额外的日子。朱利叶斯凯撒在公元前46年颁布了一项法令&#xff0c;规定每四年增…

SAP PP学习笔记26 - User Status(用户状态)的实例,订单分割中的重要概念 成本收集器,Confirmation(报工)的概述

上面两章讲了生产订单的创建以及生产订单的相关内容。 SAP PP学习笔记24 - 生产订单&#xff08;制造指图&#xff09;的创建_sap 工程外注-CSDN博客 SAP PP学习笔记25 - 生产订单的状态管理(System Status(系统状态)/User Status(用户状态)),物料的可用性检查&#xff0c;生…

最长下降序列

如何理解这个题目呢,我们可以每个人的分数放到排名上&#xff0c;然后求解最长下降序列即可 #include<bits/stdc.h> using namespace std;int n; const int N (int)1e5 5; int a[N]; int b[N]; int d[N]; int dp[N]; int t;int main() {cin >> t;while (t--) {…

Java中Set集合

Set集合类似于数学中集合的概念&#xff0c;具有和数学中集合一样的唯一性&#xff08;就是说Set集合中的元素不能重复&#xff09;&#xff0c;在Java中我们最长使用的集合有HashSet、LinkedHashSet和TreeSet&#xff0c;这里最常使用的就是HashSet。 一、HashSet 1.HashSet的…

Apache Hadoop之历史服务器日志聚集配置

上篇介绍绍了Apache Hadoop的分布式集群环境搭建&#xff0c;并测试了MapReduce分布式计算案例。但集群历史做了哪些任务&#xff0c;任务执行日志等信息还需要配置历史服务器和日志聚集才能更好的查看。 配置历史服务器 在Yarn中运行的任务产生的日志数据不能查看&#xff0…

2024-07-13 Qt6.5版本后视频渲染

文章目录 前言一、先上代码1.将yuv/nv12等解码后的视频数据放入QVideoSink内2.将AVFrame数据保存进QVideoSink3.在qml中的显示4.同理,在widget中也可显示 二、QVideoFarme支持的格式三、说说渲染效率问题 前言 Qt 6版本中&#xff0c;视频播放能力得到了质的飞越&#xff0c;相…