【c++】——类和对象(中)——赋值运算符重载

作者:chlorine

专栏:c++专栏

你站在原地不动,就永远都是观众。

【学习目标】

  • 拷贝复制——赋值运算符重载

目录

🎓运算符重载的初步认识

🌈运算符重载

🌈赋值运算符重载格式 (上)

🌈operator__判断俩个日期是否相等

🎓运算符重载的深入认识

🌈赋值运算符重载格式(下)

👉拷贝构造和赋值运算符重载的区别 

👉格式(下) 

🌈默认赋值运算符重载

🌈❌重载成全局函数


🎓运算符重载的初步认识

🌈运算符重载

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

  • 函数名字为:关键字operator后面接需要重载的运算符符号
  • 函数原型:返回值类型 operator操作符(参数列表)
//判断真假
bool operator==(参数列表);
//返回类型Date 运算符=
Date operator=(参数列表);
注意:
  • 不能通过连接其他符号来创建新的操作符:比如operator@

  • 重载操作符必须有一个类类型参数

  • .*  ::  sizeof  ?:  . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

🌈赋值运算符重载格式 (上)

  • 参数类型:const T&,传递引用可以提高传参效率

还有几个点我们后面会遇到问题提出的

  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义

对于operator关键字来对俩个数据之间的操作,我们首先来敲一段


🌈operator__判断俩个日期是否相等

利用operator来实现《判断俩个日期是否相等,如果相等返回1,如果不相等返回0》

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

这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。(友元后期会告诉)
这里既然private里的成员变量无法访问。

第一种方法:给private改成public,运行成功。 

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}
//private:int _year;int _month;int _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(2023, 10, 5);Date d2(2023, 11, 5);cout << (d1 == d2) << endl;d1.print();d2.print();return 0;
}

 第二种方法:将重载成成员函数,在类中。

我们放进类中充当成员函数,就一定能实现嘛?看看能不能运行成功。

参数太多,可是我们就俩个对象,为什么显示参数太多呢?
——这里就提到了我们之前说的一个重要指针——this(C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。)

所以我们上面实际上是三个参数,只是this对用户来说透明的,不能显示传递。

//d1==d2//d1就相当于this,d2相当于形参列表里面一个,所以括号里面就只能有一个参数。
// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& x){return _year == x._year&& _month == x._month&& _day == x._day;}

这里的判断俩个日期是否相等实际上就是再比较是否d1==d2?

d1就相当于this,d2相当于形参列表里面一个,所以括号里面就只能有一个参数。

这样就运行成功了。

判断俩个日期是否相等代码如下:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}//d1==d2//d1就相当于this,d2相当于形参列表里面一个,所以括号里面就只能有一个参数。
// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& x){return _year == x._year&& _month == x._month&& _day == x._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 10, 5);Date d2(2023, 11, 5);cout << (d1 == d2) << endl;/*d1.print();d2.print();*/return 0;
}

所以上面的代码实现了,运行没有问题


🎓运算符重载的深入认识

接下来我们了解了operator关键字的使用,我们接下来真正进入

赋值运算符重载

赋值运算符重载的内容——一个对象赋值给另一个对象。


🌈赋值运算符重载格式(下)

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}//拷贝构造Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}//赋值运算符重载void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 10, 5);Date d2(2023, 11, 5);d1 = d2;Date d3(d1);d1.print();d3.print();return 0;
}


👉拷贝构造和赋值运算符重载的区别 

我们针对上面一段代码来进行解读。看看这段代码有没有一些毛病或者一些优化的地方。

	Date d1(2023, 10, 5);Date d2(2023, 11, 5);d1 = d2;Date d3(d1);
这俩者有啥子区别呢?或者说
哪个是拷贝构造,哪个是赋值运算符重载呢?

  • 拷贝构造:是对同类对象初始化创建对象(创建一个新对象,然后给新对象初始化)

——就如上面代码的Date d3(d1)就是拷贝构造。

  • 赋值重载运算符:一个对象赋值给另一个对象(前提俩个对象都存在)

——就如上面代码的d1=d2就是赋值重载运算符。

光标对准d1=d2赋值重载运算符但是这里还没实现 ,按下fn+f10就可以看到

d2的成员变量的值赋值给了d1,继续走,创建的新的d3对象就引入了拷贝构造函数

三者都相等了,这就是运行的过程,大家可以自己敲一下来调试,进行查看。

ps:这些都是浅拷贝(值拷贝)但是日期类都是运行浅拷贝。

让我们继续来挑这段代码的毛病吧~

int i, j, k;i = j = k = 0;

对于这种赋值运算符,c语言允许不允许这样写?——允许(连续赋值)

0先赋值给k,这个表达式有个返回值,这个返回值是k,左边的操作数就是返回值,然后继续k赋值给j,j就是返回值,以此类推.......最后i的返回值就是0。

那么日期类支持嘛?
 

	Date d4, d5;d5 = d4 = d1;

因为这里从右往左,d1赋值给d4,返回值是void,所以是无法往前走。

所以这里正确的方法是什么?

这里从右往左,d1赋值给d4,返回值应该是d4,然后d4赋值给d5

所以我们就得探究 如何让d4是返回值,而不是void?

d4=d1;

d就是d1,this就是d4的地址。this的类型是(const Date&this),所以我们的返回类型是Date.

我们需要返回d4,如何返回d4呢?

我们当初说了this不能在形参和实参的位置给,但是可以在函数内部显示给。

//赋值运算符重载//d4=d1Date operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}

👉格式(下) 

  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 返回*this :要复合连续赋值的含义

        a.(全局对象和静态对象)出了作用域都还在,用引用返回效率更高。

        b. 局部对象出了作用域都不在了,不用引用返回.

那我们的这里的*this出了作用域还在不在?

//赋值运算符重载//d4=d1Date operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}

——当然在啦

——因为首先我们要知道this是形参在栈区,出了作用域就会被销毁,那么这里是this嘛?是*this,*this是d4,d4的生命周期不再函数中,至少销毁了d4还在,*this只是一个中介而已。所以可以用引用返回。*this的别名是d4.

	//赋值运算符重载//d4=d1Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}

最后返回的是*this的别名那就是d4.

传值返回和传引用返回的区别:

传值返回:传值返回的是对象的拷贝,每一个operator赋值都是一次拷贝。(传值返回大多数是临时变量)

传引用返回:传引用返回的是*this的别名。

这样我们就可以连续赋值了。

  • 检测是否自己给自己赋值

大家有没有想过d1=d1是怎样的呢?是编译报错还是正常运行呢?

这是成功运行的。

我们来调试看看咋样?

这样也是可以的,this和&d都是自己。

如果你不想拥有自己与自己赋值,那么就可以加一个断言

Date &operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}

d1=d1

if(this!=&d) ___this是d1的地址,&d就是d的地址。如果俩者地址都相同就不用赋值了。

🌈默认赋值运算符重载

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
默认生成赋值重载跟拷贝构造行为一样;
1.内置类型成员——值拷贝/浅拷贝  (Date)
2.自定义类型成员会去调用他的赋值重载 (MyQueue)
Stack是深拷贝,编译器自动生成的是浅拷贝。
如果我们不写赋值重载函数,编译器会不会自动生成?
class Date
{
public:Date(int year = 2003, int month = 10, int day = 5){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}//拷贝构造Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}//赋值运算符重载//Date& operator=(const Date& d)//{//	if (this != &d)//	{//		_year = d._year;//		_month = d._month;//		_day = d._day;//	}//	return *this;//}private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 10, 5);Date d2(2023, 11, 5);d1 = d2;d1.print();d2.print();return 0;
}

我们给赋值运算符重载函数屏蔽调,看编译器是否会进行自动生成?

连续赋值呢?

所以默认生成的赋值运算符重载是可以实现连续对象赋值。

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,但是和拷贝构造一样,并不是所有都是值拷贝,Date和Myqueue不需要我们自己实现赋值重载,因为Date是浅拷贝(值拷贝),Myqueue是自定义类型,但是Stack是需要自己去实现的,因为它是深拷贝,而默认生成的是浅拷贝.

🌈❌重载成全局函数

赋值运算符只能重载成类的成员函数不能重载成全局函数
就像命名空间域展开和全局变量一样的。
赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的
赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
能不能声明和定义分离?——可以。类外面给定义,类里面给声明,还是成员函数。

你站在原地不动,就永远都是观众。

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

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

相关文章

Mysql 一步到位实现插入或替换数据(REPLACE INTO语句)

单条数据插入/替换 比如有一个数据表叫test_table&#xff0c;包含: 主键&#xff1a;key_id数据&#xff1a;value 运行&#xff1a; REPLACE INTO test_table (key_id,value) VALUES ("id_1","value_1"); REPLACE INTO test_table (key_id,value) VAL…

使用 Socks5 来劫持 HTTPS(TCP-TLS) 之旅

MITM 劫持的过程中&#xff0c;HTTP 协议并不是唯一选择。 实际在 MITM 使用过程中&#xff0c;BurpSuite 和 Yakit 提供的交互式劫持工具只能劫持 HTTP 代理的 TLS 流量&#xff1b;但是这样是不够的&#xff0c;有时候我们并不能确保 HTTP 代理一定生效&#xff0c;或者说特…

Adobe Photoshop 2020给证件照换底

1.导入图片 2.用魔法棒点击图片 3.点选择&#xff0c;反选 4.选择&#xff0c;选择并遮住 5.用画笔修饰证件照边缘 6. 7.更换要换的底的颜色 8.新建图层 9.使用快捷键altdelete键填充颜色。 10.移动图层&#xff0c;完成换底。

工业摄像机参数计算

在工业相机选型的时候有点懵&#xff0c;有一些参数都不知道咋计算的。有些概念也没有区分清楚。‘’ 靶面尺寸 CMOS 或者是 CCD 使用几分之几英寸来标注的时候&#xff0c;这个几分之几英寸计算的是什么尺寸&#xff1f; 一开始我以为这个计算的就是靶面的实际对角线的尺寸…

clickhouse安装与远程访问

安装&#xff08;本文以ubuntu系统为例&#xff09; 单节点设置​ 为了延迟演示分布式环境的复杂性&#xff0c;我们将首先在单个服务器或虚拟机上部署ClickHouse。ClickHouse通常是从deb或rpm包安装&#xff0c;但对于不支持它们的操作系统也有其他方法。 例如&#xff0c;…

贺天下功夫酱酒闪耀亮相2023佛山秋色系列活动

11月1日至5日&#xff0c;2023年广东非遗周暨佛山秋色巡游系列活动在佛山举行&#xff0c;以“品味佛山 秋醉岭南”为主题&#xff0c;好戏连台。贵州贺天下酒业独家赞助佛山祖庙秋祭、乡饮酒礼&#xff0c;还全面参与佛山秋色巡游、佛山非遗美食展、佛山非遗音乐会等多个活动&…

Tomcat隐藏版本号和关闭默认管理页面

一. 隐藏Tomcat异常页面中的版本信息&#xff0c;Tomcat服务器版本号泄露 Tomcat/8.5.xx相关版本号等信息&#xff0c;是不安全的。这会被黑客获取到&#xff0c;利用该版本的其他漏洞对服务器进行异常操作&#xff0c;所以需要隐藏掉。 进入tomcat安装目录 apache-tomcat-8.…

索引和事务

文章目录 一、索引1.1 概念1.2 作用1.3 使用场景1.4 使用 二、事务2.1 为什么要使用事务2.2 事务的概念2.3 事务的使用 三、内容重点总结 一、索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含对数据表所有记录的引用指针。在MySQL中&#xff0c;索引是基于一个或多个列的…

vr地铁消防虚拟逃生自救系统降低财产及人员伤害

无论是在公共场所还是在家中&#xff0c;火灾都是一种常见的突发事件。这往往会严重影响到人们的财产和生命安全。因此&#xff0c;如何预防火灾和安全逃生就成为了非常重要的话题。这款VR模拟火灾疏散逃生系统&#xff0c;帮助人们了解火灾逃生的技巧以及正确的应对方法。 以传…

火爆全网!用 Pyecharts 就能做出来“迁徙图“和“轮播图“

1.pyecharts知识点回顾 1&#xff09;知识回顾 前面我们已经讲述了&#xff0c;如何使用pyecharts进行图形的绘制&#xff0c;一共涉及到如下四步。我们今天就是按照下面这几步来进行迁徙图和轮播图的绘制。 ① 选择图表类型&#xff1b; ② 声明图形类并添加数据&#xff1…

《QT从基础到进阶·十五》用鼠标绘制矩形(QGraphicsView、QPainter、QGraphicsRectItem)

以下是鼠标绘制矩形最全的一种用法&#xff0c;完整源码将会放在最后面。 QT版本&#xff1a;5.15.2 VS版本&#xff1a;2019 1、在界面加载一张图片 界面的搭建选用QGraphicsView&#xff0c;自定义类GraphicsView继承QGraphicsView&#xff0c;在主程序中点击按钮打开 图片&…

什么是Node.js的NVM(Node Version Manager)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…