首页 > 其他分享 >11_1、多态性:概念及运算符重载

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

时间:2024-06-12 15:31:20浏览次数:10  
标签:11 函数 int 多态性 多态 运算符 CTimeSpan 重载

多态性

多态的概念和类型

消息:消息在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)。

标签:11,函数,int,多态性,多态,运算符,CTimeSpan,重载
From: https://blog.csdn.net/qingttqing/article/details/139605490

相关文章

  • dark1130_theme.xml sourceinsight 主题
    <ThemeList> <Theme Name="dark1130" > <DisplayColors> <ItemName="DefaultText"Color="#e0e0e0"/> <ItemName="WindowBackground"Color="#001515"/> <ItemName......
  • Arria 10 GX现场可编程门阵列10AX115N1F40I1SG、10AX115R2F40I2SG、10AX115R2F40I1SG
    Arria®10器件系列包括高性能,低功耗的20nm中端FPGA和SoC。Arria®10器件系列实现了:比上一代中高端FPGA更高的性能。通过一套综合节能技术来降低功耗。Arria®10器件专为各领域中高性能、功耗敏感的中端应用而设计。与竞争对手相比,利用公开的OpenCore设计,Arria®10F......
  • 【CMake系列】11-CMake Pack
    cmakepack用于将我们的写好的项目打包,发送给使用方;打包后产生的内容有源代码包二进制包平台原生的二进制安装Debian->.debredhat->.rpmmacOS->.dmgwindows->NSIS本专栏的实践代码全部放在github上,欢迎star!!!如有问题,欢迎留言、......
  • C51学习归纳11 --- PWM原理、应用案例
        本节进入到一个更加常用的环节PWM的产生与应用,日常生活中,电机的使用非常普及,如何控制转速?其实就可以应用我的PWM。一、PWM的产生原理    PWM(PulseWidthModulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需......
  • 《UML基础、案例与应用》习题记录-第11章
    部分习题,使用visio或plantuml,非正确答案,仅供参考,欢迎评论,谢绝转载。第11章活动图11.10.2习题1.活动图 2.活动图 3.活动图,动作带钉 4.活动图 ......
  • 6.11
    上午计网实验三  综合性训练(搭建中小企业园区网)  一、实验目的: 1.通过对网络设备的连通和对拓扑的分析,加深对常见典型局域网拓扑的理解; 2.通过路由建立起网络之间的连接,了解网络路由的设计与配置;3.进一步熟悉交换机、路由器的基本操作命令。二、实验设备:六台PC,两台二......
  • 【练习代码】6.11 java学习记录:继承与多态(实例:媒体资料库的设计)
    设计一个媒体资料库,能存入不同类别的媒体资料,例如CD与DVD,并且能完成添加与列表等操作,需要些什么?最基础的想法一个代表整体库的DataBase类,内部的属性包括CD和DVD的Arraylist,对应操作通过定义自己的方法来实现,部分代码如下:publicclassDatabase{privateArrayList<CD>......
  • WIN11系统下JDK1.8保姆级别安装步骤
    一、下载安装包链接:https://pan.baidu.com/s/1KonVGsgl8u6nDpvOR84zxw?pwd=meb0 提取码:meb0 可以直接去官网下载,也可以选择我提供的百度网盘链接下载二、JDK安装过程1.双击下载好后的安装包,点击下一步2.修改安装目录,可以自己在D盘建一个文件夹3.jre的安装,点击完成......
  • 【网络编程开发】11.IO模型 12.IO多路复用
    11.IO模型什么是IO:IO是Input/Output的缩写,指的是输入和输出。在计算机当中,IO操作通常指将数据从一个设备或文件中读取到计算机内存中,或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。通常用户进程中的一个完整I/O分为两个阶段......
  • 6/11
    SparkSQL的所有语句 SparkSQL提供了一种基于结构化数据处理的高级API,它允许使用SQL或DataFrameAPI进行数据查询和分析。以下是SparkSQL中常用的语句:创建表格:使用CREATETABLE语句创建表格,可以基于现有数据源或手动定义模式。示例:CREATETABLEtableName(col......