C++ 赋值运算符重载

news2025/1/22 12:19:15

赋值运算符重载

 运算符重载:

C++为了增强代码的可读性,可以对 运算符 进行重载,运算符重载  就是具有特殊函数名的函数,这个函数也具有返回值类型,函数名字和参数列表,它的返回值和参数列表的形式和普通函数类似。

比如,我们在比较内置类型大小的时候,可以使用  <   ,   >   ,  ==   等等这些运算符来实现,那么在C++当中也希望,我们的自定义类型也能  和 内置类型一样使用这些运算符,所以他就搞了一个运算符重载, 其实就是实现一个函数来 实现原本 运算符实现的 效果,然后我们在使用 这些运算符的时候,如果是对应的自定义类型,就会调用这个函数,来直接实现结果。

这样的话,假设我们要比较两个自定义类型的大小,就不用写一个函数,然后再主函数中进行调用,这样就算是 写了注释,而且 函数名命名 好 的函数也需要花费时间来阅读,来思考这个函数到底实现了什么功能,如下所示:

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

bool Dateequal(const Date& date1, const Date& date2)
{
	return date1._year == date2._year
		&& date1._month == date2._month
		&& date1._day == date2._day;
}

int main()
{
	Date d1(2020,1,1);
	Date d2(2020,1,1);
	bool Mybool = Dateequal(d1, d2);
	cout << Mybool << endl;  //1

	return 0;
}

我们发现这个代码,就是实现了一个函数,然后调用这个函数,这样子,就算我们命名给了提示,他还是需要看代码和注释来了解这个函数实现了什么,那么如果我们比较内置类型,直接使用 == 这个运算符就可以实现了,他返回的也是 bool  类型的值:

int a = 10;
int b = 10;

cout << a == b << endl;

这样是不是非常的直观,我们一看就知道这个 a == b 是什么意思,那么我们也想 自定义类型也这样用,那么此时就可以使用 运算符 的重载了,运算符的重载使用了 operator 这个关键词。

语法:

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

那么上述例子,我们就可以这样来实现这个 运算符的重载:

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

然后我们在主函数中就可以这样使用:

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

int main()
{	
	Date d1(2020,1,1);
	Date d2(2020,1,1);

	cout << (d1 == d2) << endl;//1
	return 0;
}

如上述,要实现了之前的效果,但是可读性大大提高了。

需要注意的是:因为我们写的是运算符重载,这个运算符也是有优先级的,就像上述, " << " 流运算符的优先级是很高的,如果我们想要  得到  d1 == d2  这样的结果,输出的话,那么我们最好是加上括号。

如上述,如果我们不加括号 就会报错:

 他会先运算  cout << d1 这一个表达式,那么就会出错。

 当然,我们在选择重载运算符的时候,也要看看这个重载运算符实现的结果对这个类是否有意义,比如上述,日期 -  日期    计算天数,这个是有意义;但是 日期 + 日期 这个的结果就没有什么意义。

 现在我们来实现,日期的比较大小  (  < ) :

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;
}

int main()
{	
	Date d1(2020,1,1);
	Date d2(2020,1,1);

	//cout << (d1 == d2) << endl; //1
	cout << (d1 < d2) << endl; //0
	return 0;
}

 我们只能重载 自定义类型的 运算符重载,如果全是是内置类型重载,这样是不行的:

 运算符的重载,参数列表当中必须有一个是 自定义类型:

 如上图,此时编译通过。

而且上述的参数个数也是有限定的,运算符有多少个操作数,我们重载的函数就应该有几个参数

 我们上述都是直接访问Date类当中的 成员,成员的访问权限是 public的,其实成员应该是 protected 的,如果需要访问 protected 的成员,需要其他方法,比如友元解决,但是有元是突破访问限制,这样是不到万不得已不使用的,所以我们就干脆直接使用 类当中的成员函数:

也就是在类当中来定义这个函数:

	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;
	}

 需要注意是:我们上述 函数 只使用了 一个参数,因为在类当中的 成员函数,在调用的时候会给一个this 指针,这个this 指针指向的就是当前对象指针,而我们上述也说过,重载的运算符函数的参数个数,是和对应的运算符的操作数是相等的,所以如果我们 此处像之前一样给了 两个参数,就会报错:

而上述我们在调用这个  重载运算符函数的时候,我们是直接  d1 < d2  ,这样来实现,但是其实,因为是在类当中实现的,那么我们应该像访问类当中的成员函数一样来调用这个函数,但是上述我们并没有报错,其实是 编译器会对这个进行转换,例如这个 d1 < d2 ,就被转换为  d1.operator<(d1,d2);  这样的表达式,这也符合我们对类当中成员函数的访问方式。

当然我们也可以这样来使用这个  重载的运算符函数:

d1.operator<(d2)

 这样看似只传入一个参数,其实是两个参数,因为这个函数还需要传入这个 d1 对象的this 指针。

 我们把上述两种方式都写出来,看看汇编代码是如何写的:

 两个代码反汇编是一样的。

 以下五个操作符是不能进行重载的:

  • .*  
  •  ::        域访问操作符
  •  sizeof      计算大小
  •  ?:      相当于 if
  • .      成员访

总结  :

在 运算符重载当中,我们需要注意的是:
注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .  *   ::   sizeof   ?:    . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
  • 是否要重载运算符,要看这个运算符实现效果对这个类是否有意义

 赋值运算符重载

 赋值运算符就是  " = " 这个运算符,他的定义方式和其他函数的定义方式是一样的,都是使用 operator  这个关键词来定义,如下例子:

void operator=(const Date& d1, const Date& d2)
{

}

需要注意的是我们需要把 赋值运算符 和 拷贝构造函数 这两个 区别开来;拷贝构造函数的本质是构造函数,他是在主函数中有一个 对象的时候,调用拷贝构造函数,来创建一个新的对象,把其中的值拷贝过去;而赋值运算符 重载函数本质是 运算符重载,他是在主函数中本来就有两个对象,然后把 =  前面对象当中的值,赋值给第二个对象当中。

 现在我们在类当中定义这个 赋值运算符重载函数:

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

Date类当中只需要 浅拷贝就能实现 赋值,向上述一样,就是最简单的赋值操作符的重载。

d1 = d2  执行之前:

 d1  =  d2 执行之后:

 我们发现,成功赋值了。

但是这样写有一些问题:

我们在内置类型当中使用赋值运算符的时候,可以一次多次赋值:

int a = 10;
int b = 10;
int c = 10;

a = b = c = 0;

 他是从右到左进行一次赋值的:

 如果我们把 上述实现的 重载赋值运算符 运用在 我们实现的自定义类型当中,进行如上述的多元赋值,就会有些问题:

	d4 = d3 = d1;

像这个二元赋值的表达式,在运算的时候就报错了:

二元  " = " 这个指的就是 赋值给 d4 的这个 " = " ,意思就是这个 " = "  的右值是 void 类型的,那就不能再进行赋值了,如下图:

 

 这时因为:我们在写 赋值运算符函数的时候,给的返回值就是 void 的,那么这样就不太好,向上述的多元赋值就不行。

 我们之前说过,像 i = j = 0; 对于 i 来说,他的接收的值是 j ,也就是说 j = 0 这个表达式返回值是 j,所以我们在 函数中的返回值也应该是对应的对象,我们可以使用this 指针来返回 当前对象:

	Date operator=(const Date& d2)
	{
		_year = d2._year;
		_month = d2._month;
		_day = d2._day;

        return *this;
	}

上述代码能实现,但是其实上述代码还是有些问题,因为上述代码是传值返回,是解引用this 指针,来返回这个 对象,我们知道,函数返回是需要创建一个临时变量来拷贝到主函数中接收的变量的,如果这个对象很大,那么对内存和效率的消耗也很大。

所以,因为上述中的对象是在主函数中的,他的生命周期是在主函数中的,这个函数销毁之后,对象不会被销毁,那么我们就用使用 引用返回,这样就不用 在创建临时变量了。

	Date& operator=(const Date& d2)
	{
		_year = d2._year;
		_month = d2._month;
		_day = d2._day;

        return *this;
	}

还有一种情况: d1 = d1 ,这种情况,在一般情况下是不会报错的,但是在一些极特殊的情况下就会报错,所以我们可以在函数中加一个断言,不给这样使用。

 比如这样判断:

if( this != &d)
{
     //执行代码
}

这样写也行:

if(*this != d)

但是上述是要写了 != 重载运算符函数才行,而且这样写代价有点大,每一次都需要调用函数去判断。

 对于赋值 运算符的重载函数,如果用户没有显示定义,那么编译器会自动创建 赋值运算符重载。

这个默认的重载和 拷贝构造函数 的行为是一样的:内置类型/成员---值拷贝/浅拷贝;自定义类型拷贝----会去调用它的赋值重载函数。

像这里的Date 类是不用我们去写 赋值运算符重载函数的,因为这里只是浅拷贝,浅拷贝默认的赋值运算符重载就能帮我们实现。(浅拷贝就是值的方式逐字节拷贝)

像类似Stack 这样的,对应类的对象当中里面有 开辟空间的 ,这时候需要深拷贝,这时候就需要我们来 写了。

 注意:我们之前实现的 普通运算符重载函数,既可以写在 全局中,又可以写在类当中,构成成员函数。但是这里的 赋值 运算符重载函数,就不能写成 全局的。

 因为这里的 赋值运算符重载函数是一种特殊的 运算符重载函数,他是默认成员函数,而普通的运算符重载函数不是默认成员函数。赋值运算符重载函数,只能再类当中进行定义和声明,当然,如果定义和声明都是在类当中的,声明和定义是可以分离的。

如这个例子:

我们在全局定义了这个 赋值运算符重载函数,发现直接报错了。 

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

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

相关文章

基于SAM的二次开发案例收集分享

一、AnyLabeling[1]——制作人&#xff1a;vietanhdev AnyLabeling LabelImg Labelme Improved UI Autolabeling AnyLabeling软件是一个集成了YOLO、Segment Anything模型&#xff08;AI支持&#xff09;的高效数据标注工具&#xff0c;它可以通过点击目标的方式完成目标检…

商业银行财富管理“智能原生”能力呈阶梯化,AI助力商业模式趋向多元化发展

易观&#xff1a;金融业的财富管理从经营角度来看&#xff0c;是“客户与渠道管理场景运营产品研发”三位一体共同构建以客户为中心&#xff0c;数据驱动的业务经营体系。其中&#xff0c;“客户与渠道管理”是将客户利益作为核心目标&#xff0c;通过升级用户体验、客户全生命…

获奖名单公布|香港BlockBooster x Moonbeam黑客松圆满收官

Moonbeam基金会赞助的”Into the Socialverse”主题的BlockBooster黑客松于近日落幕。该活动由BlockBooster、OKX、Gitcoin和OxU香港区块链俱乐部联合主办&#xff0c;共有22个开发团队参赛。经过多位评委的严格筛选&#xff0c;3支优秀团队脱颖而出&#xff0c;获得Moonbeam基…

zookeeper集群命令使用

1.zookeeper脚本使用(地址填写集群中任意一个主机地址) 连接客户端命令行 /etc/zookeeper/zookeeper/bin/zkCli.sh -server 10.1.60.112:2181 启动zookeeper服务 /etc/zookeeper/zookeeper/bin/zkServer.sh start 停止zookeeper服务 /etc/zookeeper/zookeeper/bin/zkServer…

春风吹,战鼓擂,忆享科技-云服务事业部春季员工关怀活动集锦,温情相伴

前言 时序更替&#xff0c;忆享科技又迎来新的一年。回顾2022&#xff0c;忆享科技在风雨中前行&#xff0c;实现了一次又一次的突破。在这2023年春暖花开&#xff0c;万物复苏的美好季节&#xff0c;忆享科技怀抱着它满满的关怀向大家走来&#xff01;春季云服务事业部开展了五…

推动科技企业成长,开源网安受邀参加数字经济企业孵化器建设座谈会

近日&#xff0c;为更好地做好数字经济孵化器的孵化培育工作&#xff0c;推动数字经济孵化器和入驻企业高质量发展&#xff0c;高创公司召开数字经济企业孵化器建设座谈会。高新区工委委员、管委会副主任贺菲出席会议&#xff0c;开源网安合肥公司总经理菅志刚受邀参加本次座谈…

vue生命周期代码示范--Vue基本介绍--MVVM-示意图--数据渲染--事件绑定--修饰符--组件化--和全部代码示范

目录 Vue 基本介绍 官网 git 地址: MVVM-示意图 解读 MVVM 思想(上图) 下载官网 简单的代码示例方便理解 Vue 数据绑定机制分析! 注意事项和使用细节 数据单向渲染 基本说明 应用实例 注意事项和使用细节 数据双向绑定 应用实例 ​编辑代码实现 代码综合-单…

带头双向循环链表--数据结构

魔王的介绍&#xff1a;&#x1f636;‍&#x1f32b;️一名双非本科大一小白。魔王的目标&#xff1a;&#x1f92f;努力赶上周围卷王的脚步。魔王的主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王.&#x1f525;&#x1f525;&#x1f525; ❤️‍&#x1f…

Rocket 框架基础

Rocket v0.5 DOC Rocket是Rust的一个web框架&#xff0c;它使编写快速、安全的web应用程序变得简单&#xff0c;而不会牺牲灵活性、可用性或类型安全性。 类型安全 从请求到响应&#xff0c;Rocket确保您的类型有意义。样板免费 把时间花在编写真正重要的代码上&#xff0c;让…

Amazon S3 对象存储Java API操作记录(Minio与S3 SDK两种实现)

缘起 今年(2023年) 2月的时候做了个适配Amazon S3对象存储接口的需求&#xff0c;由于4月份自学考试临近&#xff0c;一直在备考就拖着没总结记录下&#xff0c;开发联调过程中也出现过一些奇葩的问题&#xff0c;最近人刚从考试缓过来顺手记录一下。 S3对象存储的基本概念 …

Unity Camera -- (4)探索不同类型的镜头

不同类型的镜头会呈现出不同的氛围和感觉&#xff0c;通常镜头的类型和相机聚焦方式和位置相关。本节我们来看看一些常见的不同类型的镜头。 广角 广角镜头通常在画面中包含更多的环境。观众接受到的是通常从远处拍摄的范围更广的视觉信息。 1. 工程窗口中&#xff0c;在Scene…

04/27课后作业(Qt)

widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("文件管理"); }Widget::~Widget() {delete ui; }//字体按钮对…

Spring Boot——@Autowired属性注入问题

&#x1f388; Autowired问题 当我们在使用Autowired属性注入时,会发现idea提示Field injection is not recommended ,译为:不推荐使用属性注入   要想了解Spring和idea之所以不推荐使用Autowired属性注入,首先就要先了解Spring常用的注入方式&#xff1a;简单类型注入、集…

基于Redis的分布式限流详解

前言 Redis除了能用作缓存外&#xff0c;还有很多其他用途&#xff0c;比如分布式锁&#xff0c;分布式限流&#xff0c;分布式唯一主键等&#xff0c;本文将和大家分享下基于Redis分布式限流的各种实现方案。 一、为什么需要限流 用最简单的话来说&#xff1a;外部请求是不可…

ArcGIS Pro拓扑

地理数据库拓扑帮助确保数据完整性。拓扑的使用提供了一种对数据执行完整性检查的机制&#xff0c;帮助地理数据库中验证和保持更好的要素表示。 拓扑是点、线和多边形要素共享几何的方式的排列布置。拓扑的用途包括以下几个方面&#xff1a; &#xff08;1&#xff09;限制要…

模型服务,支持渲染多张输出图片|ModelWhale 版本更新

清明时节雨纷纷。晚春的雨季中&#xff0c;ModelWhale 迎来了新一轮的版本更新。 本次更新中&#xff0c;ModelWhale 主要进行了以下功能迭代&#xff1a; • 新增 模型服务多图输出渲染&#xff08;专业版✓ 团队版✓ &#xff09; • 优化 门户成果交流展示&#xff08;团队…

Java异常机制

异常概念 异常是程序在运行期发生的不正常的事件&#xff0c;它会打断指令的正常执行流程。 设计良好的程序应该在异常发生时提供处理这些不正常事件的方法&#xff0c;使程序不会因为异常的发生而阻断或产生不可预见的结果。 Java语言使用异常处理机制为程序提供了异常处理的能…

卡尔曼滤波简介 —— 一维卡尔曼滤波

原文&#xff1a;The alpha - beta - gamma filter (kalmanfilter.net) 一维卡尔曼滤波 在本章中&#xff0c;我们将在一个维度上推导出卡尔曼滤波。本章的主要目标是简单直观地解释卡尔曼滤波的概念&#xff0c;而不使用可能看起来复杂和令人困惑的数学工具。 我们将逐步推进…

oracle connect by 学习

【Connect by 层次查询】 https://www.bilibili.com/video/BV1jV411t7CB/?share_sourcecopy_web&vd_sourced88a617727cccf1c106d623afec0c6b6 简单来说这个connect by 就是为了查父子节点的。 CREATE TABLE test.emp (id varchar(10),name varchar(10),manager_id varch…

Java的位运算

目录 1 Java中支持的位运算 2 位运算规则 3 逻辑运算 3.1 与运算&#xff08;&&#xff09; 3.2 或运算&#xff08;|&#xff09; 3.3 异或运算&#xff08;^&#xff09; 3.3 取反运算&#xff08;~&#xff09; 4 位移操作 4.1 左移&#xff08;<<&#…