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

news2024/11/26 16:40:11

作者: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=d1
	Date operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}

👉格式(下) 

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

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

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

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

//赋值运算符重载
	//d4=d1
	Date 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=d1
	Date& 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.coloradmin.cn/o/1190707.html

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

相关文章

万宾科技智能井盖,实现对井盖的监测

随着人工智能和物联网技术的不断变化&#xff0c;各种适用于市政府提高管理能力和公共服务水平的高科技产品不断更新。在道路基础设施建设过程中&#xff0c;智能井盖传感器的出现时刻保护着城市地下生命线&#xff0c;而且可以对地下水道井盖进行实时的监测并完成数据上传等工…

Elastic Observability 8.11:ES|QL、APM 中的通用分析和增强的 SLOs

作者&#xff1a;Tom Grabowski, Katrin Freihofner, Israel Ogbole Elastic Observability 8.11 引入了 ES|QL for Observability&#xff08;技术预览版&#xff09;、Universal ProfilingTM 和 Elastic APM 集成&#xff0c;以及针对 Elastic Observability 的新 SLO &#…

判断sparse matrix是否是对称矩阵

参考&#xff1a; https://stackoverflow.com/questions/48798893/error-in-checking-symmetric-sparse-matrix import scipy.sparse as sp import numpy as np np.random.seed(1)a sp.random(5, 5, density0.5)a结果如下 sym_err a - a.T sym_check_res np.all(np.abs(s…

docker influxdb

docker & influxdb 搜索镜像 docker search influxdb docker pull influxdb: 1.4.2 docker run -d -p 8086:8086 --name influxdb influxdb:1.4.2 docker exec -it influxdb bash 连接influxdb 控制台 influx -host localhost -port 8086 influx -username root -passw…

docker部署mongodb

1&#xff1a;拉去momgodb镜像 2&#xff1a;拉去成功后&#xff0c;通过docker-compose.yml配置文件启动mongodb&#xff0c;docker-compose.yml配置如下 version: 3.8 services:mongodb-1:container_name: mongodbimage: mongo ports:- "27017:27017"volumes:- G:…

ESP8266 WiFi模块快速入门指南

ESP8266是一种低成本、小巧而功能强大的WiFi模块&#xff0c;非常适合于物联网和嵌入式系统应用。本指南将为您提供关于ESP8266 WiFi模块的快速入门步骤和基本知识。 第一步&#xff1a;硬件准备 首先&#xff0c;您需要将ESP8266 WiFi模块与您的开发板连接。通常情况下&#…

简单好看个人引导页毛玻璃页面 HTML 源码

毛玻璃个人引导页源码&#xff0c;界面简洁&#xff0c;已测可完美搭建&#xff0c;UI非常不错的&#xff0c;有兴趣的自行去安装体验吧&#xff0c;其它就没什么好介绍的了。 学习资料源代码&#xff1a;百度网盘 请输入提取码&#xff1a;ig8c

代挂单页网址发布页+加盟代理+APP下载页源码

代挂单页加盟代理网址发布页app下载页HTML单页版本&#xff0c;自行修改源码内文字。自行修改联系方式、登录地址&#xff01;上传即可使用。源码我已全部打包好&#xff0c;直接上传本站提供的源码&#xff0c;无后台&#xff0c;直接访问即可&#xff01; 源码下载&#xff…

Termius for Mac:掌控您的云端世界,安全高效的SSH客户端

你是否曾经在Mac上苦苦寻找一个好用的SSH客户端&#xff0c;让你能够远程连接到Linux服务器&#xff0c;轻松管理你的云端世界&#xff1f;现在&#xff0c;我们向你介绍一款强大而高效的SSH客户端——Termius。 Termius是一款专为Mac用户设计的SSH客户端&#xff0c;它提供了…

SPASS-探索性分析

探索性分析的意义 探索性分析更加强大,它是一种在对资料的性质、分布特点等完全不清楚的情况下,对变量进行更深入研究的描述性统计方法。在进行统计分析前,通常需要寻求和确定适合所研究的问题的统计方法, SPSS提供的探索性分析是解决此类问题的有效办法 探索性分析提供了很…

Vue3全局共享数据

目录 1&#xff0c;Vuex2&#xff0c;provide & inject2&#xff0c;global state4&#xff0c;Pinia5&#xff0c;对比 1&#xff0c;Vuex vue2 的官方状态管理器&#xff0c;vue3 也是可以用的&#xff0c;需要使用 4.x 版本。 相对于 vuex3.x&#xff0c;有两个重要变…

Hive 常用存储、压缩格式

1. Hive常用的存储格式 TEXTFI textfile为默认存储格式 存储方式&#xff1a;行存储 磁盘开销大 数据解析开销大 压缩的text文件 hive 无法进行合拆分 SEQUENCEFILE sequencefile二进制文件&#xff0c;以<key,value>的形式序列到文件中 存储方式&#xff1a;行存储 可…

贾扬清开源 AI 框架 Caffe | 开源英雄

【编者按】在开源与人工智能的灿烂星河里&#xff0c;贾扬清的名字都格外地耀眼。因为导师 Trevor Darrell 教授的一句“你是想多花时间写一篇大家估计不是很在意的毕业论文&#xff0c;还是写一个将来大家都会用的框架&#xff1f;”&#xff0c;学生贾扬清一头扎进了创 Caffe…

零代码+分布式微服务架构打造新一代一站式服务集成平台

目 录 01 项目背景 02 普元ESB产品介绍 03 新版本功能特性 04 应用案例‍‍‍‍ 05 展望与发展 01 项目背景 企业在实现数字化转型的过程中&#xff0c;随着信息化程度的提高&#xff0c;越来越多的企业开始采用微服务架构来构建自己的业务系统,各种系统之间的集成、数据共享…

计算机网络:概述

0 学时安排及讨论题目 0.1讨论题目&#xff1a; CSMA/CD协议交换机基本原理ARP协议及其安全子网划分IP分片路由选择算法网络地址转换NATTCP连接建立和释放再论网络体系结构 0.2 本节主要内容 计算机网络在信息时代中的作用 互联网概述 互联网的组成 计算机网络在我国的发展 …

【vector题解】连续子数组的最大和 | 数组中出现次数超过一次的数字

连续子数组的最大和 连续子数组的最大和_牛客题霸_牛客网 描述 输入一个长度为n的整型数组array&#xff0c;数组中的一个或连续多个整数组成一个子数组&#xff0c;子数组最小长度为1。求所有子数组的和的最大值。 要求:时间复杂度为 O(n)&#xff0c;空间复杂度为 O(n) 进…

Java算法(五):手写数组逆置API方法,实现数组逆置。 while实现 for循环实现

Java算法&#xff08;五&#xff09; while 循环实现 需求&#xff1a; 已知一个数组&#xff0c;arr {11, 22, 33, 44, 55};使用程序实现把数组中的元素交换位置。 交换后的数组为 arr {55, 44, 33, 22, 11}; 并在控制台输出交换后的数组元素。 代码示例 package com.…

集成MCU的OTP-2.4G合封芯片XL2401D,收发一体 上手简单

芯岭技术的XL2401D是一颗2.4G合封芯片&#xff0c;收发一体。合封芯片可以很好的节省PCB面积和开发成本。一颗芯片可以做到之前两颗芯片才能做到的事情。XL2401D内含MCU为九齐NY8A054E。有九齐MCU开发经验的话开发起来非常容易上手。 XL2401D芯片是工作在2.400~2.483GHz世界通…

UE5蓝图接口使用方法

在内容区右键创建蓝图接口 命名自定义&#xff08;可以用好识别的&#xff09; 双击打开后关闭左边窗口 右键函数 -- 重命名 -- 名称自定义&#xff08;用好记的&#xff09; 点击下边输入后面的 号创建一个变量 点击编译并保存 在一个蓝图类里面 -- 点击类设置 在右侧已实现的…