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.mzph.cn/news/82412.shtml

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

相关文章

jvm中对象创建、内存布局以及访问定位

对象创建 Java语言层面&#xff0c;创建对象通常&#xff08;例外&#xff1a;复制、反序列化&#xff09;仅仅是一个new关键字即可&#xff0c;而在虚拟机中&#xff0c;对象&#xff08;限于普通Java对象&#xff0c;不包括数组和Class对象等&#xff09;的创建又是怎样一个过…

小米华为,化干戈为玉帛!

近日来&#xff0c;手机圈又掀起了各大厂家推出新品的高潮。首先是华为Mate60的推出&#xff0c;其自研的麒麟9000S芯片瞬间点燃了国内手机市场&#xff0c;得到了国内甚至国外业界人士的认可和好评。 而近日网上盛传的小米创始人雷军的“愿意加入华为技术生态圈”的邀请&…

Redis缓存实现及其常见问题解决方案

随着互联网技术的发展&#xff0c;数据处理的速度和效率成为了衡量一个系统性能的重要指标。在众多的数据处理技术中&#xff0c;缓存技术以其出色的性能优化效果&#xff0c;成为了不可或缺的一环。而在众多的缓存技术中&#xff0c;Redis 以其出色的性能和丰富的功能&#xf…

JDK jps命令复习

之前写过jdk命令工具的博文&#xff0c;下面复习jps命令&#xff1b; jps 是 Java Process Status Tool 的简称,它的作用是为了列出所有正在运行中的 Java 虚拟机进程和相关信息&#xff1b; jps 命令参数 -q 只输出进程 ID,省略主类的名称 -m 输出虚拟机进程启动时传递…

AG35学习笔记(一):debug串口抓取模组log、debug串口测试AT指令、echo命令通过串口发送16进制数据

目录 一、概述二、抓取模组log2.1 硬件接口2.2 用户登录2.3 相关指令 三、测试AT指令3.1 查看端口3.2 进入模式 四、串口发16进制echo使用 一、概述 二、抓取模组log 在之前记录了通过USB&#xff0c;使用移远工具Qwinlog来抓取log&#xff08;3.3 抓取模组log&#xff09;。…

【Java】第一个Servlet程序

第一个Servlet程序 创建项目引入依赖手动创建必要的目录/文件编写代码打包程序部署验证程序是否正常工作 创建项目 选中maven 创建好项目后,观察左侧项目结构 引入依赖 当权代码需要使用servlet开发,而Java标准库中并没有servlet,此时就需要让maven能够把servlet的依赖获取…

子网的划分

强化计算机网络发现王道没有这一块的内容&#xff0c;导致做题稀里糊涂。于是个人调研补充。 子网划分是将一个大型IP网络划分成更小的子网&#xff0c;以实现更有效的网络管理和资源分配。 原因&#xff1a; 提高网络性能&#xff1a;子网划分可以减少广播域的大小&#xff…

成集云 | 用友NC集成旺店通ERP(旺店通主管库存)| 解决方案

源系统成集云目标系统 方案介绍 用友NC是用友NC产品的全新系列&#xff0c;是面向集团企业的世界级高端管理软件。它以“全球化集团管控、行业化解决方案、全程化电子商务、平台化应用集成”的管理业务理念而设计&#xff0c;采用J2EE架构和先进开放的集团级开发平台…

bootstrap柵格

.col-xs- 超小屏幕 手机 (<768px) .col-sm- 小屏幕 平板 (≥768px) .col-md- 中等屏幕 桌面显示器 (≥992px) .col-lg- 大屏幕 大桌面显示器 (≥1200px) 分为12个格子 -后面的1代表占12分子1也就是一份 1.中等屏幕 <div class"container-fluid a">&l…

Autojs 小游戏实践-潮玩宇宙开扭蛋

概述 最近在玩潮流宇宙&#xff0c;里面有扭蛋兔的一个玩法&#xff0c;开始有很多蛋&#xff0c;需要我们一个个点开&#xff0c;然后根据装备品质替换分解&#xff0c;潮流提供了自动开扭蛋功能&#xff0c;但是开到品质比自己装备好的时候回暂停&#xff0c;由于个人懒得看…

在Kubernetes上安装和配置Istio:逐步指南,展示如何在Kubernetes集群中安装和配置Istio服务网格

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

掌握这三大要素,轻松写出爆款软文

随着网络的快速发展&#xff0c;软文营销的趋势也在不断变化&#xff0c;做软文看似简单&#xff0c;但是想要做出成绩&#xff0c;真正吸引用户其实是有一定难度的&#xff0c;也有不少企业向媒介盒子咨询软文写作的相关话题&#xff0c;今天就让媒介盒子告诉大家&#xff0c;…

Linux查找文件内容的命令

在Linux中&#xff0c;您可以使用以下命令来查找文件内容&#xff1a; grep命令&#xff1a; grep命令用于在文件中搜索指定的文本模式&#xff0c;并将包含匹配的行打印出来。语法如下&#xff1a; grep "要查找的文本" 文件名例如&#xff0c;要在名为example.txt的…

【深度学习 | LSTM】解开LSTM的秘密:门控机制如何控制信息流

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

(超详解)堆排序+(图解)

目录&#xff1a; 1:如何建堆(两种方法) 2:两种方法建堆的时间复杂度分析与计算 3:不同类型的排序方式我们应该如何建堆 文章正式开始&#xff1a; 1&#xff1a;如何建堆 在实现堆排序之前我们必须得建堆&#xff0c;才能够实现堆排序 首先在讲解如何建堆之前让我们先来回顾一…

JDK8新特性

Lembda表达式 lembda表达式是一个简洁、可传递的匿名函数,实现了把代码块赋值给一个变量的功能 是我认为jdk1.8中最让人眼前一亮的特性&#xff08;我没用过其他函数式的语言&#xff09; 在了解表达式之前&#xff0c;我们先看两个概念 函数式接口 含有且仅含有一个抽象方法&…

CSS核心使用

CSS核心使用 box-sizingbox-shdowtext-shadowpositionwriting-mode box-sizing 定义计算一个元素的总高度和总宽度. 属性值 content-box 默认值,width 内容宽度,height内容的高度border-box 宽度和高度包含内容,内边距和边框 widthborderpadding内容宽度, heightborderpaddi…

测试进阶知识之零日攻击的发现和防御

零日攻击是指针对软件或系统中未公开&#xff08;或未被开发者知晓&#xff09;的漏洞进行的攻击。这些漏洞被称为零日漏洞&#xff0c;因为在被公开之前&#xff0c;它们对开发者或安全研究人员来说是未知的&#xff0c;所以没有足够的时间进行防御或修复。 发现零日漏洞 发…

启动YOLO进行图片物体识别

查看官方文档YOLO: Real-Time Object Detection 这些是一些模型的对比&#xff0c;显示了YOLO的优势&#xff0c;继续往下面看 CoCoData set 是一个数据库&#xff0c;用来训练模型&#xff0c;这里面有丰富的物体检测&#xff0c;分割数据集&#xff0c;图像经过了精确的segm…

Pikachu Burte Force(暴力破解)

一、Burte Force&#xff08;暴力破解&#xff09;概述 ​ “暴力破解”是一攻击具手段&#xff0c;在web攻击中&#xff0c;一般会使用这种手段对应用系统的认证信息进行获取。 其过程就是使用大量的认证信息在认证接口进行尝试登录&#xff0c;直到得到正确的结果。 为了提高…