学习c++的第十二天

目录

多态性

多态性

虚函数

虚析构函数

纯虚函数


多态性

多态性

多态性的实现依赖于继承和虚函数。在面向对象编程中,通过基类的指针或引用来调用派生类的成员函数时,可以根据实际的对象类型决定调用哪个类的成员函数,从而实现多态性。

  • 在静态联编中,编译器在编译阶段就能确定调用的具体函数,这种情况下多态性的效果并不明显。而在动态联编中,编译器只能确定调用的是基类的成员函数,具体调用哪个派生类的函数则在运行时决定,这就发挥了多态性的优势。
  • 动态联编和多态性的典型应用是通过虚函数实现的。虚函数是在基类中声明的带有 virtual 关键字的成员函数,它可以被派生类重写(Override)。当使用基类的指针或引用调用虚函数时,实际调用的是派生类中重写的函数。

下面,我们围绕静态联编,给大家举一个简单例子:

#include <iostream>class Shape {
public:void draw() {std::cout << "绘制一个形状" << std::endl;}
};class Circle : public Shape {
public:void draw() {std::cout << "绘制一个圆形" << std::endl;}
};class Rectangle : public Shape {
public:void draw() {std::cout << "绘制一个矩形" << std::endl;}
};int main() {Shape shape;Circle circle;Rectangle rectangle;// 基类指针指向派生类对象Shape* shapePtr1 = &circle;Shape* shapePtr2 = &rectangle;// 调用基类的成员函数,由于静态联编,在编译时就确定了调用的函数shape.draw();       // 绘制一个形状shapePtr1->draw();  // 绘制一个形状shapePtr2->draw();  // 绘制一个形状return 0;
}

在上述示例中,我们有一个基类 Shape 和两个派生类 Circle 和 Rectangle。基类和派生类都有一个名为 draw 的成员函数。

在 main 函数中,我们创建了一个基类对象 shape 和两个派生类对象 circle 和 rectangle。然后,我们将派生类对象的地址赋给基类指针 shapePtr1 和 shapePtr2。

在调用成员函数时,基类对象 shape 和派生类对象的指针 shapePtr1 和 shapePtr2 都调用了 draw 函数。由于静态联编,在编译时就确定了调用的函数,因此无论是通过基类对象还是派生类对象的指针调用,都会执行基类的 draw 函数,输出 "绘制一个形状"。

这个示例展示了静态联编的特点,即在编译时就确定了调用哪个函数,不会根据对象的实际类型来决定调用哪个函数。

通过多态性,我们可以以一种统一的方式处理不同类型的对象。这样做的好处是,我们可以将对象视为其基类类型,而不需要关注具体是哪个派生类的实例。这使得我们可以设计出更加灵活和可扩展的代码,同时提高了代码的可读性和可维护性。

总结来说,多态性是面向对象编程的重要特性,它通过继承和虚函数机制实现。它允许不同类型的对象对同一个消息做出不同的响应,提高了代码的灵活性和可扩展性。静态联编和动态联编是多态性的两种实现方式,静态联编由于编译时候就已经确定好怎么执行,因此执行起来效率高;而动态联编想必虽然慢一些,但优点是灵活。两者各有千秋,有各自不同的使用场景。

虚函数

虚函数是用于实现动态联编和多态性的一种机制。通过将父类中的成员函数声明为虚函数,可以在子类中重写该函数并根据对象的实际类型选择调用哪个函数,从而实现多态性。

在 C++ 中,将函数声明为虚函数需要在函数名前加上关键字 virtual。当一个函数被声明为虚函数时,它的子类中的同名函数也会自动成为虚函数。当使用一个指向子类对象的基类指针或引用调用虚函数时,程序会在运行时动态地确定调用哪个函数,这就是动态联编。

一般形式如下:

virtual 函数返回值 函数名(形参)
{函数体
}

需要注意的是:

  1. 虚函数不能是静态成员函数或友元函数:虚函数依赖于对象的实际类型来确定调用哪个函数,而静态成员函数和友元函数不属于特定的对象,因此无法实现动态多态性,所以它们不能被声明为虚函数。
  2. 内联函数不能是虚函数:内联函数是在编译时插入代码的,而虚函数的调用是在运行时动态决定的。即使虚函数在类的定义中被标记为 inline,编译器仍然会将其视为非内联函数,因为虚函数的调用需要在运行时根据对象的实际类型来确定。
  3. 构造函数不能是虚函数,析构函数可以是虚函数:构造函数在创建对象时被调用,并负责初始化对象的成员变量等工作。由于在调用构造函数时对象尚未完全构建,因此无法实现动态多态性,所以构造函数不能声明为虚函数。而析构函数在销毁对象时被调用,可以实现动态多态性,通常情况下我们将析构函数声明为虚函数,以确保在删除基类指针时正确调用派生类的析构函数,防止内存泄漏。

以下是一个简单的使用虚函数的示例:

#include <iostream>
using namespace std;class Shape {
public:virtual void draw() {cout << "绘制一个形状" << endl;}
};class Circle : public Shape {
public:void draw() {cout << "绘制一个圆形" << endl;}
};class Rectangle : public Shape {
public:void draw() {cout << "绘制一个矩形" << endl;}
};int main() {Shape shape;Circle circle;Rectangle rectangle;Shape* shapePtr1 = &circle;Shape* shapePtr2 = &rectangle;// 通过基类指针调用虚函数,程序会在运行时动态地确定调用哪个函数shape.draw();       // 绘制一个形状shapePtr1->draw();  // 绘制一个圆形shapePtr2->draw();  // 绘制一个矩形return 0;
}

在上述示例中,我们将 Shape 类的 draw 函数声明为虚函数,并在其派生类 Circle 和 Rectangle 中重写了该函数。在 main 函数中,我们创建了一个基类对象 shape 和两个派生类对象 circle 和 rectangle,并将派生类对象的地址赋给基类指针。

通过基类指针调用虚函数时,程序会在运行时动态地确定调用哪个函数。因此,通过基类对象 shape 调用 draw 函数时,输出 "绘制一个形状";通过派生类对象的指针 shapePtr1 和 shapePtr2 调用 draw 函数时,分别输出 "绘制一个圆形" 和 "绘制一个矩形",实现了多态性。

虚析构函数

在C++中,我们不能将构造函数声明为虚函数,因为构造函数的调用是在实例化对象时发生的,而虚函数的实现是通过虚函数表来实现的。在实例化对象之前,对象还没有被创建,也没有内存空间可供调用虚函数,所以将构造函数声明为虚函数是没有意义的。

然而,析构函数可以被声明为虚函数,并且通常情况下我们将其声明为虚析构函数。通过将析构函数声明为虚函数,可以在删除指向派生类对象的基类指针时,根据实际所指向的对象类型动态绑定调用相应的析构函数,从而实现正确的对象内存释放。

当使用基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么在删除指针时只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类对象中动态分配的资源没有得到正确释放,从而造成内存泄漏。

通过声明析构函数为虚函数,我们可以保证在删除基类指针时调用正确的析构函数,确保对象的内存得到正确释放,避免内存泄漏问题。

下面是一个使用虚析构函数的示例:

#include <iostream>
using namespace std;class Base {
public:virtual ~Base() {cout << "调用了基类的析构函数" << endl;}
};class Derived : public Base {
public:~Derived() {cout << "调用了派生类的析构函数" << endl;}
};int main() {Base* ptr = new Derived();delete ptr;return 0;
}

在上述示例中,我们定义了一个基类 Base 和一个派生类 Derived。基类 Base 的析构函数被声明为虚函数(使用 virtual 关键字),而派生类 Derived 的析构函数没有显式声明为虚函数。

在 main 函数中,我们创建了一个派生类对象的指针 ptr,并将其赋给基类指针。接着,我们使用 delete 关键字删除基类指针,这会触发析构函数的调用。

由于基类的析构函数是虚函数,因此在删除指针时,会调用正确的析构函数。输出结果为:

调用了派生类的析构函数
调用了基类的析构函数

可以看到,通过将基类的析构函数声明为虚函数,我们确保了派生类的析构函数也被正确地调用,从而释放了派生类对象中的资源。

纯虚函数

在C++中,纯虚函数是一种在基类中声明但没有实现的虚函数。纯虚函数的声明形式是在函数原型的末尾使用 "= 0" 进行标记。纯虚函数用于定义一个接口,要求派生类必须实现该函数。

virtual 返回值 函数名(形参)=0;

纯虚函数的作用是定义一个基类的接口,这个接口可以被派生类继承并实现。通过在基类中声明纯虚函数,我们可以强制派生类提供相应的实现,以确保派生类具有某些特定的行为或功能。

下面是一个使用纯虚函数的示例:

#include <iostream>
using namespace std;class Shape {
public:virtual double getArea() const = 0; // 纯虚函数
};class Rectangle : public Shape {
private:double length;double width;
public:Rectangle(double l, double w) : length(l), width(w) {}double getArea() const override { // 派生类必须实现纯虚函数return length * width;}
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}double getArea() const override { // 派生类必须实现纯虚函数return 3.14159 * radius * radius;}
};int main() {Shape* shape1 = new Rectangle(5, 6);Shape* shape2 = new Circle(3);cout << "矩形的面积: " << shape1->getArea() << endl;cout << "圆形的面积: " << shape2->getArea() << endl;delete shape1;delete shape2;return 0;
}

在上述示例中,我们定义了一个基类 Shape,其中声明了一个纯虚函数 getArea()。这个函数没有提供实现,只是要求派生类必须实现它。派生类 Rectangle 和 Circle 都继承自基类 Shape,并实现了 getArea() 函数。

在 main 函数中,我们创建了基类指针 shape1 和 shape2,分别指向派生类对象 Rectangle 和 Circle。通过调用 getArea() 函数,我们可以获得矩形和圆形的面积。

注意到,基类 Shape 是抽象类,因为它包含一个纯虚函数。抽象类不能被实例化,只能用作其他类的基类。而派生类必须实现纯虚函数,否则它们也会成为抽象类。

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

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

相关文章

串口通信(6)应用定时器中断+串口中断实现接收一串数据

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

I/O多路转接之select

承接上文&#xff1a;I/O模型之非阻塞IO-CSDN博客 简介 select函数原型介绍使用 一个select简单的服务器的代码书写 select的缺点 初识select 系统提供select函数来实现多路复用输入/输出模型 select系统调用是用来让我们的程序监视多个文件描述符的状态变化的; 程序会停在s…

springboot+Redis+AOP实现请求限流器

写在开头 本文参考技术帖 程序员那点事 主要对学习经验进行总结&#xff0c;也会加上自己的理解注释。 配置RedisTemplate实例 //配置redis 使用String数据结构 //对key value 进行序列化 //根据配置连接redis Configuration public class RedisLimiterHelper {Beanpublic …

unittest 通过TextTestRunner(failfast=True),失败或错误时停止执行case

failfast是unittest.TextTestRunner的一个参数&#xff0c;它用于控制测试运行过程中遇到第一个失败或错误的测试方法后是否立即停止执行。 当failfast设置为True时&#xff0c;一旦发现第一个失败或错误的测试方法&#xff0c;测试运行就会立即停止&#xff0c;并输出相应的失…

Java多版本环境共存配置

文章目录 1. jenv是什么&#xff1f;2. 安装3. 配置4.多版本切换5. 总结 现在已经基本过了那句“他发由他发&#xff0c;我用Java8”的时代了&#xff0c;越来越多的开发者开始拥抱Java11&#xff0c;Java17&#xff08;毕竟spring boot 3要求最低这个版本了&#xff09;&#…

如何保证缓存中都是热点数据?

确保缓存中保留热点数据是关键&#xff0c;因为热点数据通常是最常被访问的数据&#xff0c;提高了缓存的命中率和整体性能。以下是一些策略和方法&#xff0c;能够帮助你维护缓存中的热点数据&#xff1a; 缓存策略&#xff1a; 缓存预热&#xff1a; 在系统启动时或负载较低的…

sql基础+考点+题

查询&#xff1a;select from 筛选&#xff1a;where and和or 排序&#xff1a;order by&#xff08;降序排列需要指定DESC关键字&#xff09; join&#xff1a;left join 、right join 和inner join 分组聚合&#xff1a;group by ---搭配count , sum , avg 过滤&#x…

前端面试大纲

一、CSS 1.说一下CSS的盒模型。 在HTML页面中的所有元素都可以看成是一个盒子 盒子的组成&#xff1a;内容content、内边距padding、边框border、外边距margin 盒模型的类型&#xff1a; 标准盒模型 margin border padding content IE盒模型 margin content(border padd…

安全防御——二、ENSP防火墙实验学习

安全防御 一、防火墙接口以及模式配置1、untrust区域2、trust区域3、DMZ区域4、接口对演示 二、防火墙的策略1、定义与原理2、防火墙策略配置2.1 安全策略工作流程2.2 查询和创建会话 3、实验策略配置3.1 trust-to-untrust3.2 trust-to-dmz3.3 untrust-to-dmz 三、防火墙的区域…

消息中间件-RabbitMQ介绍

一、基础知识 1. 什么是RabbitMQ RabbitMQ是2007年发布&#xff0c;是一个在AMQP(高级消息队列协议)基础上完成的&#xff0c;简称MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法&#xff0c;由Erlang&#xff08;专门针对于大…

SPSS单因素方差分析

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

Python基础入门例程39-NP39 字符串之间的比较(运算符)

最近的博文&#xff1a; Python基础入门例程38-NP38 牛牛的逻辑运算&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程37-NP37 不低于与不超过&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程36-NP36 谁的数字大&#xff08;运算符&#xff09;-CSD…

FPGA芯片内部结构

参考链接&#xff1a;FPGA的进阶之第二章FPGA芯片内部结构&#xff08;2&#xff09;

航模模拟器训练

固定翼吃灰很久忘记咋么操作 故这里发帖防止忘记 准备物品 航模遥控器 aux线 即两端都是耳机插头的线 解密狗 电脑 phoenixRC 航模模拟软件(【飞舜极创】凤凰5.0飞行模拟器 安装和设置方法_哔哩哔哩_bilibili) 链接&#xff1a;https://pan.baidu.com/s/1XL4ZWhMR7MQMxDPC7B…

PTA: 矩阵的乘法运算

矩阵的乘法运算 题目输入格式输出格式输入样例输出样例 代码 题目 线性代数中的矩阵可以表示为一个row&#xff0a;column的二维数组&#xff0c;当row和column均为1时&#xff0c;退化为一个数&#xff0c;当row为1时&#xff0c;为一个行向量&#xff0c;当column为1时&…

GraphQL入门与开源的GraphQL引擎Hasura体验

背景 Hasura 是一个开源的 GraphQL 引擎&#xff0c;它可以帮助开发人员快速构建和部署现代应用程序的后端。它提供了一个自动化的 GraphQL API &#xff0c;可以直接连接到现有的数据库&#xff0c;并提供实时数据推送和订阅功能。 Hasura 团队总部位于印度。 下载安装 脚本…

简述多模态学习中,对齐、融合和表示

在多模态学习中&#xff0c;对齐、融合和表示是三个核心概念&#xff0c;它们相互关联&#xff0c;共同支持多模态数据的处理和分析。 对齐&#xff08;Alignment&#xff09; 对齐是多模态学习中的一个关键步骤&#xff0c;它涉及到如何在不同的数据模态之间发现和建立对应关…

分享86个工作总结PPT,总有一款适合您

分享86个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/12aRTr5NKG5YTnMnwNbqOrQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知…

顶层模块【FPGA】

1顶层模块&#xff1a; 不能像C语言的h文件那样&#xff0c;把io的定义放在其他文件。 在Verilog中&#xff0c;顶层模块是整个设计的最高层次&#xff0c;它包含了所有其他模块和子模块。 顶层模块定义了整个设计的输入和输出端口&#xff0c;以及各个子模块之间的连接方式。…

大数据毕业设计选题推荐-旅游景点游客数据分析-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…