21天学会C++:Day11----运算符重载

· CSDN的uu们,大家好。这里是C++入门的第十一讲。
· 座右铭:前路坎坷,披荆斩棘,扶摇直上。
· 博客主页: @姬如祎
· 收录专栏:C++专题

 

目录

1. 知识引入

2. 运算符重载

2.1 operator<() 

2.2 operator=()

2.3 operator+=

2.4 operator++

2.5 operator<<

2.6 operator>> 

3. 运算符重载总结


1. 知识引入

来看下面的代码,我们定义了一个日期类,实现了他的构造函数和拷贝构造函数。现在我们想要比较两个日期的大小,如果是你的话,你会怎么写呢?

class Date
{
public://构造函数Date(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}//拷贝构造函数Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;
};int main()
{return 0;
}

 你可能会写一个成员函数,假设你写的是比较两个对象谁比较小。你可能会写出一个名为Less的函数,里面封装了两个Date类对象比较大小的逻辑。

	bool Less(const Date& d){if (_year < d._year|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day)){return true;}return false;}

然后你实例化出来两个对象运行代码发现并没有问题,非常nice。 

但是这样做是不是有点麻烦呢?于是你想:要是可以直接这样写该多好呀!

cout << (d1 < d2) << endl;

直接这样写肯定是不行的。对于内置类型,编译器知道如何去比较,但是对于自定义类型,编译器就无从下手了!因为他不知道你定义的类型里面有哪些成员变量,比较逻辑是什么?

那我们该怎么做呢?C++祖师爷本贾尼设计出了一个叫做运算符重载的东东,能够满足你的一切幻想。

2. 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

1:不能通过连接其他符号来创建新的操作符:比如operator@。

2:重载操作符必须有一个自定义类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义。

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

4:.*   ::   sizeof   ?:   .   以上5个运算符不能重载。这个经常在笔试选择题中出现。 

上面提到过,重载运算符的函数可以写在类里面,也可以写在类外面 (赋值运算符重载是例外哦!前一讲提到过赋值运算符是类的6个默认成员函数之一,你如果在类外重载赋值运算符,那么编译器就会提供默认的赋值运算符重载的函数,从而与你类外重载的赋值运算符冲突。) 

2.1 operator<() 

好的我们现在就来重载一个小于运算符试试吧:下面的代码是在类内书写的版本:

bool operator<(const Date& d)
{if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}return false;
}

我们回看运算符重载的特性:一个运算符有几个操作数,那么operator该运算符的形参列表就会有几个参数。我们在类里面实现的<符号的重载只有一个参数,那是因为类成员函数都有一个隐藏的this。

在我们重载了<运算符之后,d1 < d2的调用逻辑还是:d1.operator<(d2)。通过上面的汇编代码我们也能够看出来!

那么在类外重载<运算符应该怎么书写呢?大家不妨一试,谨记operator函数参数列表参数的个数和操作数的个数是一样的哦!

bool operator<(const Date& d1, const Date& d2)
{if (d1._year < d2._year){return true;}else if (d1._year == d2._year && d1._month < d2._month){return true;}else if (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day){return true;}return false;
}

 如果你是这么写的,那么恭喜你,写对了。但是如果在定义Date类的时候,你将成员变量全部设置为私有,你这里就会出现私有成员无法访问的报错提示。这该怎么解决呢?

方法一:直接将成员变量的访问权限修改为public。(成员变量暴露,不推荐)

方法二:提供能够获取到成员变量的函数,比如:GetYear() 等等。(太麻烦不推荐)

方法三:友元。在C++中有一个关键字:friend。他能够将一个函数或者一个类设置为另一个类的友元。设置为友元之后,这个函数或者类就可以直接访问另一个类被private修饰的成员变量和成员函数。

语法:friend + 函数或者类的声明

例如:我们的operator<的全局函数想要访问Date类中的私有成员,就可以在Date类中将operator<声明为Date类的友元:

方法四:直接把operator<函数写在类的里面(这里的写在里面,可以是operator<函数的定义在类里面,实现在类外面)。(推荐的做法)

2.2 operator=()

什么是赋值运算符重载?显然就是重载=这个运算符哇!

赋值运算符重载有什么用已经存在的两个对象,当我们将一个对象通过 = 运算符赋值给另一个对象时编译器会自动调用赋值运算符重载。

C++的前一讲我们知道赋值运算符重载也是类的6个默认成员函数之一。我们没有书写编译器会自动提供的,那么编译器自动提供的赋值运算符重载会干什么呢?想必经过之前的学习你已经能猜个大概了吧。编译器提供的赋值运算符重载对内置类型直接赋值,对自定义类型会调用该自定义类型的赋值运算符重载。我们来尝试写一个赋值运算符重载的函数吧:

void operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}

我们可以看到重载=运算符之后,对象的赋值就算是成功了!通过汇编代码我们能够看到对象的赋值实际上就是调用了赋值运算符的重载函数!

但是 = 运算符的重载还没完!我们在写代码的时候肯定见过这样的代码吧:

int a, b, c;
int d = 1;
a = b = c = d;

没错就是连续赋值的问题!我们来看看我们写的operator<能否做到连续赋值呢?

因此对于我们的 operator= 还需要修改一下我们的返回值来确保连续赋值的正确性。

	Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}

因为我们的Date类里面的成员变量全部都是内置类型,因此,编译器默认提供的赋值运算符重载和拷贝构造就够用了!我们就不需要动手自己写了!但是如果说你定义的类里面有成员变量维护了堆区的空间,那么就需要你自己动手写拷贝构造和赋值运算符重载了!!赋值运算符重载为什么也要写呢,原因就是因为编译器默认提供的赋值运算符重载对于内置类型是直接赋值嘛!!!任何的指针都是内置类型,当我们的指针维护有堆区的空间时,直接复制就会有两个指针指向同一块堆区的空间,在对象销毁的时候就会发生二次析构的问题(前提是你正确书写了析构函数,没正确书写析构函数的话就是内存泄漏了)。

下面我们来看看赋值运算符重载与拷贝构造的区别

拷贝构造:用一个已经存在的对象来初始化一个正在实例化的对象,是构造函数!

赋值运算符重载:已经存在两个对象,将一个对象成员变量的值赋值给另一个对象的成员变量。

直接看代码:请问下面的代码 Date d2 = d1; 调用的是赋值运算符重载还是拷贝构造呢?

int main()
{Date d1(2004, 01, 01);Date d2 = d1;
}

答案是拷贝构造函数啦!当你分不清的时候,就看看拷贝构造函数与赋值运算符重载的定义!这里是用 d1 这个对象去初始化一个正在实例化的对象,当然是拷贝构造啦!

在赋值运算重载的实现里面,我们习惯加上一个判断:判断是否是自己给自己赋值,如果是的话,就不用赋值,直接结束函数即可!因为没有太大的意义嘛!

//不会修改d的内容建议加上const
Date& operator=(const Date& d)
{if (this == &d)return *this;_year = d._year;_month = d._month;_day = d._day;return *this;
}

2.3 operator+=

我们重载+=这个运算符的目的就是为了能够让日期对象加上一个常数,然后计算加上该常数之后的日期时多少!该函数的原型:Date& operator+=(int day);

我们之前看到运算符重载的函数要求时必须要有一个自定义类型!这里看上去虽然没有,但是还是有一个隐藏的this呢!

+=的逻辑应该怎么写呢?因为在增加天数的过程中会涉及月份或者年份的增加,我们需要能够判断是否到达了增加月份的条件,因此我们可以封转一个函数,用于返回这个月的天数!例如GetMonthDay(int year, int month),这个函数用于返回当前月的天数。在operator+=的函数体中,我们直接让对象的_day加上传过来的形参,然后循环与当前月的实际天数作比较,如果大于当前月的实际天数,我们就让月份 + 1,同时让_day减去当前月的实际天数。直到_day小于当前月的实际天数为止!在此过程中注意到月份如果大于12则需要将年份+1,同时将月份修正为1。

//获取一个月的实际天数, 不可以返回int& 因为 29 没法寻址
int GetMonthDay(int year, int month)
{static int daysArr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}else{return daysArr[month];}
}//重载的 += 运算符
Date& operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;
}

2.4 operator++

 在实现日期类的时候++运算符的重载也是很有必要的!++运算符右前置++和后置++两种。我们先来讲讲前置++:前置++是先++,然后返回++之后的对象!

实现方式很简单啊!因为我们之前就已经实现过 operator+= 了我们只需要复用这个接口就行了!

//返回引用的目的是提高效率
Date& operator++()
{*this += 1;return *this;
}

我们来看后置++应该怎么写!因为前置++和后置++的运算符相同,操作数也相同!祖师爷本贾尼就规定后置++的运算符重载需要多加一个形参以示区分。函数的原型 :Date& operator++(int),没错是不需要写形参的!这个int只用来与前置++构成函数重载的,接受形参并没有多大的意义。因此我们可以不用写形参!后置++返回的是++之前的那个对象,因此我们还需要创建一个对象来保存++之前的值,用于函数的返回值:

//返回值不能是引用!不可返回局部对象的引用
Date operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}

我们可以看到在调用前置++的时候只传了一个实参,调用后置++的时候传了两个实参!

2.5 operator<<

 <<这是什么操作符?在C++的第二讲提到过,这个叫做流插入运算符!cout << "hello world" ;你肯定见过嘛!我们要打印Date对象,总不能也去写一个Print函数撒!太麻烦了!

我要直接cout << d1;

于是我们就需要重载流插入运算符了!

我们观察下面的图发现:cout是ostream的对象,嘿嘿传参的问题就得到了解决!

我们先来在类里面重载流插入运算符看看是否正确吧!

ostream& operator<<(ostream& out)
{out << _year << "-" << _month << "-" << _day << endl;return out;
}

这样写没什么大问题,但是调用的时候就非常奇怪,因为我们的this指针位于形参的第一个,想要调用operator<<就必须让Date对象在前面,Date对象在前面确实能够调用了!但是非常不符合我们cout的使用习惯!因此我们需要将流插入的重载写在全局!

我们把流插入运算符的重载写在全局,并且让Date对象位于第二个参数,并且让返回值是一个ostream的对象!就能实现正确习惯的连续流插入了! 

注意:形参不能加const,流插入是要往对象里面写东西的!你加上const就没法写东西了!

ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day;return out;
}

还有一个知识点:假设你写了一个Display()函数来打印Date对象中的成员变量! 这里仅用这个例子来讲解知识点,重载流插入更方便嘛!

你发现普通对象调用Display()没有任何问题,但是const 对象调用Display()就会出问题!这是为什么呢? 我们知道,Display()的参数中有一个隐藏的this指针,指向调用该函数的对象。当我们用普通对象调用Display()传过去的this指针是这样的:Date*;当我们用const对象调用 Display() 传过去的this指针是这样的:const Date*。而我们的Display()的形参是这样的:Date* 。显然是不能用Date* 去接受const Date* 的实参的!会发生权限的放大!

因此我们只要修改Display()的形参让他的this指针是 const Date* 就能解决这个问题了!祖师爷想来想去最后决定在成员函数后面加上const,此const 修饰 *this 代表this指向的内容不允许修改!

2.6 operator>> 

流插入你会写了,流提取问题应该也不大!cin是一个istream的对象!好的快去尝试写写吧!

istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;
}

对象d不可以加const修饰,因为你要向里面写入内容嘛!记得加友元或者封转函数返回私有的成员变量哦!

3. 运算符重载总结

 以上实现的运算符重载并不包含所有,但是包含了大部分的运算符重载的知识。

例如:operator+(),operator-(),operator<() 等等。

重载运算符的时候能复用就尽量复用哈!比如你重载了 < 和 == 运算符,那么 > ,  >= , <= , != 都可以复用你重载的 < 和 == 运算符!

 运算符重载并不是每一个都需要写!根据你的需求重载相对应的运算符即可!

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

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

相关文章

Vue的详细教程--基础语法【上】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Vue的相关操作吧 一.插值 1.文本 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>插值</title>&l…

python爬虫爬取电影数据并做可视化

思路&#xff1a; 1、发送请求&#xff0c;解析html里面的数据 2、保存到csv文件 3、数据处理 4、数据可视化 需要用到的库&#xff1a; import requests,csv #请求库和保存库 import pandas as pd #读取csv文件以及操作数据 from lxml import etree #解析html库 from …

element 搜索框静态查询

效果图 代码块 <template><div><!-- 1.产品搜索 --><div class"header"><div class"from"><el-form :inline"true" :model"formInline" class"demo-form-inline"><el-form-item l…

Vue复选框批量删除示例

Vue复选框批量删除 通过使用v-model指令绑定单个复选框 例如<input type"checkbox" id"checkbox" v-model"checked"> 而本次我们要做的示例大致是这样的&#xff0c;首先可以增加内容&#xff0c;然后通过勾选来进行单独或者批量删除&…

[计算机入门] 电源选项设置

3.10 电源选项设置 有时候我们的电脑一段时间没有用&#xff0c;会自己关掉屏幕或者直接睡眠&#xff0c;这是电源选项没有设置好导致的。 1、打开控制面板&#xff0c;打开其中的电源选项 2、点击左侧上方的选择关闭显示器的时间 3、进入到编辑计划设置界面&#xff0c;在…

【Vue】MVVM模型还没懂嘛

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue教程持续更新哈&#xff0c;想要学习&巩固&避坑就一起学习叭~ MVVM 模型 Vue虽然没有完全遵循MVVM模型&#xff0c;但Vue的设计也收到了它的启发在文档中也会使用VM&#xff08;ViewModel的缩写&#xff09;这个变…

安防电源芯片有哪些-42v转5v芯片

安防电源芯片有多种种类和型号&#xff0c;以下是一些常见的安防电源芯片&#xff1a; 1. 电源管理芯片&#xff08;Power Management IC&#xff0c;PMIC&#xff09;&#xff1a;这些芯片用于管理和控制安防系统的电源供应&#xff0c;包括电压调整、电流控制、电池管理等功…

全网多种方法解决idea中报出的Cannot find declaration to go to的问题

文章目录 1. 发现错误2. 分析问题3. 解决错误4. 解决该错误的其他方法4.1 其他方法14.2 其他方法24.3 其他方法34.4 其他方法44.5 解决方法54.6 解决方法6 5. 文章总结 1. 发现错误 今早下载一新项目&#xff0c;打开之后&#xff0c;点击对应的代码时&#xff0c;却报出如下错…

thrift的简单使用

写在前面 本文一起看下一种由facebook出品的rpc框架thrift。 源码 。 1&#xff1a;开发步骤 1:编写thrift idl文件 2&#xff1a;根据thrift idl文件生成java模板代码 3&#xff1a;继承模板代码的*.Iface接口给出server的具体服务实现 4&#xff1a;使用模板的HelloWorldSe…

Leetcode: 645.错误的集合 题解【超详细】

题目 集合 s 包含从 1 到 n 的整数。不幸的是&#xff0c;因为数据错误&#xff0c;导致集合里面某一个数字复制了成了集合里面的另外一个数字的值&#xff0c;导致集合 丢失了一个数字 并且 有一个数字重复 。 给定一个数组 nums 代表了集合 S 发生错误后的结果。 请你找出重复…

Redis模块一:缓存简介

目录 缓存的定义 应用 生活案例 程序中的缓存 缓存优点 缓存的定义 缓存是⼀个高速数据交换的存储器&#xff0c;使用它可以快速的访问和操作数据。 应用 1.CPU缓存&#xff1a;CPU缓存是位于CPU和内存之间的临时存储器&#xff0c;它的容量通常远小于内存&#xff0…

微信小程序云开发手搓微标提示,逻辑思路记录及代码实现

目录 写前小叙 功能需求背景 首页js的逻辑思路第一部分 发布公告js逻辑 首页js显示“新”公告思路实现 首页js关闭“新”公告思路实现 管理员“已阅读”js逻辑 首页js显示“新”邮件思路实现 首页js关闭“新”邮件思路实现 写前小叙 今儿凌晨&#xff0c;我又是一个人…

综合管廊安全监测,助力市政管廊智能化管理

综合管廊是一种集管线维护、建设、管理于一体的地下综合通道&#xff0c;可以将电力、通讯、燃气、供热、供水等工程管线集于一体&#xff0c;综合管廊对于城市建设具有重要意义&#xff0c;可以防止管线破裂&#xff0c;杜绝反复开挖路面&#xff0c;有效缓解交通拥堵&#xf…

强化历程7-排序算法(2023.9.12)

此笔记学习图片来自于如下网址 1https://www.west999.com/info/html/chengxusheji/Javajishu/20190217/4612849.html 文章目录 强化历程7-排序算法1 冒泡排序(交换排序)2 选择排序3 直接插入排序4 希尔排序5 归并排序6 快速排序7 堆排序8 计数排序 强化历程7-排序算法 1 冒泡排…

商业综合体AI+视频安防监控与智能监管解决方案

一、方案背景 商业综合体需要具备更好的品质和环境才能吸引更多客流&#xff0c;如何有效地进行内部管理、外部引流&#xff0c;是综合体管理人员思考的重点。 传统的视频监控需要靠人盯牢屏幕或者发生报警后通过查看录像&#xff0c;才能找到意外事件相关人员与起因&#xf…

4G模块驱动移植

一、4G模块概述 1、调试的模块型号是广和通的 NL668-EAU-00-M.2。 2、使用的接口是 M.2 Key-B。实际只用到了M2里的USB接口。 调试过程 以QMI_WWAN号方式进行说明&#xff0c;其他拨号方式也试过。最后以QMI_WWAN方式调通了&#xff0c;拨号成功了。 其他拨号方式因为现有文档…

通过Power Platform自定义D365CE业务需求 - 1. Microsoft Power Apps 简介

Microsoft Power Apps是一个趋势性的、无代码和无代码的商业应用程序开发平台,配有一套应用程序、服务和连接器。其数据平台为构建适合任何业务需求的自定义业务应用程序提供了快速开发环境。随着无代码、少代码应用程序开发的引入,任何人都可以快速构建低代码应用程序,并与…

linux系统报“INFO: task java:xxx blocked for more than 120 seconds.”解决办法

1、问题描述 linux系统&#xff0c;输入dmesg -T&#xff0c;报“INFO: task java:xxx blocked for more than 120 seconds.”&#xff0c;如下 一般情况下&#xff0c;linux会把可用内存的40%的空间作为文件系统的缓存。当缓存快满时&#xff0c;文件系统将缓存中的数据整体同…

安卓系列机型 另类体验第三方系统 DSU操作步骤解析 不影响主系统开启第二系统

dsu loader即 动态系统更新&#xff0c;可以在使用动态分区的安卓设备上&#xff0c;不影响原来系统的同时安装一个副系统&#xff0c;用于体验最新的原生安卓系统。可以不影响主系统的基础上体验其他gsi第三方。DSU 依赖于 Android 动态分区功能&#xff0c;并要求 GSI 作为可…

VRTK4⭐四.和 UI 元素交互

文章目录 &#x1f7e5; 安装Tilia Unity.UI&#x1f7e7; 配置射线与UI交互器1️⃣ 配置直线射线2️⃣ 配置UI交互器 &#x1f7e8; 配置UI1️⃣ 更新EventSystem2️⃣ 进行Canvas设置 我们要实现的功能: 右手触摸到圆盘:显示直线射线 右手圆盘键按下:与选中UI交互 &#x1f7…