C++之类和对象--赋值运算符重载和const成员函数

news2024/11/24 2:03:40

目录

1.赋值运算符重载

1.1运算符重载

 1.2赋值运算符重载

1.3其它特性 

2.const成员函数

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


hello,欢迎大家来到小恶魔频道,今天讲解的是C++里面的赋值运算符重载以及const成员函数

1.赋值运算符重载

1.1运算符重载

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

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 4, 22);
	Date d2(2024, 1, 1);
	return 0;
}

像以上代码

在这个代码中,我们如何比较d1和d2的是否相同呢?

常规方法:写一个函数去比较

bool Compare(const Date& dt1,const Date& dt2)
{
	return dt1._year == dt2._year
		&& dt1._month == dt2._month
		&& dt1._day == dt2._day;
}

 运行完后发现为0,也就是不相等

那么如果接下来我们想要直接比较d1==d2

这时候就会运用到运算符重载

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

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

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

函数原型返回值类型 operator操作符(参数列表)

所以这里我们可以改写为:

bool operator==(const Date& dt1,const Date& dt2)
{
	return dt1._year == dt2._year
		&& dt1._month == dt2._month
		&& dt1._day == dt2._day;
}
int main()
{
	Date d1(2024, 4, 22);
	Date d2(2024, 1, 1);
	cout << (d1 == d2) << endl;
	return 0;
}

注意:这里的d1和d2必须用括号括起来,不然会无法运行

这样也是直接成功了,我们可以看一下反义编码去验证一下

 

这里发现调用了operator==,直接进行了函数比较 

但是问题也就来了

这里我们的函数比较是建立在全局变量上的,也就是说我们的函数成员需要变成共有才能够使用函数,既然是共有的成员变量,怎么保证其分装性呢?

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


int main()
{
	Date d1(2024, 4, 22);
	Date d2(2024, 1, 1);
	cout << d1.operator==(d2) << endl;
	cout << (d1 == d2) << endl;
	return 0;
}

在这里我们将函数分装到类的里面,依旧是利用的运算符重载==

只不过这里我们值传递了一个参数,另一个参数我们利用这里隐藏的this指针去实现代码的构建,从而实现==

细说剖析:

  1. 首先呢,值传递一个参数也就是const Date& dt2,它是右边的比较数的参数,而我们的左边比较数利用的是这里面隐藏的this指针,也及时this指针指向的对象。
  2. 这里呢,我们加入了关键字:const,同时也加入了引用传参。加入这两个是为了避免传递值的修改以及避免空间的开辟浪费和传递的效率。
  3. 最后就是bool值,我们这里的函数返回类型利用的是bool值,如果他们的年月日相同就会返回真(true)也就是1不同就会返回假(false)也就是0。

我们是这调用这个函数 看一看

我们再通过反义编码看一看

我们发现这里都调用了operator==函数 

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

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数(自定义类型参数)
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  • 作为类成员的重载函数时其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参
  • .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。

 1.2赋值运算符重载

在这里我们知道拷贝赋值有两种,一种是 拷贝构造一种是拷贝赋值

Date d1(2024,4,22);
Date d2(d1);

这样直接进行拷贝构造

那如果运用运算符重载呢?

d2 = d1;

这两者有什么区别呢?

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

我们在初始化哪里加上一个     

   cout << "Date" << endl;

 运行发现这里只打印了一次Date

也就变向说明了两者的区别

既然运算符重载是将对象的值赋值给另一个对象,我们想一下

可不可以进行连续赋值呢?

比如:a=b=10这种?

在C语言中我们通常是这样的

int a,b;
a = b = 10;

但是在赋值运算符重载中,我们则是需要更新一下自己的方式:返回*this

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

在这里我们的返回类型是Date,没有使用引用,而是进行的传值返回

所以我们这里返回的不是*this,而是他的一个拷贝

可以看到返回this指针时调用了他的拷贝

所以为了加快效率我们这次加入引用

Date& operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}
int main()
{
	Date d1(2024, 4, 22);
	Date d2(1,1,1);
	Date d3 = d2 = d1;
	return 0;
}

这次就没有拷贝 

但是问题来了,如果这里我传参传的是自己呢?如果我给自己赋值会怎么样?

这里是不行的

为什么不行呢?

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

所以这里我们还需要改进以下代码

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

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

1.3其它特性 

这里我们看到报错了

然后我们把成员类型设置为公有的,发现还是报错

这是因为:赋值运算符只能重载成类的成员函数不能重载成全局函数

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

       故赋值运算符重载只能是类的成员函数

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

答案是会的

这里我们把operator=给注释掉后 结果仍旧一样

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

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

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

答案需要的 

如果我们使用的是栈的话

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc fail");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		_array[_size++] = data;
	}
	~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共享同一份内存空间,最后销毁时会导致同一份内存空间释放两次而引起程序崩溃

这里有点类似于我们之前学习的拷贝构造,如果不分开,也是占用的同一块内存,最后会崩溃报错

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

2.const成员函数

假如我们现在定义一个const对象,想访问它的Print函数,我们发现是调用不了的:

class Date
{
public:
	Date(int year, int month, int day)
	{
		//加上这里的打印是为了调试更加清楚
		cout << "Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& dt2)
	{
		return _year == dt2._year
			&& _month == dt2._month
			&& _day == dt2._day;
	}
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
			return *this;
		}
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year = -1;
	int _month = -1;
	int _day = -1;
};

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

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

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

class Date
{
public:
	Date(int year, int month, int day)
	{
		//加上这里的打印是为了调试更加清楚
		cout << "Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& dt2)
	{
		return _year == dt2._year
			&& _month == dt2._month
			&& _day == dt2._day;
	}
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
			return *this;
		}
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print() const
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year = -1;
	int _month = -1;
	int _day = -1;
};

 我们在Print函数后加上const

这样就可以了

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

可以的,这里是权限的缩小 

请思考下面的几个问题:

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

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

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

这个我们主要是了解,不作为重点讲解,后边用到会更新讲解

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

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

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

这里是默认成员函数,我们删去这两个函数照样可以取地址 

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

		_year = year;
		_month = month;
		_day = day;
	}
	//Date* operator&()
	//{
	//	return this;
	//}
	//const Date* operator&()const
	//{
	//	return this;
	//}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1; 
};
void main()
{
	Date d1;
	const Date d2;
	cout << &d1 << endl;
	cout << &d2 << endl;
}

这里,我们没有必要深究这个东西究竟有什么用,我们只进行简单的语法了解即可

看到这里,一键三连支持一下呗。

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

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

相关文章

CentOS-7安装grafana

一、通用设置&#xff08;分别在4台虚拟机设置&#xff09; 1、配置主机名 hostnamectl set-hostname --static 主机名2、修改hosts文件 vim /etc/hosts 输入&#xff1a; 192.168.15.129 master 192.168.15.133 node1 192.168.15.134 node2 192.168.15.136 node33、 保持服…

uniapp微信小程序(商城项目)

最近&#xff0c;闲来无事&#xff0c;打算学一下uniapp小程序 于是在跟着某站上学着做了一个小程序&#xff0c;主要是为了学uniapp和vue。某站黑马优购 完成的功能主要有&#xff1a;首页、搜索、分类和购物车。 有人问了为什么没有登录、和添加订单呢&#xff1f;问的很好…

canvas 学习

最近的项目涉及到 canvas 相关的知识&#xff0c;就在网站上找资源先大概了解一下&#xff0c;然后再细细研究。 看到了一篇 “canvas详细教程” 的资源&#xff0c;感觉十分不错&#xff0c;就分享给大家&#xff1a; canvas详细教程! ( 近1万字吐血总结)这期是潘潘整理的万…

input的type=‘radio‘设置只读属性颜色为灰色,如何修改

目录 1.设置input和label的样式为不可点击。 2.设置input的readonly属性。 3.若想变回可修改&#xff0c;用js实现 4.如何自定义radio的颜色。 5.完整代码 input的单选框有时候需要实现只读&#xff0c;两个办法&#xff0c;一个disabled&#xff0c;一个是readonly. 但d…

面试十七、list和deque

一、 Deque Deque容器是连续的空间&#xff0c;至少逻辑上看来如此&#xff0c;连续现行空间总是令我们联想到array和vector,array无法成长&#xff0c;vector虽可成长&#xff0c;却只能向尾端成长&#xff0c;而且其成长其实是一个假象&#xff0c;事实上(1) 申请更大空间 (…

用户体验至上:独立站脱颖而出的关键要素解析

在数字化时代&#xff0c;独立站成为了许多品牌和企业展示自身形象、推广产品、建立客户联系的重要平台。然而&#xff0c;要想在众多的独立站中脱颖而出&#xff0c;吸引并留住用户&#xff0c;良好的用户体验至关重要。本文Nox聚星将和大家探讨如何做好独立站的用户体验&…

Rust实战 | 用 RustRover 开发猜数字游戏

#1、概述 从这期开始&#xff0c;我会以实际项目的形式&#xff0c;分享个人学习 Rust 语言的过程。期间&#xff0c;我不会讲太多语法知识&#xff0c;需要各位看官自行查阅文档&#x1f604;。 开始前&#xff0c;需具备 Rust 环境&#xff08;我用的是当前最新版本1.77.2&…

Syncovery for Mac v10.14.3激活版:文件备份和同步工具

Syncovery for Mac是一款高效且灵活的文件备份与同步工具&#xff0c;专为Mac用户设计&#xff0c;旨在确保数据的安全性和完整性。该软件支持多种备份和同步方式&#xff0c;包括本地备份、网络备份以及云备份&#xff0c;用户可以根据实际需求选择最合适的方案。 Syncovery f…

20240416,深拷贝浅拷贝,对象初始化和清理,对象模型和THIS指针

哈哈哈乌龟越狱了 目录 2.5 深拷贝&浅拷贝 2.6 初始化列表 2.7 类对象作为类成员 2.8 静态成员 2.9 成员变量和成员函数分开存储 2.10 THIS指针的用途 2.11 空指针访问成员函数 2.12 COSNT修饰成员函数 2.5 深拷贝&浅拷贝 浅拷贝&#xff1a;简单的赋值拷贝…

12、【装饰器模式】动态地为对象添加新功能

你好&#xff0c;我是程序员雪球。 今天我们来聊聊 23 种设计模式中&#xff0c;一种常见的结构型模式&#xff0c;装饰器模式。聊聊它的设计思想、实现原理&#xff0c;应用场景&#xff0c;以及如何使用。 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型…

智慧水务是什么样的?如何打造智慧水务大屏?

在信息化和数字化快速发展的今天&#xff0c;智慧水务作为城市供水管理的重要组成部分&#xff0c;正变得越来越重要。智慧水务大屏作为智慧水务系统的可视化核心&#xff0c;不仅提升了水务管理的效率&#xff0c;而且通过数据的实时监控和分析&#xff0c;为决策者提供了强有…

揭秘爬虫技术:助你打开网络数据的大门

在当今信息爆炸的时代&#xff0c;网络上蕴藏着各种宝贵的数据资源&#xff0c;而要想获取这些宝藏&#xff0c;爬虫技术无疑是最为有效的利器之一。今天我将向大家深入探讨爬虫技术的奥秘&#xff0c;并带领大家一起走进这个数据世界的大门。 文章目录 什么是爬虫技术&#xf…

垃圾焚烧发电:从国资到专业公司,运营模式新变革|中联环保圈

近日&#xff0c;云南富源县生活垃圾焚烧发电项目运营管理技术服务招标引发广泛关注&#xff0c;与此同时&#xff0c;众多垃圾焚烧发电项目也纷纷启动了运管工作的招标。值得注意的是这些项目的招标人均为当地国资&#xff0c;且其中多数缺乏项目的运营经验。 在垃圾焚烧发电行…

4个在ArcGIS中可加载的图源分享

数据是GIS的血液。 这里分享4个来自网友分享的图源&#xff0c;该图源可以直接在ArcGIS中打开进行查看。 4个图源分享 你可以先打开ArcMap软件&#xff0c;然后将以下文分拖放到软件中打开即可查看。 4个图源 这4个图源分别为有地名和路网的地形地图、电子地图、有地名和路…

mysql-connector 交叉编译

1.下载 官网选择对应的系统以及版本&#xff0c;这里我用的是6.1.5https://downloads.mysql.com/archives/c-c/ 2.解压 tar -zxvf mysql-connector-c-6.1.5-src.tar.gz 3.先常规编译&#xff08;因为交叉编译的过程中&#xff0c;会用到生成的二进制文件&#xff09; cd m…

Llama3-8B+ LLaMA-Factory 中文微调

Llama3-8B LLaMA-Factory 中文微调 Llama3是目前开源大模型中最优秀的模型之一&#xff0c;但是原生的Llama3模型训练的中文语料占比非常低&#xff0c;因此在中文的表现方便略微欠佳&#xff01; 本教程就以Llama3-8B-Instruct开源模型为模型基座&#xff0c;通过开源程序LL…

深度相机(3D相机)

传统的RGB彩色相机称为2D相机&#xff0c; 只能得到2D的图像信息&#xff0c; 无法得到物体与相机的距离信息&#xff0c;也就是深度信息。 顾名思义&#xff0c; 深度相机除了获取2D信息&#xff0c;还能得到深度信息&#xff0c;也叫RGBD相机&#xff0c; 或3D相机。 顺便提…

人像摄影姿势入门,人像拍照姿势详解

一、资料前言 本套人像摄影资料&#xff0c;大小537.44M&#xff0c;共有17个文件。 二、资料目录 《如何摆出好POSE》.黑面.影印版.pdf 《人像摄影姿势入门》.pdf 《人像摄影技巧》.pdf 《人像摄影摆姿指南》.比尔.赫特尔.扫描版.pdf 《人像摄影摆姿全集》龙文摄影.彩图…

三羟甲基氨基甲烷(TRIS)应用场景广泛 苏州亚科科技是主要供应商

三羟甲基氨基甲烷&#xff08;TRIS&#xff09;应用场景广泛 苏州亚科科技是主要供应商 三羟甲基氨基甲烷&#xff08;TRIS&#xff09;是一种有机化合物&#xff0c;外观为白色晶体&#xff0c;溶于水。三羟甲基氨基甲烷分子结构中含有一个氮原子和三个羟基&#xff0c;反应活…

C语言指针+-整数、指针-指针、指针关系运算、指针和数组、二级指针、指针数组

文章目录 前言一、指针 - 整数二、指针 - 指针三、指针的关系运算四、指针和数组五、二级指针六、指针数组指针数组可以将几个一维数组模拟成二维数组 总结 前言 C语言指针整数、指针-指针、指针关系运算、指针和数组、二级指针、指针数组等介绍&#xff0c;还包括指针数组将几…