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

news2024/11/22 5:43:09

· 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.coloradmin.cn/o/1022300.html

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

相关文章

上市公司的公众环境关注度(2011-2022年)

随着互联网的普及&#xff0c;公众越来越多地通过互联网表达看法。国内外一些学者在研究中采用Google搜索引擎搜索功能构建指标&#xff0c;表达公众需求和关注程度(Kahn & Kotchen, 2011; Choi & Varian, 2012; 郑思齐等&#xff0c; 2013) 百度搜索在中国内地的市场占…

高速、低功耗模拟开关芯片 MS703D

MS703D 是一款高速、低功耗模拟开关芯片&#xff0c;其工作电压范围 是 1.8 至 5.5V 。其具有低码间偏移、高通道噪声隔离度以及大带 宽特性。 主要应用范围包括&#xff1a;手持设备和消费电子&#xff0c;如手机、数码相 机、笔记本电脑等。 主要特点  3V 下导通电…

开源日报 0824 | 构建UI组件和页面的前端工作坊

Storybook 是一个用于构建 UI 组件和页面的前端工作坊&#xff0c;支持多种主流框架&#xff0c;提供丰富的插件&#xff0c;具有可配置性强和扩展性好的特点。 storybookjs/storybook Stars: 79.9k License: MIT Storybook 是一个用于构建 UI 组件和页面的前端工作坊&#x…

px to rem rpx vw中文文档 |px自动转换rem插件

【px to rem & rpx & vw】项目地址&#xff1a; https://github.com/cipchk/vscode-cssrem/blob/HEAD/README.zh-CN.md 作者&#xff1a;卡色-cipchk https://github.com/cipchk cssrem 一个 px 与 rem 单位互转的 VSCode 插件&#xff0c;且支持WXSS微信小程序。 特性…

成集云 | 金蝶EAS与旺店通ERP集成(旺店通主管库存)| 解决方案

源系统成集云目标系统 方案介绍 金蝶EAS是一款全球首款融合TOGAF标准SOA架构的企业管理软件&#xff0c;专门为大中型企业设计&#xff0c;以“创造无边界信息流”为产品设计理念&#xff0c;支持云计算、SOA和动态流程管理的整合技术平台。 旺店通ERP系统是一款专…

API(十)时间相关的SDK

一 时间相关的SDK ① 时间记录的必要性 1、案发现场的时间点2、通过时间判断性能3、时间的不准确性,日志落盘时间 --> 缓冲区导致延迟 ② 使用哪些日期和时间的函数 1、lua 标准时间函数,函数 os.time、os.date 和 os.difftime 提供了所有日期和时间2、在 openresty…

什么是商品价格监控,需要用到API接口嘛

商品价格监控是指通过系统化的方法来追踪、分析和比较商品价格的动态变化&#xff0c;以帮助商家及时获取市场价格信息&#xff0c;做出相应的决策。为了实现这一目标&#xff0c;API接口可以被用来获取商品价格信息。 具体来说&#xff0c;商家可以通过API接口连接到电商平台…

生产制造业厂家固定资产怎么管理

固定资产的管理对于企业的运营效率和盈利能力具有重要影响。然而&#xff0c;传统的固定资产管理方法往往存在许多问题&#xff0c;如资产的低效使用、维护成本高昂以及决策者对资产价值缺乏准确了解等。 因此&#xff0c;我们需要采用一种全新的方式来管理我们的固定资产。本文…

“温莎当下·麦克成风”2023赛季 杭州赛区决赛圆满落幕!

2023年9月16日&#xff0c;“温莎当下麦克成风”2023赛季上海赛区决赛在拱墅区大悦城水秀广场落下帷幕。比赛现场气氛高燃、精彩纷呈&#xff0c;选手们在璀璨的舞台上激情演唱&#xff0c;上演了一场精彩刺激的巅峰争霸赛。经过多轮比拼&#xff0c;最终7号选手方雪莹脱颖而出…

标准防雷接地网和简易地网的制作方法

防雷接地网是整套防雷系统不可缺少的部分&#xff0c;一般是由埋在地下一定深度的多个金属接地极和由导体将这些接地极相互连接组成一网状结构的接地体的总称。它广泛应用在电力、建筑、计算机&#xff0c;工矿企业、通讯等众多行业之中&#xff0c;起着安全防护、屏蔽等作用。…

Spring cloud gateway+apollo=bug?

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 spring cloud 版本 spring cloud: 2021.0.4spring cloud gateway: 3.1.4 背景 最近在配置研究网关的超时时间&#xff0c;有这么一个需求。 服务路由转发接口…

北工大汇编——综合题(2)

题目要求 编写一个比赛得分程序。共有7 个评委&#xff0c;按百分制打分&#xff0c;计分 原则是去掉一个最高分和一个最低分&#xff0c;求平均值。要求&#xff1a; 评委的打分以十进制从键盘输入。成绩以十进制给出&#xff0c;并保留 1位小数。输入输出时屏幕上要有相应提…

口袋参谋:淘宝宝贝秒卡首屏的方法!

​对于新手卖家来说&#xff0c;很多都不太明白&#xff0c;淘宝卡首屏是什么意思&#xff1f;其实卡首屏就是新品前期买家找不到的情况&#xff0c;通过卡首屏让他们快速找到宝贝。 通过卡首屏的方式&#xff0c;给买家账号注入产品标签&#xff0c;让买家进店前就是店铺的精…

「大数据-0.1」虚拟机VMware安装、配置、使用、创建大数据集群教程

目录 一、下载VMware Wworkstation Pro 16 二、安装VMware Wworkstation Pro 16 三、检查与设置VMware的网卡 1. 检查 2. 设置VMware网段 四、在VMware上安装Linux虚拟机 五、对安装好的虚拟机进行设置 1. 打开设置 2. 设置中文 3. 修改字体大小 4. 修改终端字体大小 5. 关闭虚…

【Vue】MVVM模型还没懂嘛

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

终于搞清了:SPI、UART、I2C通信的区别与应用!

电子设备之间的通信就像人类之间的交流&#xff0c;双方都需要说相同的语言。在电子产品中&#xff0c;这些语言称为通信协议。 之前有单独地分享了SPI、UART、I2C通信的文章&#xff0c;这篇对它们做一些对比。 串行 VS 并行 电子设备通过发送数据位从而实现相互交谈。位是…

JVM内存结构解析(图文详解)

JVM内存结构 共享 和 隔离 线程共享区域&#xff1a;方法区、堆、直接内存 线程隔离区域&#xff1a;虚拟机栈、本地方法栈、程序计数器 线程共享&#xff1a;定义一个变量或者一个方法&#xff0c;多线程都可以同时访问、修改这个方法或者变量 线程隔离&#xff1a;就是数…

iPhone恢复出厂设置,掌握2个方法!

当您的手机出现闪退、内存不足、严重卡顿等情况&#xff0c;或者是想将手机进行二手转让时&#xff0c;您可能需要通过将iphone恢复出厂设置来解决问题。但是恢复出厂设置后&#xff0c;手机上的所有数据都会被清除。iPhone怎么恢复出厂设置&#xff1f;本文将为您介绍两种简单…

混淆矩阵和数据不平衡 (2/3)

一、说明 当我们的数据标签具有比另一个类别更多的类别时&#xff0c;我们说我们有数据不平衡。 如果数据集数据不平恒&#xff0c;如何评估分类器的效果&#xff1f;如果分类器不好&#xff0c;如何改进分类器&#xff1f;本篇将讲述不平衡数据下&#xff0c;混淆矩阵的应用。…

许战海战略文库|品类缩量时代:制造型企业如何跨品类打造份额产品?

所有商业战略的本质是围绕着竞争优势与竞争效率展开的。早期&#xff0c;所有品牌立足于从局部竞争优势出发。因此,品牌创建初期大多立足于单个品类。后期增长受限,就要跨品类持续扩大竞争优势&#xff0c;将局部竞争优势转化为长期竞争优势&#xff0c;如果固化不前很难获得增…