11_1、多态性:概念及运算符重载

多态性

  • 多态的概念和类型
    • 多态的类型
    • 多态的实现
  • 运算符重载
    • 运算符重载的概念和规则
      • 概念
      • 规则
    • 运算符重载为类的成员函数
      • 双目运算符
      • 单目运算符
    • 运算符重载为类的友元函数
      • 双目运算符重载
      • 单目运算符重载

多态的概念和类型

消息:消息在C++编程中指的是对类的成员函数的调用。
多态就是指相同的消息被不同类型的对象接收会引起不同的操作,直接点讲,就是在不同的情况下调用同名函数时,可能实际调用的并不是同一个函数。

多态的类型

多态性有四种类型:

  1. 重载多态(专用多态)。普通函数的重载和类的成员函数的重载,它们都属于重载多态。
  2. 强制多态(专用多态)。整型变量和浮点型变量相加时,需要先把整型变量强制转换为浮点型再进行加法运算,这就是强制多态。从概念上讲,强制多态就是将一个变量的类型进行转换,以满足一个函数运算的要求。
  3. 参数多态(通用多态)。类模板将类型参数化,设定了确定的类型才可以实例化。由类模板实例化得到的所有类都有相同的操作,但是被操作对象的类型不同,这就是参数多态
  4. 包含多态(通用多态)。包含多态是指类族中不同类的同名成员函数实现的操作不相同。包含多态一般通过虚函数来实现。

多态的实现

从多态实现的阶段不同来分类可以分为:

  1. 编译时的多态
  2. 运行时的多态

编译时的多态是指在编译的过程中就确定了具体调用同名函数中的哪个函数,而运行时的多态则是在程序运行过程中才动态的确定调用的具体函数。这种确定调用同名函数的哪个函数的过程就叫做联编或者绑定。

绑定实际上就是确定某个标识符对应的存储地址的过程。按照绑定发生的阶段的不同可以分为:静态绑定和动态绑定。静态绑定就对应着编译时的多态,动态绑定对应运行时的多态。

  1. 如果绑定过程发生在编译链接阶段,则称为静态绑定。在编译链接过程中,编译器根据类型匹配等特征确定某个同名标识究竟调用哪一段程序代码,也就是确定通过某个同名函数到底调用哪个函数体。1中的四种多态中有三种需要静态绑定:重载多态、强制多态和参数多态。
  2. 而如果绑定过程发生在程序运行阶段,则成为动态绑定。在编译链接过程中无法确定调用的具体函数,就要等到程序运行时动态确定。包含多态就需要使用动态绑定实现。

运算符重载

运算符重载的概念和规则

概念

运算符重载就是为预定义的一些运算符增加新的意义,使其因操作数类型的不同而产生不同的操作。

运算符重载实际上属于函数重载,因为在运算符重载中,不是运算符表达式而是调用运算符函数,操作数变成了运算符函数的参数,运算符函数的参数不同时调用不同的函数。这些与函数重载如出一辙。

为什么我们需要运算符重载?
因为自定义数据类型有时也需要使用运算符进行某些运算,比如加法运算,但是预定义的运算符的操作数只能是基本数据类型,所以自定义数据类型的运算需要进行运算符重载。

class Date
{
public:Date(int nYear, int nMonth, int nDay)  { m_nYear=nYear; m_nMonth=nMonth; m_nDay=nDay; }  // 构造函数void show();       // 显示日期
private:int m_nYear;int m_nMonth;int m_nDay;
};
  • 假设我们声明了两个Date类的对象:Date date1(2011, 11, 1), date2(2012, 1, 6);。然后需要计算date1和date2所表示日期差多少天,也就是进行减法运算,最简单的就是用运算符“-”,但是如果直接写date2-date1,编译器会报错,因为编译器不知道怎样进行此减法运算。这就需要我们自己写程序来说明在对Date类对象进行“-”运算时,具体做哪些处理,也就是需要进行运算符重载。

规则

运算符重载的使用有如下规则:

  1. 运算符重载是为了让自定义数据类型能够使用预定义运算符,对预定义运算符进行重定义,但一般重定义的功能与原运算符的功能相似,运算符重载的参数个数与原运算符的操作数个数相同,而且至少有一个参数属于自定义数据类型。
  2. 运算符重载后其优先级和结合性都与原运算符相同。
  3. 除了类属关系运算符“.”、成员指针运算符“.*”、作用域分辨符“::”、sizeof运算符和条件运算符“?:”这五种运算符外,其余C++运算符都能重载,而且只有C++中已有的运算符可以重载。

运算符重载为类的成员函数时的声明形式为:

函数类型 operator 运算符(参数表)
{函数体;
}
  • 函数类型是运算符重载的返回值类型。operator是声明和定义运算符重载时的关键字。运算符就是需要重载的运算符,比如“+”或“-”,但不能是“.”、“.*”、“::”、sizeof或“?:”。参数表列出重载运算符的参数及类型,这里当重载运算符不是后置“++”或“–”时,参数的个数比原运算符的操作数个数少一个,因为类的对象调用运算符重载成员函数时,自己的数据可以直接访问,不需要在参数表中传递,所以参数表中就不必列出该对象本身了。

运算符重载为类的友元函数时的声明形式为:

friend 函数类型 operator 运算符(参数表)
{函数体;
}
  • 与上面运算符重载为类的成员函数时不同的是,在函数类型前需要加关键字friend。另外,运算符重载友元函数访问类的对象的数据时,必须通过类的对象名访问,所以此友元函数的所有参数都需要进行传递,参数个数与原运算符的操作数个数相同。

在软件开发中用了运算符重载后,会体会到复杂类型数据也能进行加减运算的方便的。这让我们的程序书写更简单,可读性更高,更易维护,最终提高软件开发效率。

运算符重载为类的成员函数

运算符重载为类的成员函数后就可以像其他成员函数一样访问本类的数据成员了。在类的外部通过类的对象,可以像原运算符的使用方式那样使用重载的运算符,比如,“+”运算符被重载为类A的成员函数后,A的对象a和其他对象b就可以这样进行加法运算:a+b。
重载的运算符可能是双目运算符也可能是单目运算符。

双目运算符

如果是双目运算符,比如“+”和“-”,则一个操作数是使用此运算符的对象本身,另一个操作数使用运算符重载函数传递进来的对象。假设有双目运算符U,a为类A的对象,另有某类也可以是A类的对象b,我们想实现a U b这样的运算,就可以把U重载为类A的成员函数,此函数只有一个形参,形参的类型为对象b的类型。这样进行a U b的运算就相当于函数调用:a.operator U(b)。

#include <iostream>
using namespace std;
class CTimeSpan
{
public:CTimeSpan(int nHours = 0, int nMins = 0);      // 构造函数CTimeSpan operator +(CTimeSpan ts);        // 运算符“+”重载为成员函数int GetHours() { return m_nHours; }   // 获取小时数int GetMins() { return m_nMins; }    // 获取分钟数void Show();                               // 显示时间值
private:int m_nHours;       // 小时数int m_nMins;        // 分钟数
};
CTimeSpan::CTimeSpan(int nHours, int nMins)          // 构造函数的实现
{nHours += nMins / 60;nMins %= 60;m_nHours = nHours;m_nMins = nMins;
}
CTimeSpan CTimeSpan::operator +(CTimeSpan ts)    // 重载运算符函数实现
{int nNewHours;int nNewMins;nNewHours = m_nHours + ts.GetHours();nNewMins = m_nMins + ts.GetMins();nNewHours += nNewMins / 60;nNewMins %= 60;return CTimeSpan(nNewHours, nNewMins);
}
void CTimeSpan::Show()
{cout << m_nHours << "小时" << m_nMins << "分钟" << endl;
}
int main()
{CTimeSpan timeSpan1(2, 50);CTimeSpan timeSpan2(3, 30);CTimeSpan timeSum;timeSum = timeSpan1 + timeSpan2;cout << "timeSpan1: ";timeSpan1.Show();cout << "timeSpan2: ";timeSpan2.Show();timeSum = timeSpan1 + timeSpan2;cout << "timeSum=timeSpan1+timeSpan2: ";timeSum.Show();return 0;
}

在这里插入图片描述

  • 运算符重载成员函数跟一般的成员函数类似,只是使用了关键字operator。使用重载运算符的方式与原运算符相同。运算符作用于整型、浮点型和CTimeSpan等不同的对象会发生不同的操作行为,这就是多态性。

单目运算符

如果是单目运算符,比如“++”和“–”,操作数就是此对象本身,重载函数不需要传递参数,只是后置单目运算符语法上规定有一个形式上的参数,以区别于前置单目运算符。

  1. 假设有前置单目运算符U,如前置“++”,a为类A的对象,我们想实现U a这样的运算,也可以把U重载为类A的成员函数,此函数没有形参。这样U a表达式就相当于函数调用:a.operator U()。
  2. 假设有后置单目运算符U,如后置“–”,a为类A的对象,我们想实现a U这样的运算,同样可以把U重载为类A的成员函数,但此函数需要有一个整型的形参。重载后a U表达式就相当于函数调用:a.operator U(0)。

前置单目运算符重载和后置单目运算符重载在语法形式上的区别就是前者重载函数没有形参,而后者重载函数有一个整型形参,此形参对函数体没有任何影响,这只是语法上的规定,仅仅是为了区分前置和后置。

#include <iostream>
using namespace std;
class Clock //时钟类声明
{
public: //外部接口Clock(int NewH = 0, int NewM = 0, int NewS = 0);void ShowTime();Clock& operator ++();  //前置单目运算符重载Clock operator ++(int);  //后置单目运算符重载
private: //私有数据成员int Hour, Minute, Second;
};
Clock::Clock(int NewH, int NewM, int NewS)
{if (0 <= NewH && NewH < 24 && 0 <= NewM && NewM < 60 && 0 <= NewS && NewS < 60){Hour = NewH;Minute = NewM;Second = NewS;}elsecout << "错误的时间!" << endl;
}
void Clock::ShowTime()
{cout << Hour << ":" << Minute << ":" << Second << endl;
}
Clock& Clock::operator ++() //前置单目运算符重载函数
{Second++;if (Second >= 60){Second = Second - 60;Minute++;if (Minute >= 60){Minute = Minute - 60;Hour++;Hour = Hour % 24;}}return *this;
}
//后置单目运算符重载
Clock Clock::operator ++(int)      //注意形参表中的整型参数 
{Clock old = *this;      //此处返回的为old的指针,将没有参与前置运算的指针返回++(*this);return old;
}
int main()
{Clock myClock(23, 59, 59);cout << "初始时间myClock:";myClock.ShowTime();cout << "myClock++:";(myClock++).ShowTime();myClock.ShowTime();cout << "++myClock:";(++myClock).ShowTime();return 0;
}

在这里插入图片描述

  • 此处区分前置运算和后置运算的区别:前置在显示前处理,后置在显示后处理(返回操作之前的指针即可)。
  • 因为后置单目运算符重载函数中的整型形参没有实际意义,只是为了区分前置和后置,所以参数表中只给出类型就行了,参数名写不写都可以。

运算符重载为类的友元函数

友元函数通过类的对象可以访问类的公有、保护和私有成员,也就是类的所有成员友元函数都能访问到。所以运算符重载为类的友元函数以后也可以访问类的所有成员。
与运算符重载为成员函数时不同的是,重载的友元函数不属于任何类,运算符的操作数都需要通过函数的形参表传递。操作数在形参表中从左到右出现的顺序就是用运算符写表达式时操作数的顺序。

双目运算符重载

如果有双目运算符U,它的其中一个操作数是类A的对象a,那么运算符U就可以重载为类A的友元函数,此友元函数的两个参数中,一个是类A的对象,另一个是其他对象,也可以是类A的对象。这样双目运算符重载为类的友元函数后,假设运算符的两一个操作数是对象b,则表达式a U b就相当于调用函数operator U(a, b)。

#include <iostream>
using namespace std;
class CTimeSpan
{
public:CTimeSpan(int nHours = 0, int nMins = 0);      // 构造函数friend CTimeSpan operator +(CTimeSpan ts1, CTimeSpan ts2); // 运算符“+”重载为成员函数int GetHours() { return m_nHours; }   // 获取小时数int GetMins() { return m_nMins; }    // 获取分钟数void Show();                               // 显示时间值
private:int m_nHours;       // 小时数int m_nMins;        // 分钟数
};
CTimeSpan::CTimeSpan(int nHours, int nMins)          // 构造函数的实现
{nHours += nMins / 60;nMins %= 60;m_nHours = nHours;m_nMins = nMins;
}
void CTimeSpan::Show()
{cout << m_nHours << "小时" << m_nMins << "分钟" << endl;
}
CTimeSpan operator +(CTimeSpan ts1, CTimeSpan ts2)  // 重载运算符函数实现
{int nNewHours;int nNewMins;nNewHours = ts1.m_nHours + ts2.m_nHours;nNewMins = ts1.m_nMins + ts2.m_nMins;nNewHours += nNewMins / 60;nNewMins %= 60;return CTimeSpan(nNewHours, nNewMins);
}
int main()
{CTimeSpan timeSpan1(2, 50);CTimeSpan timeSpan2(3, 30);CTimeSpan timeSum;timeSum = timeSpan1 + timeSpan2;cout << "timeSpan1: ";timeSpan1.Show();cout << "timeSpan2: ";timeSpan2.Show();timeSum = timeSpan1 + timeSpan2;cout << "timeSum=timeSpan1+timeSpan2: ";timeSum.Show();return 0;
}

在这里插入图片描述

  • 加法运算符重载为CTimeSpan类的友元函数而不是成员函数,我们看到运算符重载函数有两个形参ts1和ts2,通过这两个参数将需要进行运算的操作数传递进去,而在此函数中也能够访问类CTimeSpan的私有成员m_nHours和m_nMins。
  • 此处为+的运算符重载,还有其他运算符的重载。不同的运算符的操作方式不同。

单目运算符重载

如果有前置单目运算符U,比如前置“–”,a为类A的对象,我们想实现U a这样的运算,就可以把U重载为类A的友元函数,此友元函数只有一个形参,为类A的对象,重载后表达式U a相当于调用函数operator U(a)。如果是后置单目运算符U,如后置“++”,a还是类A的对象,那么要实现a U这样的运算,也可以把U重载为类A的友元函数,此时友元函数就需要有两个形参,一个是类A的对象,另一个是整型形参,此整型形参没有实际意义,与上一节后置单目运算符重载为成员函数时的整型形参一样,只是为了区分前置运算符和后置运算符的重载。重载后表达式a U就相当于调用函数operator U(a, 0)。

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

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

相关文章

[2024-06]-[大模型]-[Ollama]- WebUI

主要涉及要部署的前端webui是来源于:https://github.com/open-webui/open-webui 正常就使用: docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-web…

【机器学习】基于CNN-RNN模型的验证码图片识别

1. 引言 1.1. OCR技术研究的背景 1.1.1. OCR技术能够提升互联网体验 随着互联网应用的广泛普及&#xff0c;用户在日常操作中频繁遇到需要输入验证码的场景&#xff0c;无论是在登录、注册、支付还是其他敏感操作中&#xff0c;验证码都扮演着重要角色来确保安全性。然而&am…

不谈AI能替代什么,就聊能帮我们干活的AI客服机器人!

最近在直播群里有老板在找客服外包&#xff0c;因为客服压力大&#xff0c;接不过来了&#xff0c;想找找外包支援一下。据了解电商客服除了压力大&#xff0c;还会遇到过量的重复劳动&#xff0c;比如我们问一个产品&#xff0c;同一天可能就有几百上千客户问同样的问题&#…

[Shell编程学习路线]--shell中重定向和管道符(详细介绍)

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f6e0;️Shell编程专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月12日10点50分 &#x1f004;️文章质量&#xff1a;93分 ——前言—— 在Shell编程中&#xff0c;重定向和管道符是两个…

短剧系统满足个性化需求,推动行业持续发展

一、短剧市场背景&#xff0c;行业发展迅速 随着科技的不断进步和观众需求的日益多样化&#xff0c;传统的影视制作方式已无法满足市场和观众的需求。因此&#xff0c;随着ai智能运用的发展&#xff0c;短剧系统以用户体验为中心&#xff0c;围绕观影体验和增强用户黏性展开设计…

mac 安装HomeBrew

目录 一、HomeBrew是什么&#xff1f;二、HomeBrew命令1、检查是否安装HomeBrew2、更新brew版本 三、安装HomeBrew1、官网安装2、100%成功安装 一、HomeBrew是什么&#xff1f; homebrew是一款Mac OS平台下的软件包管理工具&#xff0c;拥有安装、卸载、更新、查看、搜索等功能…

echart盒子没有跟着当前div大小变化而自适应

一、问题描述 当echarts图表在一个盒子里的时候&#xff0c;盒子大小变化了&#xff0c;但是图表没有跟着自适应&#xff0c;比如这样&#xff0c;盒子变大了&#xff0c;但是图表没变化 二、解决方法 在盒子大小更改的同时&#xff0c;调用图表的resize方法&#xff0c;记…

Linux kernel本地权限提升漏洞(CentOS8升级内核的解决方案)

一、CentOS8升级kernel内核的必要性 1、增强系统的安全性。 升级CentOS内核可以提供更好的安全性保障。新的内核版本通常包含了的安全补丁和漏洞修复&#xff0c;可以有效防止系统遭受恶意攻击&#xff0c;提高系统的稳定性和安全性。 2、优化硬件兼容性。 CentOS升级内核可以…

盐酸酸洗废水合理的处理手段

盐酸酸洗废水处理是工业生产中一项重要的环保措施&#xff0c;特别是在钢铁、金属加工等行业中&#xff0c;酸洗废水因其高酸性和高金属离子含量而需要得到妥善处理。以下是对盐酸酸洗废水处理的详细分析和讨论。 一、盐酸酸洗废水的特性 盐酸酸洗废水主要来源于钢材的酸洗线&a…

Golang——gRPC认证

一. OpenSSL 1.1 介绍 OpenSSL是一个开放源代码的软件库包&#xff0c;用于支持网络通讯过程中的加密。这个库提供的功能包含了SSL和TLS协议的实现&#xff0c;并可用于生成密钥、证书、进行密码运算等。 其组成主要包括一下三个组件&#xff1a; openssl&#xff1a;多用途的命…

MTK烧录USB驱动下载

下载链接 https://www.catalog.update.microsoft.com/Search.aspx?qMediaTek%20USB%20Port 驱动安装教程 https://miuiver.com/install-official-mediatek-driver/

@Test注解方法,方法无法执行

1.背景 写了一个测试方法,执行后如图 2.原因是 该项目是springbootgradle...构建的项目 在build.gradle配置文件中关闭了单元测试: test {useJUnitPlatform()// 是否启用单元测试enabled false } 3.处理方式 开启单元测试 test {useJUnitPlatform()// 是否启用单元测试ena…

操作系统复习-Linux的文件系统

文件系统概述 FAT FAT(File Allocation Table)FAT16、FAT32等&#xff0c;微软Dos/Windows使用的文件系统使用一张表保存盘块的信息 NTFS NTFS (New Technology File System)WindowsNT环境的文件系统NTFS对FAT进行了改进&#xff0c;取代了日的文件系统 EXT EXT(Extended…

全域推广是什么意思?业务范围有哪些?

随着全域时代的到来&#xff0c;与全域相关的各种概念不断涌现&#xff0c;引发了一轮又一轮的热议。在此背景下&#xff0c;全域推广一经出现便一跃成为了互联网的有一大热词&#xff0c;以全域推广是什么意思为代表的相关问题也成为了多个创业者社群中的热点话题。 相关资料显…

Linux基础(2)基础命令与vim

文件的复制和移动 cp 拷贝文件和目录 cp file file_copy --> file 是目标文件&#xff0c;file_copy 是拷贝出来的文件 cp file one --> 把 file 文件拷贝到 one 目录下&#xff0c;并且文件名依然为 file cp file one/file_copy --> 把 file 文件拷贝到 one 目录下…

MCK主机加固:智能科技,构筑网络安全的铜墙铁壁

在数字化转型的浪潮中&#xff0c;企业服务器的安全已成为维护业务连续性和保护数据资产的关键。MCK主机加固产品&#xff0c;以其创新技术&#xff0c;为企业提供了一个全面、智能、高效的安全解决方案。 一、智能安全监测 MCK主机加固产品采用深度学习算法&#xff0c;能够…

SpringCloud-OpenFeign拓展-连接池、最佳使用方法、日志输出

目录 1 OpenFeign连接池 1.1 常见连接类型 1.2 连接池使用方法 1.2.1 引入依赖 1.2.2 开启连接池功能 1.2.3 配置完成&#xff0c;重启实例即可&#xff0c;底层将更改设置。 2 OpenFeign最佳使用方法 2.1 每个微服务都是单独的project&#xff0c;内部有三个独立模块 …

C# Web控件与数据感应之模板循环输出

目录 关于模板循环输出 准备数据源 ​范例运行环境 RepeatHtml 方法 设计与实现 如何获取模板内容 getOuterHtml 方法 getInnerHtml 方法 调用示例 小结 关于模板循环输出 数据感应也即数据捆绑&#xff0c;是一种动态的&#xff0c;Web控件与数据源之间的交互&…

测试开发面经分享,面试七天速成 DAY 1

1. get、post、put、delete的区别 a. get请求&#xff1a; i. 用于从服务器获取资源。请求参数附加在URL的查询字符串中。 ii. 对服务器的请求是幂等的&#xff0c;即多次相同的GET请求应该返回相同的结果。 iii. 可以被缓存&#xff0c;可以被收藏为书签。 iv. 对于敏感数据不…

vscode打包vue项目

1&#xff09;npm install 2&#xff09;npm install -g vue/cli 3&#xff09;npm run build 第三步注意&#xff1a;要查看根目录下 package.json 配置&#xff1b; 如果和我的一样 3&#xff09;应该为 npm run build:prod 4)将dist 文件考到nginx等web服务器即可使用…