【C++】运算符重载(日期类的实现)

文章目录

  • 前言
  • 一、运算符重载的概念和意义
  • 二、运算符重载的规则
  • 三、常用运算符重载
    • 1.关系运算符重载
    • 2.=赋值运算符重载
    • 3.+=、-=、+、-重载
    • 4.前置++和后置++重载
    • 5.流插入<<和流提取>>重载

前言

之前在总结类的六个默认成员函数时,没有过多介绍运算符重载,只简单介绍了赋值运算符重载。本节内容将会总结常用的运算符重载,以实现一个日期类为例。

一、运算符重载的概念和意义

什么是运算符重载?

运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时做出不同的行为。

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数。

运算符重载的语法格式

返回值类型 operator运算符(参数列表)
{函数体
}

:这里的运算符可以是+、-、*、/、>、>=等,但不能创建新的运算符如@、$等。
.* :: sizeof ?: .这5个运算符不支持重载。

二、运算符重载的规则

以下面一个日期类为例

class Date
{
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};

我们重载一个比较日期大小的运算符==

bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

判断是否相等的运算符==是双目运算符,所以参数列表有两个参数。因为传值传参会调用拷贝构造,降低效率,所以用传引用传参;
因为不改变实参,所以参数最好加上const;成员函数后面加const,对于不改变成员变量的成员函数,我们最好在函数名后面加上const,提高代码健壮性。

但是我们如果重载成全局函数,就需要类成员变量是公有属性,因为类外无法访问类的私有成员。但是这样就会破坏封装性。 因此,为了保证封装性,我们一般将运算符重载为类的友元函数或类的成员函数

  • 重载为类的友元函数(全局函数)
friend bool operator==(const Date& d1, const Date& d2);

函数定义不变,只需要在类中加上友元的声明,就可以正常使用上述函数,还不会破坏类的封装性。

  • 重载为类的成员函数
bool operator==(const Date& d) const
{return _year == d._year && _month == d._month && _day == d._day;//等价于//return this->_year == d._year && this->_month == d._month && this->_day == d._day;
}

可以看到,参数个数减少了一个,这是为什么呢?
答:这是由于类的每个非静态成员函数都有一个隐藏的this指针,占第一个参数的位置,也就是说,上述写法表面上是一个参数,实际上有两个参数。如下,但我们定义时不需要显示地传this指针。

//等价于,但不能这样写
bool operator==(Date* this, const Date& d2) const

注:对于不改变this的成员函数,我们最好在函数名后面加上const,变成常成员函数,增加代码健壮性。

总结:作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针

运算符重载的调用方法如下,和之前调用方法一样。

int main()
{Date d1(2024, 6, 25);Date d2(2024, 6, 24);//d1 == d2//重载为友元函数,等价于if(operator==(d1, d2))//重载为成员函数,等价于if(d1.operator==(d2))if (d1 == d2) {cout << "d1 == d2" << endl;}return 0;
}

运算符重载规则:

1.只能对已有的运算符进行重载,不可以创建新的运算符
2.重载后的运算符的优先级、结合性也应该保持不变,也不能改变其操作个数和语法结构。
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义。
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针。
5..* :: sizeof ?: . 这5个运算符不支持重载。
6.赋值、下标[]、调用()、成员访问->这4个运算符必须重载为类的成员函数,不能重载为全局函数。
4.若一个运算符的操作需要修改对象的状态(修改this指针),最好重载为类的成员函数。

三、常用运算符重载

1.关系运算符重载

总共有==、!=、<、<=、>、>=这六个关系运算符,进行对象之间的比较判断,所以返回值为false或者true,即bool类型。

这里我将关系运算符重载为类的成员函数(也可以重载为类的友元函数)

class Date
{
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day){}//函数重载声明bool operator==(const Date& d) const;bool operator!=(const Date& d) const;bool operator<(const Date& d) const;bool operator<=(const Date& d) const;bool operator>(const Date& d) const;bool operator>=(const Date& d) const;
private:int _year;int _month;int _day;
};
//定义
bool Date::operator==(const Date& d) const
{return _year == d._year && _month == d._month && _day == d._day;
}bool Date::operator!=(const Date& d) const
{return !(*this == d);
}bool Date::operator<(const Date& d) const
{if (_year == d._year){if (_month == d._month)return _day < d._day;elsereturn _month < d._month;}else{return _year < d._year;}
}bool Date::operator<=(const Date& d) const
{return *this < d || *this == d;
}bool Date::operator>(const Date& d) const
{return !(*this <= d);
}bool Date::operator>=(const Date& d) const
{return !(*this < d);
}

重载之后我们就可以比较类类型对象的大小。要理解关系运算符的对应关系,比如实现了=<重载,我们可以借此来更简单地实现<=>等。

2.=赋值运算符重载

前面总结类的六个默认成员函数时,已经介绍过赋值运算符重载。赋值运算符重载只能重载为类的成员函数。
赋值运算符重载用在两个及以上已存在的对象之间进行赋值。分清这点与拷贝构造(用已存在对象初始化新对象)的区别。

赋值运算符重载格式:

参数类型:const T&,传递引用可以提高传参效率;
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值;
返回*this :支持连续赋值;
检测是否自己给自己赋值;

日期类的赋值运算符重载(可以不用写)

Date& operator=(const Date& d)//传引用提高传参效率
{if (&d != this)//判断优化{_year = d._year;_month = d._month;_day = d._day;return *this;}
}

赋值运算符重载也是类的默认成员函数,我们不写,编译器会生成一个默认赋值运算符重载,完成数据的浅拷贝。

对于日期类这种成员变量都是内置类型的类,我们不需要显示定义赋值运算符重载,使用编译器默认生成的就可以了。但是涉及到资源申请的比如栈这种,浅拷贝就无法满足,需要我们自己显示定义赋值运算符重载完成深拷贝。

3.+=、-=、+、-重载

对于+=-=运算符,我们知道,这两个运算符会改变对象自身,且返回修改后的对象。

+= 的第二个操作数有可能是负数,-=的第二个操作数有可能是正数,因此先进行判断,情况要考虑周全。

Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month))//当前月份对应的天数{_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}return *this;//返回对象本身
}Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){_month--;if (_month <= 0){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;//返回对象本身
}

实现+=和-=后,+和-就可以套用了。当然也可以先实现+和-,再套用实现+=和-=

Date Date::operator+(int day) const
{Date tmp(*this);tmp += day;return tmp;//出了作用域销毁,不能用引用返回
}Date Date::operator-(int day) const
{Date tmp(*this);tmp -= 1;return tmp;//出了作用域销毁,不能用引用返回
}

上述函数重载都是对日期进行指定天数的加减运算,那如何进行日期与日期间的运算呢?运算符重载不能实现无意义的重载,比如两个日期相加,是不符合逻辑的;但两个日期是可以相减的,返回两个日期的差值。

int Date::operator-(const Date& d) const
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int cnt = 0;while (max != min){cnt++;min++;}return cnt * flag;
}

这里的减-与之前的减-构成重载关系。

注意:

1.+=-=会改变对象本身,且返回*this即对象本身,所以可以用引用返回提高效率。
2.+-并不会改变对象的值,返回的是对象进行加减运算后的值(临时变量),不能用引用返回。

4.前置++和后置++重载

前置++(- -)和后置++(- -)都是单目运算符,如果重载为类的成员函数,则是无参的。

为了让前置++与后置++能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。 也就是说,二者的调用还是和内置类型调用的方法一样,只不过定义重载时后置++的参数多个无意义的int参数。

由于前面已经实现了+=的功能,这里不再重复了,直接套用即可。

//前置++
Date& Date::operator++()
{*this += 1;return *this;
}
//后置++
Date Date::operator++(int)//int无任何意义,只是为了区分前置还是后置
{Date tmp(*this);*this += 1;return tmp;
}

调用方法

int main()
{Date d1(2024, 6, 25);++d1;//等价于d1.operator++();d1++;//等价于d1.operator++(0);return 0;
}

调用后置形式的重载函数时,对于那个没用的 int 形参,编译器自动以 0 作为实参。

前置++: 返回+1之后的结果,this指向的对象再函数结束后不会销毁,所以用引用返回提高效率。
后置++: 先使用后+1,因此需要返回+1之前的旧值,在实现时需要先将原始对象保存一份,然后*this+1;注意临时对象只能以值的方式返回,不能返回引用。

前置- -与后置- -也是同理,这里不再具体实现。

5.流插入<<和流提取>>重载

C++标准库对左移运算符<<和右移运算符>>分别进行了重载,与cout和cin搭配使用,使其能够用于不同数据的输入输出。

int i = 0;
double d = 1.23;
cout << i;
cout << d;

<<和>>可以直接支持内置类型是因为C++标准库里已经实现好了,我们可以直接使用;
可以直接支持自动类型识别是因为函数重载。

对于内置类型,我们可以直接使用,但对于自定义类型,我们需要重载这两个操作符。

比如对于日期类,我们重载这两个运算符后,可以实现输入和输出年月日。

Date d1;
cin >> d1;
cout << d1;

通过查阅C++官网资料,我们知道,cin是istrem类的对象,cout是是ostrem类的对象,这两个都在<iostream>头文件中;因为C++标准库中istrem类和ostrem类重载了内置类型的参数,所以内置类型可以直接使用并且可以自动识别类型。

在这里插入图片描述

在这里插入图片描述

为什么输入输出操作符要重载为友元函数,不能重载为成员函数?

我们知道,成员函数的第一个参数是隐藏的this指针,如果我们重载为成员函数,也就是说,this指针为左操作数,cout/cin为右操作数,那么就不符合我们常规的调用顺序,不符合使用习惯。

ostream& operator<<(ostream& out)
{out << _year << "-" << _month << "-" << _day << endl;return out;
}
//调用
int main()
{Date d1, d2;//与我们常规调用顺序相反cout << d1;d1 << cout;//d1.等价于operator<<(cout)return 0;
}

实际使用中cin/cout为第一个形参对象,才符合常规使用。所以要将这两个运算符重载成全局函数。但又会导致类外没办法访问非公有成员,就需要借助友元来解决。

class Date
{friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day << "-";return out;
}
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
int main()
{Date d1, d2;cin >> d1 >> d2;//等价于operator>>(operator>>(cin, d1), d2);cout << d1;//等价于operator<<(cout, d1);return 0;
}

为什么要有返回值? 为什么返回第一个参数的引用?
答:返回isteam/ostream类对象的引用作为下次调用时的左操作数,是为了能够连续读取。

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

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

相关文章

文生视频模型Sora刷屏的背后的数据支持

前言&#xff1a;近日&#xff0c;OpenAI的首个文生视频模型Sora横空出世&#xff0c;引发了一波Sora热潮。与其相关的概念股连续多日涨停&#xff0c;多家媒体持续跟踪报道&#xff0c;央视也针对Sora进行了报道&#xff0c;称这是第一个真正意义上的视频生成大模型。 01 …

第三十三篇——互联网广告:为什么Google搜索的广告效果好?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 对于信息的利用&#xff0c;再广告这个维度中去洞察&#xff0c;你又能发…

苹果CMS-V10 搭建教程踩坑,跳过部分验证

我突发奇想,想要安装一个CMS 苹果CMS搭建教程-CSDN博客 然后就有了下面的问题 结论是zip相关依赖未安装, 通过 apt install php-zip, 重新打开安装页面,同样如此 最后依据某个网站提示,修改 "\\192.168.1.200\root\var\www\html\maccms\application\install\control…

鸿蒙系统最简单安装谷歌服务及软件的方法

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 近日&#xff0c;华为开发者大会在东莞松山湖召开&#xff0c;发布了盘古大模型5.0和纯血版的鸿蒙 HarmonyOS NEXT 全场景智能操作系统&#xff0c;而根据研究机构 Counterpoint Resea…

AWS云计算平台:全方位服务与实践案例

摘要 在数字化浪潮的推动下&#xff0c;云计算已成为企业转型的强大引擎。AWS作为云计算的先锋&#xff0c;不仅提供了一系列强大的基础设施服务&#xff0c;更是在人工智能领域不断探索和创新。本文将带您领略AWS的全方位服务&#xff0c;并透过实际案例&#xff0c;感受其在…

Redis 7.x 系列【7】数据类型之列表(List)

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 常用命令2.1 RPUSH2.2 LPUSH2.3 LRANGE2.4 LINDEX2.6 LREM2.7 LLEN2.8 LPOP…

matlab量子纠缠态以及量子门操作下的量子态

前言 今天我们来聊聊题外话&#xff0c;量子纠缠&#xff0c;在目前物理分支中&#xff0c;要说最深&#xff0c;最能改变人类对宇宙影响的莫过于量子力学了&#xff0c;假如我们可以人为的对两个粒子施加纠缠态&#xff0c;那么我们将可以足不出户的完成对外界的操控 简介 …

三丰云免费虚拟主机及免费云服务器评测

三丰云是一家知名的云计算服务提供商&#xff0c;其免费虚拟主机和免费云服务器备受用户好评。为了更好地了解三丰云的服务质量&#xff0c;我们进行了详细的评测。首先&#xff0c;三丰云的免费虚拟主机提供稳定可靠的性能&#xff0c;让用户可以轻松搭建自己的网站。其免费云…

【博客719】时序数据库基石:LSM Tree的增删查改

时序数据库基石&#xff1a;LSM Tree的增删查改 LSM结构 LSM树将任何的对数据操作都转化为对内存中的Memtable的一次插入。Memtable可以使用任意内存数据结构&#xff0c;如HashTable&#xff0c;BTree&#xff0c;SkipList等。对于有事务控制需要的存储系统&#xff0c;需要在…

ChatGPT在程序开发中的应用:提升生产力的秘密武器

在当今飞速发展的科技时代&#xff0c;程序开发已经成为许多企业和个人必不可少的技能。然而&#xff0c;编写代码并非总是顺风顺水&#xff0c;面对复杂的算法、繁琐的调试、持续不断的需求变更&#xff0c;程序员们常常感到压力山大。在这种情况下&#xff0c;ChatGPT应运而生…

Python数据分析之-Oracle数据库连接

文章目录 cx_Oracle 介绍cx_Oracle运行原理cx_Oracle 安装linux环境安装windows环境安装 cx_Oracle 使用单独使用结合Pandas使用 参考资料 cx_Oracle 介绍 cx_Oracle 8是一个Python扩展模块&#xff0c;它提供了对Oracle数据库的访问能力。以下是cx_Oracle 8的一些关键特性和功…

Superagent:一个开源的AI助手框架与API

在人工智能日益普及的今天,如何将AI助手无缝集成到应用中成为了开发者们关注的焦点。今天,我们要介绍的Superagent正是一个为这一需求量身打造的开源框架与API。它结合了LLM、检索增强生成(RAG)和生成式AI技术,为开发者们提供了一个强大而灵活的解决方案。 一、Superagen…

获取个人免费版Ubuntu Pro

首先上官网地址&#xff1a;Ubuntu Pro | Ubuntu 点击页面中的"Get Ubuntu Pro now" 将用途选为“Myself”&#xff0c;在此页面中Ubuntu说明了该版本只面向个人开发者&#xff0c;且最终只允许5台设备免费使用&#xff1b;因而部署设备的抉择就不得不慎重考虑了&am…

【js + ckeditor】插入base64格式的图片

一、需求说明 直接把图片转成base64插入到富文本 二、需求分析 1、富文本图片格式处理位置 在ckeidtor的目录下有个plugins文件夹&#xff0c;在plugins下新建一个文件夹&#xff08;自己命名&#xff0c;如simpleupload&#xff09;&#xff0c;进入simpleupload文件夹&…

【Java Web】XML格式文件

目录 一、XML是什么 二、常见配置文件类型 *.properties类型&#xff1a; *.xml类型&#xff1a; 三、DOM4J读取xml配置文件 3.1 DOM4J的使用步骤 3.2 DOM4J的API介绍 一、XML是什么 XML即可扩展的标记语言&#xff0c;由标记语言可知其基本语法和HTML一样都是由标签构成的文件…

安卓直装植物大战僵尸杂交版V2.1版完美运行

安卓直装植物大战僵尸杂交版V2.1版完美运行 链接&#xff1a;https://pan.baidu.com/s/1SPFouV8T-AV2LnUoZfy6lQ?pwd3gl6 提取码&#xff1a;3gl6

【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解

最终效果 文章目录 最终效果前言存储位置信息存储更多数据存储场景信息持久化存储数据完结 前言 前面写过小型游戏存储功能&#xff1a; 【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解&#xff08;包含数据安全处理方案的加密解密&#xff09; 这次做一…

告别数据线!轻松实现iOS和安卓设备间的文件共享

用 AirDroid 的附近传输功能&#xff0c;完全免费&#xff0c;几十个G的文件也可以相互传输。不限制iPhone和iPad数量&#xff0c;多个设备同时登录也不会强迫下线。 当你要在苹果手机和安卓手机之间传输文件&#xff0c;请将AirDroid安装到两台手机上&#xff0c;然后登录同一…

搞定求职难题:工作岗位列表+简历制作工具 | 开源专题 No.75

SimplifyJobs/New-Grad-Positions Stars: 8.5k License: NOASSERTION 这个项目是一个用于分享和跟踪美国、加拿大或远程职位的软件工作机会列表。该项目的核心优势和关键特点如下&#xff1a; 自动更新新岗位信息便捷地提交问题进行贡献提供一键申请选项 BartoszJarocki/cv…

从0到1实现LLM学习笔记附录B(GPT-4o翻译版)

来源&#xff1a;https://github.com/rasbt/LLMs-from-scratch?tabreadme-ov-file https://www.manning.com/books/build-a-large-language-model-from-scratch