c++笔记——概述运算符重载——解析运算符重载的难点

         前言:运算符重载是面向对象的一个重要的知识点。我们都知道内置类型可以进行一般的运算符的运算。但是如果是一个自定义类型, 这些运算符就无法使用了。那么为了解决这个问题, 我们的祖师爷就在c++中添加了运算符重载的概念。 本篇主要通过实例的实现来讲述了运算符重载的知识点。  

目录

运算符重载的应用

运算符重载的定义

赋值运算符重载

流提取和流插入

const问题

 定义位置的问题


运算符重载的应用

        首先, 先了解以下运算符重载能做些什么,先看下面这张图

        在这张图中, 红框框是我定义的一个自定义类型。 这个自定义类型的成员包括了string类型的_name。 int类型的_age.它代表的就是一个人的类型。 每一个类的实例化对象都有他们的姓名, 也有他们的年龄。 那么很显然, 它的实例化对象也可以进行年龄的加法,也就是增长年龄。 

        所以, 绿色箭头指向的位置, 如果我的本意是想要让实例化对象p1的_age加2, 也就是p1这个实例化对象的年龄增加了2。但是很显然, 这个运算对于+这个运算符来说, 是做不到的, 因为对于运算符来说, 他们默认只能处理内置类型, 而不能处理自定义类型。

        这里如果想要处理自定义类型, 就需要使用我们的运算符重载, 运算符重载的目的就是为了让一个运算符可以处理自定义类型的运算。  

        如下是我重载的peo类的一个加法运算符。

//这里我将整个类搬过来方便观察。
struct peo 
{
	string _name;
	int _age;

    
    //默认构造, 这里是默认构造, 因为每一个参数都有缺省, 那么这个就是一个默认构造函数
	peo(string str = string(), int age = 0) 
		:_age(age)
	{
		_name = str;
	}

    //定义的peo类的加法运算
    peo operator+(const int x)
    {
	    peo tmp = *this;
	    tmp._age += x;
	    return tmp;
    }


};

        拥有了这个运算之后我们原先的代码就可以跑了:

现在是还没有执行加法运算的时候:

        下图是运算之后的p1:

        可以看到, p1的年龄从原本的18变成了20.

运算符重载的定义

        知道了运算符重载的作用,那么我们如何来重载一个运算符呢。 首先, 重载运算符的函数名统一都要使用"operator", 然后在operator后面加要重载的运算符。

        比如, 我如果想要重载一个加法运算符, 那么函数名就是这样: "operator+"

        然后就是参数的问题, c++规定, 被重载的运算符是按照操作数的前后进行传参的。 也就是说, 如果我重载了一个运算符+, 那么当我使用这个操作符的时候, “+”前面的操作符就是要传的第一个形参。 “+"后面就是要传的第二个形参。 我们利用上面的代码进行举例:

        这里需要着重注意的是第二个参数, 也就是那个const int x。 如果这里的参数, 我不使用传值接收, 而是使用传引用接收,也就是int& x, 那么就是不可以的。这里的const不能丢。 因为这里我们通常传送整形传送的都是常量,也就是右值,不能取地址的值。而如果形参引用想要接收右值有两个方法:一个是使用const 将左值引用变为常性, 那么就可以接收右值了。 一个是使用右值引用,右值引用时c++11的语法, 这个内容现在先知道有就行, 后续文章会学习到。 

        所以, 如果使用左值引用接收, 也就是我们平常使用的引用&来接收, 就需要使用const, 其实这里也时我们对于const 理解的进一步加深。 就是我们平时在使用左值引用参数接收左值时, 一定要分析好, 这个左值引用是否需要接收右值, 如果不接受右值, 那么可以不加const, 但是如果接收右值的话, 一定要加const。 而其中比较烦人的就是函数的返回值, 要知道, 函数的返回值如果是一个传值返回, 那么它在返回的时候会创建一个将亡值。 如果我们要对这个函数的返回值进行运算,或者其他需要调用函数的操作。 一定要注意,调用的这个函数是一个左值引用接收的话, 这个左值引用参数一定要加const。

        好, 这里这个板块的内容大致就这些, 我们再重载几个运算符,这个板块的内容就这么过了。


struct peo 
{
	string _name;
	int _age;

	peo(string str = string(), int age = 0) 
		:_age(age)
	{
		_name = str;
	}

    //加法运算符重载
	peo operator+(const int x)
	{
		peo tmp = *this;
		tmp._age += x;
		return tmp;
	}
    
    //比较运算符重载
	bool operator>(const peo& d) 
	{
		return _age > d._age;
	}

    //比较运算符重载
	bool operator==(const peo& d) 
	{
		return _age == d._age;
	}

    //赋值运算符重载
	peo& operator=(const peo& d) 
	{
		_name = d._name;
		_age = d._age;
	}


};

赋值运算符重载

        其他的运算符都很好说, 本篇真正要着重讲解的运算符其实只有三个, 也可以说是两个, 因为有两个是差不多的。 这三个运算符就是:赋值运算符, 流提取运算符, 流插入运算符。这里流提取和流插入的性质是类似的。

       这个板块先来讲述赋值运算符。

        我的前面的文章——解析默认构造函数的那篇文章中主要分析了三个默认函数——默认构造函数,默认拷贝构造,默认析构函数。 

        这三个默认函数的性质我们再来复习一下:

  • 默认构造函数是对内置类型不做处理, 对于自定义类型会去调用它的默认构造函数。 
  • 默认拷贝构造是对内置类型就是进行浅拷贝, 对于自定义类型会去调用它的拷贝构造。 
  • 默认析构函数就是对内置类型不做处理, 对于自定义类型会去调用它的析构函数。

        但是, 其实除了上面的三个 ”我们不写,系统自动生成“ 的函数, 还有一个函数如果我们不写,系统也会自动生成。 它就是——默认赋值运算符重载。现在, 我们不写, 让编译器自己重载一个赋值运算符帮我们计算一下我们定义的peo类。

注意, 我们没有进行重载赋值符号, 我们现在通过调试观察编译器是如何帮我们进行处理的:

现在还没有调用赋值符号, 此时p1的_age成员的值是18。

        f10下一步, 执行赋值符号, 这里的p1的_age成员的值已经变了, 说明我们虽然没有自己实现赋值运算符的重载, 但是编译器自己帮我们生成了。 

        编译器默认生成的赋值运算符重载的性质:编译器默认生成的赋值重载对于内置类型进行浅拷贝, 对于自定义类型会去调用它的赋值重载。

        其实, 对于默认赋值运算符重载我们可以这么理解:


class A 
{
public:
	int a;
	int b;
	//......其他成员变量

	A(const A& ka) 
	{
		//a = ka.a;
		//b = ka.b;
		//....其他成员变量使用赋值运算符进行赋值
	}

};

        就是类似于上图, 其实编译器默认生成的赋值运算符重载我们可以理解为:对象的每一个成员变量都是用赋值符号进行直接赋值操作。 那么对于内置类型就是普通的浅拷贝。 对于自定义类型就会去调用这个自定义类型的赋值运算符重载。 

        对于赋值重载来说, 其实最重要的就是默认赋值重载。而赋值重载的重载方式与其他的重载符号没有什么不同。

流提取和流插入

const问题

        我们平时使用的>>就是库里面重载的一个流提取运算符。对于istream,由于知识储备不足, 博主不能给出详细的解释。 我们在这个板块只讨论很浅的知识点。我们看下面的一段代码:

#include<iostream>
using namespace std;
#include<assert.h>

struct student 
{
	char _name[20];
	int _age;
	char _gender[10];
	int _num;
	
};

const istream& operator>>(const istream& in, student& stu) 
{
	cout << "请输入姓名, 年龄, 性别, 和学号:>";
	in >> stu._name >> stu._age >> stu._gender >> stu._num;
	return in;
}

        这一串代码中的流提取是有问题的。 为什么? 

        这里我们可以这么理解: const 修饰istream类型的对象 in,对象in具有常性。然后在istream类里面的流指针成员变量(这里可以想象成有一个流指针成员变量)也具有了常性。 那么, 如果类被赋予了常性, 成员指针变成什么?其实是变成指针常量, 也就是不能修改指向。 所以, 如果istream类型的in具有常性, 那么它里面的指针成员就会都变成指针常量。 流指针无法在终端一个字符一个字符的改变读取数据。所以图中的流提取是不对的。

        流插入也是同样的道理, 如果给ostream的实例化对象赋予常性, 那么里面的流指针就无法改变指向, 那么就无法依次在终端输入数据。 所以, 对于流插入和流提取的运算符, 最重要的一点就是不能给他们加上const。 否则这里就无法正常编译。 正确的重载如下:

#include<iostream>
using namespace std;
#include<assert.h>

struct student 
{
	char _name[20];
	int _age;
	char _gender[10];
	int _num;
	
};

istream& operator>>(istream& in, student& stu) 
{
	cout << "请输入姓名, 年龄, 性别, 和学号:>";
	in >> stu._name >> stu._age >> stu._gender >> stu._num;
	return in;
}

ostream& operator<<(ostream& out, const student& stu) 
{
	out << stu._name << " " << stu._age << " " << stu._gender << " " << stu._num << endl;;
	return out;
}

 定义位置的问题

        然后, 对于流插入和流提取还有一个比较重要的问题就是它的定义的位置。 我们知道, 对于一个类来说, 它里面的不管是非静态成员函数还是运算符重载的第一个参数都默认是this指针。但是, 要知道, 我们在进行流插入或者流提取操作时, 谁在前, 谁在后?(就像 cin >> a)

        很明显, 流插入或者流提取符号在前, 然后标识符在后。 所以, 我们在重载流插入和流提取操作符的时候就要让第一个参数是istream或者ostream。 但是, 如果我们将这两种重载运算符放在类域中。那么第一个参数就会变成this指针, 这个就无法满足流插入流提取运算符重载的规则了。 就不对了。 所以, 我们应该将流插入运算符或者流提取运算符的定义放在类域外。 

        就像上面的一串代码, 这里搬一下放在下面:

        就像如图, 这里我定义的流提取和流插入就是在类域外。然后, 第一个参数是out, 第二个参数才是学生类。 如果不定义在类外, 那么第一个参数默认为学生类。 就无法满足要求。

----------------------------------------------------------------------------------------------------------------------

以上, 就是本节的全部内容。

        

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【时序大模型总结】学习记录(1)

1.TimeGPT-1 思路&#xff1a;在来自不同领域的大量数据上训练模型&#xff0c;然后对未见过的数据产生零样本的推断。 作者对TimeGPT进行了超过1000亿个数据点的训练&#xff0c;这些数据点都来自开源的时间序列数据。该数据集涵盖了广泛的领域&#xff0c;从金融、经济和天气…

YOLOv8原理解析[目标检测理论篇]

接下来是我最想要分享的内容&#xff0c;梳理了YOLOv8预测的整个流程&#xff0c;以及训练的整个流程。 关于YOLOv8的主干网络在YOLOv8网络结构介绍-CSDN博客介绍了&#xff0c;为了更好地介绍本章内容&#xff0c;还是把YOLOv8网络结构图放在这里&#xff0c;方便查看。 1.YOL…

AI讲师大模型培训老师叶梓:大模型应用的方向探讨

大模型应用的关键方向及其落地案例可以从多个角度进行探讨&#xff0c;结合最新的研究和实际应用案例&#xff0c;我们可以更全面地理解这些技术如何推动社会和经济的发展。 Agent&#xff08;数字代理&#xff09;: 方向说明:Agent方向的AI技术旨在创建能够独立执行任务、做出…

对于SOMP算法的测试

刚开始只上传了SOMP算法的代码&#xff0c;并没有过多介绍。 所以本篇文章对SOMP算法用法进行一个介绍 SOMP算法代码 function [X_hat] MMV_SOMP(Y, PHI, s)% SOMP:同时正交匹配追踪 simultaneous orthogonal matching pursuit% 论文&#xff1a;J. Determe, J. Lo…

若依plus 某些接口(用户信息等)响应突然变慢

今天一大早起来发现我的接口突然响应变慢了&#xff01; 就什么都没动&#xff0c;啥也没改&#xff0c;但是一些接口又很快。 百度了很多&#xff0c;都说叫我改sql查询方式&#xff0c;又怀疑是过滤器的问题&#xff0c;很遗憾都不是&#xff01; 一个响应40秒&#xff01;…

[译文] 恶意代码分析:1.您记事本中的内容是什么?受感染的文本编辑器notepad++

这是作者新开的一个专栏&#xff0c;主要翻译国外知名安全厂商的技术报告和安全技术&#xff0c;了解它们的前沿技术&#xff0c;学习它们威胁溯源和恶意代码分析的方法&#xff0c;希望对您有所帮助。当然&#xff0c;由于作者英语有限&#xff0c;会借助LLM进行校验和润色&am…

IOT-9608I-L ADC端口的使用(连续采样ADC值)

目录 概述 1 硬件介绍 1.1 认识硬件 1.2 引脚信号定义 2 软件功能实现 2.1 查看iio:device0下的接口信息 2.2 实现连续采样ADC 2.2.1 功能描述 2.2.2 代码实现 2.2.3 详细代码 3 测试 概述 本文主要讲述IOT-9608I-L ADC端口的使用方便&#xff0c;其内容包括板卡上的…

密室逃脱游戏-第12届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第58讲。 密室逃脱游戏&…

2024年第九届数维杯数学建模B题思路分享

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

分布式模式让业务更高效、更安全、更稳定

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;分布式模…

Ubuntu添加网络映射路径

参考资料 linux挂在阿里云盘&#xff08;webdav协议&#xff09;给服务器扩容、备份数据等_davfs2-CSDN博客 Linux将WebDAV为本地磁盘 - 夏日冰菓 (lincloud.pro) systemd系统开机运行rc.local_rc-local.service: failed to execute command: exec -CSDN博客 系统版本&#xff…

word格式技巧

文章目录 论文格式技巧论文交叉引用怎么弄论文的页码怎么弄 论文格式技巧 论文交叉引用怎么弄 1.取消文献原有的编号 2.定义新编号 3.具体编号设置 4.在引用的地方插入&#xff0c;具体引用选项卡–>交叉引用–>选择后插入 2. 4. 论文的页码怎么弄 假设我们有这样一…

List的两种实现

前置知识&#xff1a; 数组 baseAddress&#xff1a;数组的首地址 dataTypeSize&#xff1a;数组中元素类型的大小&#xff0c;如int为4字节 为什么数组索引从0开始&#xff0c;假如从1开始不行吗&#xff1f; 在根据数组索引获取元素的时候&#xff0c;会用索引和寻址公式来计…

HBase 读写流程

HBase 读写流程 1. 读流程 Client先访问zookeeper&#xff0c;从zookeeper获取meta region的位置从meta region中读取meta表中的数据&#xff0c;meta中存储了用户表的region信息&#xff1b;根据namespace、表名和rowkey在meta表中找到对应的region信息&#xff1b;找到这个r…

[Kotlin]创建一个私有包并使用

1.创建Kotlin项目 创建项目&#xff1a; 在Android Studio或其他IDE中选择“Create New Project”。选择Kotlin和Gradle作为项目类型和构建系统。指定项目名称和位置&#xff0c;完成设置。 添加依赖: 如果你的库需要额外的依赖&#xff0c;可以在 build.gradle (Module: app…

文件各种上传,离不开的表单 [html5]

作为程序员的我们&#xff0c;经常会要用到文件的上传和下载功能。到了需要用的时候&#xff0c;各种查资料。有木有..有木有...。为了方便下次使用&#xff0c;这里来做个总结和备忘。 利用表单实现文件上传 最原始、最简单、最粗暴的文件上传。 前端代码&#xff1a; //方…

oracle 清理 trace 和 alert 日志文件

某天,发现磁盘空间被占满了&#xff0c;继续查询发现是 oracle 的日志文件占满了磁盘空间 其中: trace文件有35G, alert 有23G 目录地址是: diag/rdbms/orcl/orcl/trace, diag/rdbms/orcl/orcl/alert 都是在 oracle 目录下的 diag 目录内部 # 可以使用 以下命令对目录大小进行排…

Git与GitHub交互

注册 https://github.com/ 本地库与远程库交互方式 创建本地库并提交文件 创建远程库 在本地库创建远程库地址别名 查看现有远程库地址的别名 git remote -v 创建远程库地址别名 git remote add [别名] [远程地址] 远程路地址位置 示例 成员1推送 git push [别名] [分支…

视频剪辑图文实例:一键操作,轻松实现视频批量片头片尾减时

视频剪辑是现代媒体制作中不可或缺的一环&#xff0c;而批量处理视频更是许多专业人士和爱好者的常见需求。在剪辑过程中&#xff0c;调整视频的片头片尾时长可以显著提升视频的质量和观感。本文将通过图文实例的方式&#xff0c;向您展示如何一键操作&#xff0c;轻松实现视频…

借助Aspose.SVG图像控件,在线将 PNG 转换为 Base64 字符串

Aspose.SVG for .NET 是用于SVG文件处理的灵活库&#xff0c;并且与其规范完全兼容。API可以轻松加载&#xff0c;保存和转换SVG文件&#xff0c;以及通过其文档对象模型&#xff08;DOM&#xff09;读取和遍历文件的元素。API独立于任何其他软件&#xff0c;使开发人员无需使用…