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

news2024/11/15 0:00:41

多态性

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

多态的概念和类型

消息:消息在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;
    }
    else
        cout << "错误的时间!" << 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.coloradmin.cn/o/1814184.html

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

相关文章

【C++进阶】模板与仿函数:C++编程中的泛型与函数式编程思想

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;栈和队列相关知识 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀模板进阶 &#x1f9e9;<&…

深度学习之激活函数

激活函数&#xff08;Activation Function&#xff09;是一种添加到人工神经网络中的函数&#xff0c;旨在帮助网络学习数据中的复杂模式。在神经元中&#xff0c;输入的input经过一系列加权求和后作用于另一个函数&#xff0c;这个函数就是这里的激活函数。 1. 为什么需要激活…

在Oxygen中如何打开文件管理器并显示文件所在目录

▲ 搜索“大龙谈智能内容”关注公众号▲ 在Oxygen中&#xff0c;我们常需要查看项目中的某文件在Windows“文件资源管理器”所在位置&#xff0c;从而进行拷贝、分享等各种操作。 通过以下方法可以实现&#xff1a; 1. 在Oxygen的“项目”中选择文件并单击右键 2. 选择菜单…

数据可视化后起之秀——pyecharts

题目一&#xff1a;绘制折线图&#xff0c;展示商家A与商家B各类饮品的销售额 题目描述&#xff1a; 编写程序。根据第9.3.1&#xff0c;绘制折线图&#xff0c;展示商家A与商家B各类饮品的销售额。 运行代码&#xff1a; #绘制折线图&#xff0c;展示商家A与商家B各类饮品的…

【区块链】解码拜占庭将军问题:区块链共识机制的哲学基石

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 解码拜占庭将军问题&#xff1a;区块链共识机制的哲学基石引言一、拜占庭将军问…

ComfyUI 快速搭建流程

相关地址 ComfyUIPytorch版本 环境准备 nvidia 3090 ----------------------------------------------------------------------------- | NVIDIA-SMI 515.65.01 Driver Version: 515.65.01 CUDA Version: 11.7 | |--------------------------------------------…

码垛机性能的关键因素及优化策略

在工业自动化领域&#xff0c;码垛机以其高效、准确的特点&#xff0c;成为现代生产线上的得力助手。然而&#xff0c;要想充分发挥码垛机的性能优势&#xff0c;就必须深入了解影响其性能的关键因素&#xff0c;并针对性地制定优化策略。星派将为您详细解析码垛机性能的关键影…

数据仓库与数据挖掘实验练习题

练习题2 1. 使用超级英雄列表来填充一个新的 Series 对象。 2. 使用力量元组来填充一个新的 Series 对象。 3. 创建一个 Series&#xff0c;将超级英雄作为索引标签&#xff0c;力量等级作为值。将这个 Series 赋值给 heroes 变量。 4. 提取 heroes Series 的前两行。 5. 提取 …

程序员必备的职业素养:专业精神、沟通能力与持续学习

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 专业精神&#xff1a;技术的执着追求 沟通能力&#xff1a;团队合作的桥梁 持续学习&#xff1a;不断进步的动力 结语 我的…

世优科技AI数字人多模态交互系统“世优波塔”正式发布

2024年6月6日&#xff0c;世优科技“波塔发布会”在北京举办&#xff0c;本次发布会上&#xff0c;世优科技以全新的“波塔”产品诠释了更高效、更智能、更全面的AI数字人产品及软硬件全场景解决方案&#xff0c;实现了世优品牌、产品和价值的全面跃迁。来自行业协会、数字产业…

商用车CAN数据数字化是促进生态环保高质量发展的桥梁纽带

在当今这个智能化、电动化、信息化、数字化快速发展的时代&#xff0c;其中数字化转型已经成为各行各业提升效率、优化管理的关键途径&#xff0c;21世纪这个被誉为“大数据时代”的纪元&#xff0c;数据的潜力、生产力、作为基础设施被无限放大&#xff0c;其在各个领域&#…

6-1RT-Thread事件集

6-1RT-Thread事件集 其中&#xff0c;一对多方式比较常见。一对多方式其线程与时间的关系又可分为特定时间触发唤醒线程&#xff0c;类似于逻辑非控制电路&#xff0c;灯泡亮灭&#xff0c;由一个开关就可以触发。 事件集中&#xff0c;任意事件触发唤醒线程类似于逻辑或控制电…

后继者00

题目链接 后继者 题目描述 注意点 题目中的树是二叉搜索树节点p在二叉搜索树中一定存在 解答思路 本题关键是找到值大于节点p的值的第一个节点&#xff0c;因为本题中的树是二叉搜索树&#xff0c;所以左子树的值始终小于根节点&#xff0c;右子树的值始终大于根节点访问到…

【画板案例-颜色 Objective-C语言】

一、接下来,我们来说这个颜色的问题, 1.设置这个颜色啊,那么,颜色,首先啊,就我们的示例程序而言,好,我们现在只要点击这个按钮, 就能够让某一条路径,也是达到不同的颜色, 我们可以让每一条路径,去设置颜色, 那么,这个颜色啊,首先,思路啊,大家先来分析一下, …

pytest中失败用例重跑

pip install pytest-rerunfailures 下载rerunfailures插件包 配置文件中加入命令 --reruns 次数 也可在命令行中pytest --rerun-failures2 可以在allure报告中看到重试效果

基于PID的直流电机自动控制系统的设计【MATLAB】

摘 要 本文在广泛查阅资料&#xff0c;了解直流电机特性的基础上&#xff0c;对直流电机的控制原理进行了的研究&#xff0c;设计了一款基于PID控制器的简单直流电机自动控制系统。 首先&#xff0c;分析了直流电机的应用背景和发展现状&#xff0c;对直流电机的工作原理和数学…

Linux系统之smem命令的基本使用

Linux系统之smem命令的基本使用 一、smem命令介绍二、smem命令的使用帮助2.1 smem命令的help帮助信息2.2 smem命令的语法解释 三、smem工具安装3.1 安装epel3.2 搜索smem包3.3 安装smem 四、smem命令的基本使用4.1 查看内存概览4.2 查看内存占用百分比4.3 查看系统内存使用情况…

FANUC机器人4种启动方式的区别

FANUC机器人4种启动方式的区别 1.初始化启动 执行初始化启动时,删除所有程序,所有设定返回标准值。 初始化启动完成时,自动执行控制启动。 说明: 执行初始化启动时,删除所有程序,所有设定返回标准值。初始化启动完成时,自动执行控制启动。执行初始化启动时,程序、设定等…

32+综述!百位大佬联袂解析“One Health”时代下新污染物对全球健康的影响

如果你关注环境健康&#xff0c;那么会发现以上研究发现层出不穷&#xff0c;新污染物正在“潜移默化”悄悄改变我们的个体健康和生存环境&#xff01; 新污染物是全球经济快速发展的“产物”。例如&#xff0c;微塑料作为一种经常出现的新污染物&#xff0c;可作为载体传播和…

Pikachu靶场--暴力破解

实验前的准备 问题解决 PHPStudy&#xff08;小皮&#xff09;V8.1安装后启动Apache报错AH00526: Syntax error 【数据库连接问题】【靶场访问错误】 抓不到本地靶场包的原因及解决方法_pakachu抓不到包 设置代理 BP添加和选择代理 火狐浏览器-->设置-->拓展-->搜索…