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

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章带大家认识赋值运算符重载,const成员,取地址及const取地址操作符重载等内容

目录

  • 1.赋值运算符重载
    • 1.1运算符重载
      • 1.1.1特性:
    • 1.2赋值运算符重载
    • 1.3 赋值运算符的其他性质
    • 1.4前置++和后置++重载
  • 2.const成员函数
  • 3.取地址及const取地址操作符重载

1.赋值运算符重载

1.1运算符重载

运算符重载是一种编程语言特性,它允许开发者为已有的运算符提供自定义的实现。这意味着你可以改变某些运算符在你自定义的类或数据类型上的行为。比如,你可以定义加号运算符(+)如何在你自定义的数据结构上进行运算

什么意思呢,我们来讲解:首先我们定义日期类Date,并实例化两个对象:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2018, 9, 26);
	Date d2(2024, 3, 29);
	return 0;
}

我们如何判断两个年份相等呢?

如果是常规方法,我们会写一个比较函数,来判断是否相同:

bool issame(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2018, 9, 26);
	Date d2(2024, 3, 29);
	cout << issame(d1, d2) << endl;
	return 0;
}

那如果我们想直接通过用d1==d2来判断是否相同呢?这里就用到了操作符重载

运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似,注意这里说的重载与我们的函数重载不是一个意思

函数名字为:关键字operator后面接需要重载的运算符符号

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(2018, 9, 26);
	Date d2(2024, 3, 29);
	cout << (d1==d2) << endl;
	return 0;
}

在这里插入图片描述

在这里插入图片描述
我们发现,直接进行判断时,调用了比较函数

但是这里是全局的定义的operator==,这里会发现运算符重载成全局的就需要成员变量是公有的,即我的成员不能是private私有的,那么封装性如何保证?

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date & d2)
	{
		return _year == d2._year
		&& _month == d2._month
			&& _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
bool operator==(const Date& d2)
{
    return _year == d2._year
           && _month == d2._month
           && _day == d2._day;
}

这部分是Date类中==运算符的重载。这个重载让你可以使用==来比较两个Date对象是否相等,即它们的年、月、日是否都相同

关键点讲解

  • 参数operator==函数接受一个类型为const Date&的参数d2,它是比较操作的右侧操作数。左侧操作数是调用这个函数的对象,this指针指向的对象
  • const关键字:参数使用const修饰符和引用传递来保证效率和避免不必要的拷贝,同时确保不会修改传入的对象
  • 函数体:函数体中,通过比较两个Date对象的年、月、日字段来决定这两个对象是否相等。如果所有字段都相等,则返回true;否则,返回false

我们接着调用这个函数:

int main()
{
	Date d1(2018, 9, 26);
	Date d2(2024, 3, 29);
	cout << d1.operator==(d2) << endl;
	cout << (d1==d2) << endl;
	return 0;
}

注意
注意这里的顺序,d1在前与在后是不同的,如果我们写一个小于函数的运算符重载,顺序不同,意思刚好相反

我们有两种方式进行调用,这两种方式是相同的:
在这里插入图片描述

在上面的讲解之后,相信大家对运算符重载有了一定的了解,他就是允许自定义对象使用运算符它的返回值是根据运算符来决定的比如完成加减操作,我们就返回int类型,判断是否大于小于,就用bool类型

1.1.1特性:

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

1.2赋值运算符重载

在这里插入图片描述
我们知道,拷贝赋值有两种,拷贝构造和赋值重载,我们看拷贝构造:

Date d1(2018, 9, 26);
Date d2(d1);

那如果我们用赋值运算符重载呢?可以写成下面的形式:

d2=d1;

关键区别

  • 拷贝构造函数在对象创建时使用,用于初始化新对象。赋值运算符重载在对象已存在时使用,用于将一个对象的值赋给另一个对象
  • 目的:拷贝构造函数的目的是创建一个新的、状态相同的对象副本。赋值运算符的目的是改变一个已存在对象的状态,使其与另一个对象的状态相同
  • 拷贝构造函数通常接收一个对同类对象的常引用。赋值运算符重载通常返回对象的引用,并接收一个对同类对象的常引用作为参数

我们接下来讲解赋值运算符重载的具体实现来体现上面的特点:

能不能直接这么写呢?:

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

在这里插入图片描述

这个在单个赋值是可以的,那如果,我想像c语言一样同时实现多个变量的连续赋值的场景呢?

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

那我们这个函数就无法满足要求了,我们该如何修改呢?

我们不妨探讨连续赋值的本质:

b=c=10;

这里执行步骤:

  • 10赋值给c,c=10这个表达式返回值为左操作数c
  • c再作为b=c的有操作数给b赋值,返回值为左操作数b

在这里插入图片描述

所以,我们的自定义类型也要符合这里的行为

所以,我们需要对函数进行修改:

第一次修改

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

返回左操作数,返回*this

我们这里用的是传值返回,意味着这里返回的不是*this,返回的是*this的拷贝,则需要调用拷贝构造函数:
在这里插入图片描述
在这里插入图片描述
所以我们需要再次修改:

第二次修改:

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

我们返回引用

还有一个问题,如果自身给自身赋值呢?

d1=d1;

为什么自赋值不行?

自赋值在大多数情况下是可以工作的,但是在特定的情况下,如果没有正确处理,它可能会引起错误或意外的行为。考虑自赋值的主要原因是为了确保当对象赋值给自身时,程序仍然能够正确、安全地运行
特别是在类中涉及到动态内存管理时,不正确处理自赋值可能会导致问题。例如,假设一个类内部分配了动态内存,如果在赋值操作中首先释放了这块内存(预备重新分配),而源对象和目标对象实际上是同一个对象,那么这个操作实际上会破坏源对象的状态,导致未定义行为

我们还需要再次修改一次:

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

我们这里判断条件是地址的比较,如果地址不相同说明不是同一个对象,可以赋值

1.3 赋值运算符的其他性质

赋值运算符只能重载成类的成员函数不能重载成全局函数
在这里插入图片描述

我们首先得把成员类型设置为公有的,发现还是报错,
在这里插入图片描述

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数

如果我们不写赋值运算符重载,编译器是否会默认生成呢?

在这里插入图片描述
结果是会生成的

所以这里与我们拷贝构造等函数性质一致:

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

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?

答案是需要的,如遇到下面的动态内存管理

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

在这里插入图片描述

  1. s1对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间,然后存了4个元素1 2 3 4
  2. s2对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间,没有存储元素
  3. 由于Stack没有显式实现赋值运算符重载,编译器会以浅拷贝的方式实现一份默认的赋值运算符重载即只要发现Stack的对象之间相互赋值,就会将一个对象中内容原封不动拷贝到另一个对象中
  4. s2 = s1;当s1给s2赋值时,编译器会将s1中内容原封不动拷贝到s2中,这样会导致两个问题:
    • s2原来的空间丢失了,存在内存泄漏
    • s1和s2共享同一份内存空间,最后销毁时会导致同一份内存空间释放两次而引起程序崩溃

注意
如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

1.4前置++和后置++重载

在C++中,前置++和后置++运算符都可以被重载,以提供用户定义类型(比如类)的自增功能。它们之间的主要区别在于参数和返回值,这影响了它们的使用和效率

前置++

前置++直接对对象进行自增操作,并返回自增后的对象引用。这意味着它在自增后立即返回对象的状态,使得操作可以立即反映在对象上

Date& operator++()
 {
 _day += 1;
 return *this;
 }

后置++

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

注意:后置++是先使用后+1因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1

Date operator++(int)
 {
 Date temp(*this);
 _day += 1;
 return temp;
 }

temp是临时对象,因此只能以值的方式返回,不能返回引用

2.const成员函数

假如我们现在定义一个const对象,想访问它的Print函数,我们发现是调用不了的:
在这里插入图片描述

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

这里权限进行放大了,接着,我们来介绍const成员函数

原来是const Date*,而我的this类型是Date*,意味着需要将this指针也改为const Date*,所以才有了下面的函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改内容是只读的

在这里插入图片描述

class Date
{
public:
 Date(int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 void Print()
 {
 cout << "Print()" << endl;
 cout << "year:" << _year << endl;
 cout << "month:" << _month << endl;
 cout << "day:" << _day << endl << endl;
 }
 void Print() const
 {
 cout << "Print()const" << endl;
 cout << "year:" << _year << endl;
 cout << "month:" << _month << endl;
 cout << "day:" << _day << endl << endl;
 }
private:
 int _year; // 年
 int _month; // 月
 int _day; // 日
};
void Test()
{
 Date d1(2022,1,13);
 d1.Print();
 const Date d2(2022,1,13);
 d2.Print();
}

我们查看结果:
在这里插入图片描述

如果没有const修饰的函数呢,我Date类型的对象能否调用const成员函数呢?

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; 
	int _month; 
	int _day; 
};
void main()
{
	Date d1(2022, 1, 13);
	d1.Print();
}

在这里插入图片描述
可以的,这里是权限的缩小

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗? 不可以,权限放大
  2. 非const对象可以调用const成员函数吗? 可以,权限缩小
  3. const成员函数内可以调用其它的非const成员函数吗? 不可以,权限放大
  4. 非const成员函数内可以调用其它的const成员函数吗?可以,权限缩小

指针和引用才存在权限放大

3.取地址及const取地址操作符重载

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{

		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

在这里插入图片描述
这里是默认成员函数,我们删去这两个函数照样可以取地址

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{

		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

在这里插入图片描述
所以,我们没有必要深究这个东西究竟有什么用,我们只进行简单的语法了解即可

当然,如果你想让它普通对象定义后只能返回空值,你可以这么写:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{

		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return nullptr;
	}
private:
	int _year; 
	int _month; 
	int _day; 
};

日常并没有这种需要

本节内容到此结束!!!感谢大家阅读!!

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

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

相关文章

【Python基础教程】3 . 算法的时间复杂度

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;python基础教程 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、…

首个基于SSM-Transformer混合架构,开源商业大模型Jamba

3月29日&#xff0c;知名AI研究实验室AI21在官网开源了&#xff0c;首个基于SSM-Transformer混合架构的商业大模型——Jamba。 目前&#xff0c;ChatGPT、Stable Difusion 、Lyria等产品使用的皆是Transformer架构&#xff0c;虽然在捕捉序列内长距离依赖关系、泛化能力、特征…

基于SpringBoot的农产品直卖平台

采用技术 基于SpringBoot的农产品直卖平台的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 用户功能 农产品信息 确认下单 农产品订单 购物车 商家功…

vue 组件基础

组件允许我们将 UI 划分为独立的、可重用的部分&#xff0c;并且可以对每个部分进行单独的思考。在实际应用中&#xff0c;组件常常被组织成层层嵌套的树状结构&#xff1a; 这和我们嵌套 HTML 元素的方式类似&#xff0c;Vue 实现了自己的组件模型&#xff0c;使我们可以在每…

在遭受攻击时如何有效监测服务器流量峰值——实战指南

引言 在网络安全领域&#xff0c;分布式拒绝服务攻击&#xff08;DDoS&#xff09;是一种常见的针对服务器及网络资源的恶意行为&#xff0c;它通过短时间内发送大量无效请求&#xff0c;导致服务器不堪重负而无法正常服务合法用户。当服务器遭受攻击时&#xff0c;快速识别并…

【重磅消息】HANA Cloud QRC1/2024发布了

SAP重棒消息&#xff0c;HANA Cloud于3月25日正式发布2024第一个版本QRC1 。有时候&#xff0c;我爱把它跟PostgreSQL的小版本进行类比。因为PG基本上也是一个季度&#xff0c;发布一个版本&#xff0c;但那个是小版本。看起来有些类似。如果以年份开头&#xff0c;HANA Cloud也…

ARP协议定义及工作原理

ARP的定义 地址解析协议(Address Resolution Protocol&#xff0c;ARP)&#xff1a;ARP协议可以将IPv4地址(一种逻辑地址)转换为各种网络所需的硬件地址(一种物理地址)。换句话说&#xff0c;所谓的地址解析的目标就是发现逻辑地址与物理地址的映射关系。 ARP仅用于IPv4协议&a…

원클릭으로 주류 전자상거래 플랫폼 상품 상세 데이터 수집 및 접속 시연 예제 (한국어판)

클릭 한 번으로 전자상거래 플랫폼 데이터를 캡처하는 것은 일반적으로 웹 페이지에서 정보를 자동으로 추출 할 수있는 네트워크 파충류 기술과 관련됩니다.그러나 모든 형태의 데이터 수집은 해당 웹 사이트의 사용 약관 및 개인 정보 보호 정책 및 현지 법률 및 규정을 준수…

手撕算法-跳跃游戏

描述 分析 如果某一个作为 起跳点 的格子可以跳跃的距离是 3&#xff0c;那么表示后面 3 个格子都可以作为 起跳点可以对每一个能作为 起跳点 的格子都尝试跳一次&#xff0c;把 能跳到最远的距离 不断更新如果可以一直跳到最后&#xff0c;就成功了 代码 class Solution {…

图论- 最小生成树

一、最小生成树-prim算法 1.1 最小生成树概念 一幅图可以有很多不同的生成树&#xff0c;比如下面这幅图&#xff0c;红色的边就组成了两棵不同的生成树&#xff1a; 对于加权图&#xff0c;每条边都有权重&#xff08;用最小生成树算法的现实场景中&#xff0c;图的边权重…

【测试篇】测试眼里的 BUG

文章目录 如何描述一个bug如何定义 bug 的级别BUG 的生命周期跟开发起争执怎么办&#xff08;高频面试题&#xff09; 如何描述一个bug 一个合格的bug描述应该包含以下几个部分&#xff1a; 发现问题的版本问题出现的环境错误重现的步骤预期行为的描述错误行为的描述其他&…

Python-基础部署

机器没法直接读懂我们写的代码&#xff0c;需要解释解释器作为中间的翻译&#xff0c;把代码转换成字节码在执行 安装python解释器 Download Python | Python.org 安装代码编辑器 pycharm Thank you for downloading PyCharm! 创建一个项目&#xff0c;每个项目里的文件夹…

全套医院手术麻醉系统源码 人工智能麻醉系统源码 医疗管理系统源码

全套医院手术麻醉系统源码 人工智能麻醉系统源码 医疗管理系统源码 手术麻醉临床信息系统有着完善的临床业务功能&#xff0c;能够涵盖整个围术期的工作&#xff0c;能够采集、汇总、存储、处理、展现所有的临床诊疗资料。通过该系统的实施&#xff0c;能够规范麻醉科的工作流…

【Node.JS】koa

文章目录 概述koa和express对比koa下载安装使用1.创建koa项目文件目录2. 创建koa服务3. 添加路由 koa-router4. 数据库服务 mongodb5. 添加请求参数json处理 koa-bodyparser6. 用户接口举例7.引入koa一些常用插件8.用户登录验证 koa-jwt9.webpack生产打包 来源 概述 Koa 是一个…

宝塔面板 -- 打包前端项目并部署提升访问速度

文章目录 前言一、打包前端项目二、添加PHP项目三、部署打包文件四、开通防火墙五、运行网站总结 前言 在前面写到的文章使用宝塔面板部署前端项目中&#xff0c;并没有将前端项目打包而是直接部署&#xff0c;导致网站访问速度非常慢&#xff0c;加载甚至要十几秒。因此&…

Image-Adaptive YOLO for Object Detection in Adverse Weather Conditions(IA-YOLO)

1、总体概述 基于深度学习的目标检测在常规条件的数据集可以获得不错的结果&#xff0c;但是在环境、场景、天气、照度、雾霾等自然条件的综合干扰下&#xff0c;深度学习模型的适应程度变低&#xff0c;检测结果也随之下降&#xff0c;因此研究在复杂气象条件下的目标检测方法…

shell的工作原理

本文旨在讲解shell的工作原理&#xff0c;希望读完本文&#xff0c;能使读者对shell的工作原理有一定的认识&#xff0c;废话不多说&#xff0c;开唠&#xff01; 在讲解shell的工作原理之前&#xff0c;我要首先给大家讲一下什么是操作系统&#xff0c;以Linux操作系统为例&am…

第N6周:使用Word2vec实现文本分类

import torch import torch.nn as nn import torchvision from torchvision import transforms,datasets import os,PIL,pathlib,warnings #忽略警告信息 warnings.filterwarnings("ignore") # win10系统 device torch.device("cuda"if torch.cuda.is_ava…

[flink 实时流基础]源算子和转换算子

文章目录 1. 源算子 Source1. 从集合读2. 从文件读取3. 从 socket 读取4. 从 kafka 读取5. 从数据生成器读取数据 2. 转换算子基本转换算子&#xff08;map/ filter/ flatMap&#xff09; 1. 源算子 Source Flink可以从各种来源获取数据&#xff0c;然后构建DataStream进行转换…

hcia datacom课程学习(5):MAC地址与arp协议

1.MAC地址 1.1 含义与作用 &#xff08;1&#xff09;含义&#xff1a; mac地址也称物理地址&#xff0c;是网卡设备在数据链路层的地址&#xff0c;全世界每一块网卡的mac地址都是唯一的&#xff0c;出厂时烧录在网卡上不可更改 &#xff08;2&#xff09;作用&#xff1a…