【C++初阶】类与对象(中)之运算符重载 + 赋值运算符重载

news2024/11/27 0:15:39

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


前景回顾

  • 构造函数 +析构函数 + 拷贝构造函数

目录

  • 前景回顾
  • 一、运算符重载
    • 1.1 问题引入
    • 1.2 运算符重载的概念
    • 1.3 运算符重载的注意事项
  • 二、赋值运算符重载
    • 2.1 赋值运算符重载格式
    • 2.2 例子引入
    • 2.3 赋值运算符重载的注意事项
  • 三、总结

一、运算符重载

1.1 问题引入

当我们实现一个日期类的时候,现在我要比较日期的大小,正常情况下,我们会封装一个比较函数来实现它:

class Date
{
public:

	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		this->Year = year;
		this->Month = month;
		this->Day = day;
	}

//private:
	int Year; // 年
	int Month; // 月
	int Day; // 日
};

bool cmp(const Date& x1, const Date& x2)
{
	// 假设默认x2的日最大
	
	// 1. 年份大的则大
	if (x1.Year < x2.Year)
		return true;
	// 2. 月份大的则大(前提是年份相等)
	else if (x1.Year == x2.Year && x1.Month < x2.Month)
		return true;
	// 3. 日大则大(前提是年和月份都相等)
	else if (x1.Year == x2.Year && x1.Month == x2.Month && x1.Day < x2.Day)
		return true;
	// 4. 以上要求都没满足返回false
	return false;
}

int main()
{
	Date d1(2023, 5, 16);
	Date d2(2022, 5, 16);

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

	return 0;
}

【程序结果】

在这里插入图片描述

以上代码虽然实现了比较两个日期的大小,但有没有发现代码好像有点缺陷 — 可读性差。原因是:cmp(d1, d2)这个代码,最后一定会输出10,问题来了,单单通过10如何区分d1大还是d2大?难道调用者还要到函数定义里查看代码逻辑?因此,为了解决此问题,C++引入了运算符重载

1.2 运算符重载的概念

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

  • 函数名字为:关键字operator后面接需要重载的运算符符号
  • 函数原型:返回值类型 operator操作符(参数列表)

所以,在【问题引入】的代码可以运用运算符重载:

#include <iostream>

using namespace std;

class Date
{
public:

	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		this->Year = year;
		this->Month = month;
		this->Day = day;
	}

//private:
	int Year; // 年
	int Month; // 月
	int Day; // 日
};

// 运算符重载
bool operator<(const Date& x1, const Date& x2)
{
	// 1. 年份大的则大
	if (x1.Year < x2.Year)
		return true;
	// 2. 月份大的则大(前提是年份相等)
	else if (x1.Year == x2.Year && x1.Month < x2.Month)
		return true;
	// 3. 日大则大(前提是年和月份都相等)
	else if (x1.Year == x2.Year && x1.Month == x2.Month && x1.Day < x2.Day)
		return true;
	// 4. 以上要求都没满足返回false
	return false;
}

int main()
{
	Date d1(2023, 5, 16);
	Date d2(2022, 5, 16);

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

	return 0;
}

【程序结果】

在这里插入图片描述

几个问题:

  1. 为什么在65行为什么要对d1 < d2加括号?
    原因是:流插入操作符(<<·)的优先级高于<
  2. 为什么可以直接写d1 < d2
    原因是:d1 < d2本质上就是在调用operator<,编译器在编译代码的时候会把d1 < d2 转化为operator<(d1, d2),可以通过反汇编来查看代码底层:
    在这里插入图片描述

1.3 运算符重载的注意事项

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

大家有没有发现,在例子1.2中,类中我把成员变量变成公有了,原因是在类外是无法访问私有的成员变量的,那么问题来了,封装性该如何保证?因此以上的代码还得改。既然类外没有访问私有的成员变量的权限,但由于类中的成员函数可以访问私有的成员变量,所以可以将operator<函数放入类中

在这里插入图片描述

虽然把函数放在类中,但是代码还是编译不过。原因是:当运算符重载作为类成员函数重载时,操作数的个数要等于形参的个数,在注意事项4提到了:作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。因此,完整代码如下:

#include <iostream>
using namespace std;

class Date
{
public:
	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		this->Year = year;
		this->Month = month;
		this->Day = day;
	}
	// 运算符重载
	// di < d2
	// 左操作数是this,指向调用函数的对象
	bool operator<(const Date& x)
	{	
		// 1. 年份大的则大
		if (this->Year < x.Year)
			return true;
		// 2. 月份大的则大(前提是年份相等)
		else if (this->Year == x.Year && this->Month < x.Month)
			return true;
		// 3. 日大则大(前提是年和月份都相等)
		else if (this->Year == x.Year && this->Month == x.Month && this->Day < x.Day)
			return true;
		// 4. 以上要求都没满足返回false
		return false;
	}

private:
	int Year; // 年
	int Month; // 月
	int Day; // 日
};

int main()
{
	Date d1(2023, 5, 16);
	Date d2(2022, 5, 16);

	cout << d1.operator<(d2) << endl;
	// 上下两行代码本质一样
	cout << (d1 < d2) << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

  • 点击查看this指针知识点

二、赋值运算符重载

2.1 赋值运算符重载格式

  1. 参数类型:const 类型&传递引用可以提高传参效率
  2. 函数名:operator=
  3. 返回值类型:类型&返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  4. 检测是否自己给自己赋值返回*this :要复合连续赋值的含义

2.2 例子引入

#include <iostream>
using namespace std;

class Date
{
public:

	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		// 以下的this->均可省略
		this->Year = year;
		this->Month = month;
		Day = day;
	}

	// 打印函数
	void Print()
	{
		cout << Year << '-' << Month << '-' << Day << endl;
	}

	// 赋值运算符重载
	// d1 = d2; 
	void operator=(const Date& x)
	{
		// this是d1的地址
		// x是d2的别名
		this->Year = x.Year;
		this->Month = x.Month;
		this->Day = x.Day;
	}

private:
	int Year;
	int Month;
	int Day;
};

int  main()
{
	Date d1(2023, 5, 16);
	Date d2(2023, 5, 20);

	// 现在要将d2赋值给d1
	d1 = d2;// 本质上是d1.operator(d2)
	
	d1.Print();
	d2.Print();
	
	return 0;
}

【程序结果】

在这里插入图片描述

温馨提示:要注意区分拷贝构造函数和复制拷贝(赋值运算符重载)

  • 赋值拷贝(赋值运算符重载):已经存在的两个对象之间的赋值拷贝
  • 拷贝构造:是用一个已经存在的对象初始化另一个对象。例如:Date d3 = d1
    在这里插入图片描述

但以上的赋值运算符重载还存在一个问题:在C语言中,我们可以进行连续赋值,例如:

在这里插入图片描述

周所周知,以上的代码赋值顺序是从右到左的,0先赋值给k,然后k = 0作为返回值,其类型是int,再赋值给j``,依次类推,但赋值重载是否也能支持连续赋值呢?

#include <iostream>
using namespace std;

class Date
{
public:

	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		// 以下的this->均可省略
		this->Year = year;
		this->Month = month;
		Day = day;
	}

	// 打印函数
	void Print()
	{
		cout << Year << '-' << Month << '-' << Day << endl;
	}

	// 赋值运算符重载
	// d1 = d2; 
	void operator=(const Date& x)
	{
		// this是d1的地址
		// x是d2的别名
		this->Year = x.Year;
		this->Month = x.Month;
		this->Day = x.Day;
	}

private:
	int Year;
	int Month;
	int Day;
};

int  main()
{
	Date d1(2023, 5, 17);
	Date d2, d3;

	d3 = d2 = d1;
	return 0;
}

【程序报错】

在这里插入图片描述

报错原因:注意类中operator=的返回值类型是void,因此d1赋值给d2后,返回的类型是void,然后再赋值给d3,类型不匹配导致报错。

所以,正确的做法应该是返回d1或者是d2的类型,也就是返回Date类型

#include <iostream>
using namespace std;

class Date
{
public:

	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		// 以下的this->均可省略
		this->Year = year;
		this->Month = month;
		Day = day;
	}

	// 打印函数
	void Print()
	{
		cout << Year << '-' << Month << '-' << Day << endl;
	}

	// 赋值运算符重载
	// d1 = d2; 
	Date& operator=(const Date& x)
	{
		// this是d1的地址
		// x是d2的别名
		this->Year = x.Year;
		this->Month = x.Month;
		this->Day = x.Day;

		// 这里返回的是d1的类型
		return *this;
	}

private:
	int Year;
	int Month;
	int Day;
};

int  main()
{
	Date d1(2023, 5, 17);
	Date d2, d3;

	d3 = d2 = d1;

	d1.Print();
	d2.Print();
	d3.Print();
	return 0;
}

【程序结果】

在这里插入图片描述

  • 为什么要用引用做返回值,而不用传值?

在引用章节中提过(点击跳转),传值返回的时候,会生成一个临时变量,而返回值会通过拷贝给临时变量,因此这里会调用一次拷贝构造函数,导致效率低,而用引用做返回值是因为效率高。并且能用引用做返回值的原因是:*this就是d1,在赋值拷贝函数栈帧销毁时,*this不会跟着销毁,而是在main函数结束后销毁。

2.3 赋值运算符重载的注意事项

  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数

因为赋值运算符重载是默认成员函数,如果不在类中自己定义,而在类外定义,编译器会自己生成,就和编译器在类中生成的默认赋值运算符重载冲突。注意:运算符重载不是默认成员函数

  1. 如果没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

【内置类型成员】

#include <iostream>
using namespace std;

class Date
{
public:

	// 构造函数(负责初始化)
	Date(int year = 1, int month = 1, int day = 1)
	{
		// 以下的this->均可省略
		this->Year = year;
		this->Month = month;
		Day = day;
	}

	// 打印函数
	void Print()
	{
		cout << Year << '-' << Month << '-' << Day << endl;
	}
	// 如果没有显式实现时,编译器会生成一个默认赋值运算符重载

private:
	int Year;
	int Month;
	int Day;
};

int  main()
{
	Date d1(2023, 5, 17);
	Date d2, d3;

	d3 = d2 = d1;

	d1.Print();
	d2.Print();
	d3.Print();
	return 0;
}

【程序结果】

在这里插入图片描述

【自定义类型成员】

栈是一个经典的要调用对应类的赋值运算符重载完成赋值。理由是:栈的赋值操作只需要将一个栈复制到另一个栈中,不需要进行对象的成员变量的赋值和内存管理的操作,因此使用编译器提供的赋值运算符重载可能会造成不必要的开销和错误。因此,在实现栈的赋值操作时,应该根据栈的特点自行实现赋值运算符重载。

在这里插入图片描述

三、总结

  • 运算符重载是指在类中定义某些运算符的行为,使得这些运算符可以作用于类的对象。运算符重载可以使得程序更加简洁易读,同时也可以提高代码的可复用性。

  • 赋值运算符重载一种特殊的成员函数,也是一种特殊的运算符重载,它用于定义对象之间的赋值操作。默认情况下,C++编译器会自动生成一个默认的赋值运算符,但这个默认的赋值运算符只是进行浅拷贝(内置类型),可能会导致指针成员变量指向同一块内存地址,从而出现意想不到的后果。因此,我们需要手动定义赋值运算符重载,以实现深拷贝(自定义类型),从而保证程序的正确性。

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

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

相关文章

CPU性能优化:Cache

CPU性能提升&#xff1a;Cache机制 随着半导体工艺和芯片设计技术的发展&#xff0c;CPU的工作频率也越来越高&#xff0c;和CPU进行频繁的数据交换的内存的运行速度却没有相应的提升&#xff0c;于是两者之间产生了带宽问题。进而影响计算机系统的整体性能。CPU执行一条指令需…

C++/PTA 至多删三个字符

至多删三个字符 题目要求解题思路代码总结 题目要求 给定一个全部由小写英文字母组成的字符串&#xff0c;允许你至多删掉其中 3 个字符&#xff0c;结果可能有多少种不同的字符串&#xff1f; 输入格式&#xff1a; 输入在一行中给出全部由小写英文字母组成的、长度在区间 […

关于摆摊气球的调研-网红气球

本章主要介绍一下最近网红气球&#xff1a; 最近看到很多摆摊的抖音视频&#xff0c;都在说卖气球很好&#xff0c;成本低&#xff0c;收益高&#xff0c;所以调研了一下&#xff0c;网红气球分好几种&#xff1a; a,飘空气球&#xff1b; b.手持网红气球 c.青蛙 首先介绍飘空…

文件上传,内容逻辑数组绕过(22)

uploadd 第十三关 这一关告诉我们的&#xff0c;有一些上传漏洞需要配合这个文件包含和加解密。 这个先在一个图片源码里面写入php后门的脚本代码 这里也可以手工注入到图片的源码里面来&#xff0c;手工注入&#xff0c;如果采用16进制打开这个图片&#xff0c;这个图片在…

okhttp篇4:RetryAndFollowUpInterceptor

在上一篇 okhttp篇3&#xff1a;RealCall_yolan6824的博客-CSDN博客 中讲到RealCall无论是在execute还是enqueue方法中&#xff0c;都是通过getResponseWithInterceptorChain方法获取Request对应的Response的。而getResponseWithInterceptorChain这个方法&#xff0c;又是通过…

基于PyQt5的图形化界面开发——Windows内存资源监视助手[附带编译exe教程]

基于PyQt5的图形化界面开发——Windows内存资源监视助手[附带编译exe教程] 0. 前言1. 资源信息获取函数——monitor.py2. UI界面——listen.py3. main.py4. 运行效果5. 编译 exe 程序6. 其他PyQt文章 0. 前言 利用 PyQt5 开发一个 windows 的资源监视助手&#xff0c;在使用虚…

【vimsolo】让vim看起来像VSCode:颜色主题和状态栏的配置

文章目录 1. 目的2. 理念&#xff1a; vimsolo3. vimrc: 配置颜色4. vimrc: 配置状态栏5. 拷贝颜色主题和.vimrc: python安装脚本 1. 目的 习惯了 VSCode 默认的配色&#xff1a;黑色主题&#xff0c;蓝色状态栏。偶尔使用 Vim 时想让 vim 伪装的像 VSCode&#xff0c;不考虑花…

Web 测试和 App 测试重点总结

单纯从功能测试的层面上来讲的话&#xff0c;App 测试、Web 测试在流程和功能测试上是没有区别的&#xff0c;但由于系统结构方面存在差异&#xff08;web 项目&#xff0c;b/s 架构&#xff1b;app 项目&#xff0c;c/s 结构&#xff09;在测试中还是有不同的侧重点内容&#…

ZED使用指南(八)Depth Sensing

ZED立体相机再现了人类双目视觉的工作方式。通过比较左眼和右眼看到的两种视图&#xff0c;不仅可以推断深度&#xff0c;还可以推断空间中的3D运动。 ZED立体相机可以捕捉到场景的高分辨率3D视频&#xff0c;通过比较左右图像之间的像素位移可以估计深度和运动。 深度感知 …

CTFHub-ctfhub-Git泄露-Log

CTFHub-ctfhub-Git泄露-Log 当前大量开发人员使用git进行版本控制&#xff0c;对站点自动部署。如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。请尝试使用BugScanTeam的GitHack完成本题 1、dirsearch扫描 github上下载dirsearch-master 命令F…

SpringMVC第二阶段:@RequestMapping注解详解

RequestMapping注解详解 RequestMapping是给个方法配置一个访问地址。就比如web学习的Servlet程序&#xff0c;在web.xml中配置了访问地址之后&#xff0c;它们之间就有一个访问映射关系。 1、value属性 value 属性用于配置方法对应的访问地址. /*** RequestMapping 可以配…

JavaScript实现背景图像切换3D动画效果

&#x1f431; 个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &am…

Flask全套知识点从入门到精通,学完可直接做项目

目录 Flask入门 运行方式 URL与函数的映射(动态路由) PostMan的使用 查询参数的获取 上传文件 其它参数 url_for 函数 响应-重定向 响应-响应内容 响应-自定义响应 Flask模板 模板介绍 模板的使用 模板-传参 模板使用url_for函数 过滤器介绍 Jinja模板自带过滤器 流程…

DTFT和DFT有何区别?一文为你讲解清楚

很多人在开始学习数字信号处理的时候&#xff0c;对于各种傅里叶变换特别是离散傅里叶变化的概念及作用完全不清楚&#xff0c;IC修真院在网上整理了关于DTFT、DFT的各知识点。下面就来了解一下关于DTFT和DFT的区别吧。 DTFT&#xff0c; DFT 的区别是含义不同、性质不同、用途…

Elasticsearch集群搭建与相关知识点整理

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章参考网上的课程&#xff0c;介绍Elasticsearch集群的搭建&#xff0c;以及Elasticsearch集群相关知识点整理。 如果文章有什么需要改进的地方还请大佬不吝赐教&am…

C++刷题--选择题4

1, 在&#xff08;&#xff09;情况下适宜采用 inline 定义内联函数 A 函数体含有循环语句 B 函数体含有递归语句 C 函数代码少、频繁调用 D 函数代码多&#xff0c;不常调用 解析 C&#xff0c;以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方…

SpringSecurity实现角色权限控制(SpringBoot+SpringSecurity+JWT)

文章目录 一、项目介绍二、SpringSecurity简介SpringSecurity中的几个重要组件&#xff1a;1.SecurityContextHolder&#xff08;class&#xff09;2.SecurityContext&#xff08;Interface&#xff09;3.Authentication&#xff08;Interface&#xff09;4.AuthenticationMana…

c++项目环境搭建(VMware+linux+ubantu+vscode+cmake)

想运行一个c项目&#xff0c;但是环境怎么整呢&#xff1f;b站走起&#xff01;&#xff01;&#xff01; 本文需要的安装包 链接&#xff1a;https://pan.baidu.com/s/1XJbR2F1boQ-CqV8P71UOqw 提取码&#xff1a;swin 一、在虚拟机中安装ubantu 八分钟完成VMware和ubunt…

Git命令大全,涵盖Git全部分类,非常值得收藏!

Git是一个分布式版本控制系统&#xff0c;可以让开发者在不同的平台和环境中协作开发项目。Git有很多命令&#xff0c;可以用来管理项目的状态、历史、分支、合并、冲突等。本文将介绍一些Git常用的命令&#xff0c;并给出示例和分类。 配置命令 配置命令可以用来设置Git的全局…

算法设计与分析:贪心法

目录 第一关&#xff1a;贪心法 任务描述&#xff1a; 相关知识&#xff1a; 贪心法的优缺点&#xff1a; 例题&#xff1a; 解题分析&#xff1a; 程序实现&#xff1a; 关键代码&#xff1a; 编程要求&#xff1a; 测试说明&#xff1a; 第二关&#xff1a;最小生成…