【C++】类和对象(四)拷贝构造、赋值运算符重载

news2024/11/27 0:28:46

文章目录

  • 四、拷贝构造函数
    • 干嘛的?
    • 写拷贝构造函数的注意事项
      • 正确写法
    • 不显示定义拷贝构造函数的情况
      • 浅拷贝
        • :one:示例:内置类型
        • :two:示例:自定义类型
        • 一个提问
      • 深拷贝
  • 五、赋值运算符重载
    • 运算符重载
      • 函数原型
      • 注意
      • 调用时的两种书写方式
      • 完整实现代码
    • 赋值运算符重载
      • 干嘛的?
      • 连续赋值
      • 总结赋值运算符重载格式
      • 默认生成的复制重载函数的行为
      • 默认生成的函数行为总结
      • 赋值运算符是否可以重载为全局函数

书接上回: 【C++】类和对象(三)构造与析构

四、拷贝构造函数

干嘛的?

拷贝构造函数:用同类型的其他对象 构造一个(初始化 )新的对象
代码演示:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//Date d2(d1);
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(d1);

	d1.Print();
	d2.Print();

	return 0;
}

写拷贝构造函数的注意事项

注意:拷贝构造函数 参数位置必须要传引用

为什么参数位置必须要传引用?
传值做拷贝构造函数的参数 🆚 传引用做拷贝构造函数的参数

C++规定 调用拷贝构造函数时,
1️⃣自定义类型本身 作为实参传递过去(传值传参),都会先调用拷贝构造。
🌰例如:
在这里插入图片描述
如果传值传参 会发生无穷递归的问题:
在这里插入图片描述
2️⃣自定义类型的引用 作为实参传递过去(传引用传参),就不会先调用拷贝构造。

除此之外,为了 防止写反方向的类似问题,我们一般给 引用前面加const修饰
例如:可能出现赋值方向写反的问题

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//Date d2(d1);
	Date(Date& d)
	{
		//赋值方向写反的问题
		d._year = this->_year;
		d._month = this->_month;
		d._day = this->_day;
		//_year = d._year;
		//_month = d._month;
		//_day = d._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(d1);

	d1.Print();
	d2.Print();

	return 0;
}

写反方向的情况如下:
在这里插入图片描述

正确写法

加入const修饰 保护要赋值给别人的对象

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

不显示定义拷贝构造函数的情况

浅拷贝

程序员不显示定义拷贝构造函数 ,则编译器会自动生成拷贝构造函数。并且

1️⃣ 对内置类型的成员变量进行值拷贝(浅拷贝)。
2️⃣对自定义类型的成员变量 调用它的拷贝构造

1️⃣示例:内置类型

在这里插入图片描述

2️⃣示例:自定义类型
class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
	// 注意:拷贝构造函数 也属于构造函数 编译器就不会自动生成 构造函数
	// 但是下面一句代码 可以强制编译器生成默认构造
	Time() = default;

	Time(const Time& t)
	{
		cout << "Time(const Time& t)" << endl;
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
	}

private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// Date d2(d1);
	/*Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}*/

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	// 内置类型
	int _year;
	int _month;
	int _day;

	// 自定义类型
	Time _t;
};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(d1);

	d1.Print();
	d2.Print();

	return 0;
}

代码演示:对自定义类型的成员变量 调用它的拷贝构造
在这里插入图片描述

一个提问

❓对于内置类型、自定义类型的成员变量,即使程序员不提供拷贝构造函数,编译器都会自动进行拷贝,那么拷贝构造函数是不是就可以不用我们写了呢?

答案当然不是的。在有些情况,编译器默认生成的拷贝构造会出现问题,我们可以看看如下的问题情况。
问题代码:

#include<iostream>
using namespace std;
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 st1;
	Stack st2(st1);
	return 0;
}

在这里插入图片描述
问题描述:经过调试,我们发现 _array的值是一个地址也被拷贝过来 ,这意味着两个对象指向同一块空间。
在这里插入图片描述

出了函数作用域,会对他们析构,把st2对象的 _array空间释放。但st2置空并不影响st1,st1依然指向那块空间,导致st1成为野指针,析构st1时,_array空间再次被释放,相当于同一块空间被释放了两次。

所以,对于动态开辟的内存空间 都要使用深拷贝。深拷贝就是,为要拷贝st1的st2再开辟一块同样大小的新空间。

深拷贝

对于动态开辟的内存空间,必须程序员自己实现深拷贝!
代码如下:

#include<iostream>
using namespace std;
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;
	}
	Stack(const Stack& s)
    {
        DataType* tmp = malloc(sizeof(s._capacity * sizeof(DataType)));
        if(tmp == nullptr)
        {
            perror("malloc fail");
            exit(-1);
        }
        memcpy(tmp , a._array,sizeof(DataType)*s._size);
        _array = tmp;
        _size = s._size;
        _capacity = s._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 st1;
	Stack st2(st1);
	return 0;
}

在这里插入图片描述
可以看到经过深度拷贝的两个指针分别开辟了两块不同的空间。

五、赋值运算符重载

运算符重载

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

函数原型

函数名:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

🌰

bool operator==(const Date& y)
bool operator<(const Date& y)

注意

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

注意:区分 运算符重载 跟 函数重载,二者没有关系。

运算符重载:让自定义类型的对象可以使用运算符。通过函数定义了该运算符的行为

函数重载:允许函数名相同 参数不同的函数存在,通过函数名修饰规则可以找到对应的函数

代码示例🌰
这里展示两个​运算符重载函数 他们是用来 比较自定义对象 日期年月日的大小

调用时的两种书写方式

d1.operator==(d2)
d1 == d2

完整实现代码

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
	// operator运算符 做函数名
	bool operator==(const Date& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}
	// operator运算符 做函数名
	bool operator<(const Date& y)
	
	{
        //如果 年小就小
		if (_year < y._year)
		{
			return true;
		}
        //如果年相等
		else if (_year == y._year)
		{
            //如果月小就小
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}

		return false;
	}

private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1(2024, 1, 28);
	Date d2(2024, 2, 27);
	//通过对象调用成员函数的方式 调用运算符重载函数 比较日期对象d1和d2的大小
	cout << d1.operator==(d2) << endl;
	cout << d1.operator<(d2) << endl;
	//另一种调用方式 加括号的原因:流插入运算符的优先级高于等于号
	cout << (d1 == d2) << endl; // cout << (d1.operator==(d2)) << endl;
	cout << (d1 < d2) << endl;  // cout << (d1.operator==(d2)) << endl;
    
    bool ret1 = d1 < d2;
    bool ret2 = d1.operator<(d2);
    
    int i = 0;
    int j = 1;
    bool ret3 =i<j;

	return 0;
}

在这里插入图片描述
通过反汇编窗口,我们可以看到

对于自定义类型的运算符重载函数的两种调用方式底层的汇编代码都是一样的。
而对于内置类型的运算符比较,是直接有cmp指令支持的,不用程序员自己规定大小比较方式。

赋值运算符重载

干嘛的?

赋值运算符重载:对已经存在的同类型对象,一个拷贝赋值给另一个,就用到了赋值运算符重载

代码示例

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//d2 = d1;
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(1990, 2, 1);
	Date d3(d2);  // 拷贝构造,同类型一个存在的对象进行初始化要创建的对象

	d2 = d1;// 已经存在的对象,一个拷贝赋值给另一个
	d2.Print();
	d3.Print();
	return 0;
}

栗子结果
在这里插入图片描述

连续赋值

int  i = 0, j = 1;
i = j = 10;

注意:

  1. 对于内置类型,10先赋值给j ,表达式的返回值为j ,j再作为下一次赋值的右操作数 以此支持连续赋值。
    同理,对于自定义类型为了支持连续赋值,我们需要拿到表达式的结果,所以在写赋值重载函数时,我们要返回被赋值好的对象 *this。
  2. 函数传值返回会调用拷贝构造函数,为了避免浪费,赋值运算符函数的返回值使用引用返回
  3. 为了防止自己给自己赋值,我们需要加一个判断。

总结赋值运算符重载格式

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

默认生成的复制重载函数的行为

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

默认生成的函数行为总结

在这里插入图片描述

赋值运算符是否可以重载为全局函数

❓运算符重载可以在全局重载,那赋值运算符重载可以在全局重载嘛?

注意: 赋值运算符只能重载成类的成员函数不能重载成全局函数

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

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

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

相关文章

skywalking segment索引占用elasticsearch大量磁盘空间

现象&#xff1a; skywalking segment索引占用elasticsearch大量磁盘空间 原因 recordDataTTL 是SkyWalking的一个配置项&#xff0c;用于设置记录数据的存活时间&#xff08;TTL, Time To Live&#xff09;。SkyWalking是一个开源的应用性能监控系统&#xff0c;用于监控分…

tplink安防监控raw文件转码合成mp4的方法

Tplink(深圳普联)专业的网络设备生产商&#xff0c;属于安防监控市场的后来者。Tplink的安防产品恢复了很多&#xff0c;其嵌入式文件系统也一直迭代更新。今天要说的案例比较特殊&#xff0c;其不仅仅要求恢复&#xff0c;还要求能解析出音频并且要求画面和声音实现“同步”。…

入门Rabbitmq

1、什么是消息队列 消息队列&#xff1a;应用之间传递消息的方式&#xff0c;允许应用程序异步发送和接收消息&#xff0c;不需要连接对方 消息&#xff1a;文本字符串&#xff0c;对象.... 队列&#xff1a;存储数据。先进先出 2、应用场景 ①库存系统挂掉之后 MQ会等待&…

AI PPT生成器,一键在线智能生成PPT工具

PPT作为商业沟通和教育培训中的重要工具&#xff0c;PPT制作对于我们来说并不陌生。但是传统的PPT制作不仅耗时&#xff0c;而且想要做出精美的PPT&#xff0c;需要具备一定的设计技能。下面小编就来和大家分享几款AI PPT工具&#xff0c;只要输入主题&#xff0c;内容就可以在…

本地快速部署大语言模型开发平台Dify并实现远程访问保姆级教程

文章目录 前言1. Docker部署Dify2. 本地访问Dify3. Ubuntu安装Cpolar4. 配置公网地址5. 远程访问6. 固定Cpolar公网地址7. 固定地址访问 前言 本文主要介绍如何在Linux Ubuntu系统使用Docker快速部署大语言模型应用开发平台Dify,并结合cpolar内网穿透工具实现公网环境远程访问…

C# WPF入门学习主线篇(二十八)—— 使用集合(ObservableCollection)

C# WPF入门学习主线篇&#xff08;二十八&#xff09;—— 使用集合&#xff08;ObservableCollection&#xff09; 在WPF中&#xff0c;数据绑定是构建动态和响应式用户界面的关键。ObservableCollection是一个特别有用的集合类型&#xff0c;它不仅支持数据绑定&#xff0c;还…

在Tomcat中部署war包

1、准备war包 确保已经有一个有效的war包&#xff0c;该war包包含了web应用程序的所有内容&#xff1b; 2、停止tomcat服务器 在部署之前&#xff0c;确保tomcat服务器已经停止&#xff0c;进入tomcat的配置目录执行命令&#xff1a;[路径]/tomcat/conf&#xff1b; 在Linux…

windows系统实现应用程序开机即运行(不登录系统也行)

由于近期需要设置一个Java程序开机自启动&#xff0c;因此试了一下方法&#xff0c;总结了两点&#xff0c;一个是需要用户登录系统之后再启动&#xff0c;一种是不需要登录&#xff0c;只要开机就会启动。 先看准备工作&#xff0c;写一个启动脚本&#xff1a; echo on E: cd…

[STM32]万年历

[STM32]万年历 需要资料的请在文章末尾获取~ ​​ 01描述 使用原件&#xff1a;stm32f103c8t6最小系统板x1&#xff0c;0.96寸OLED显示屏四角x1&#xff0c;4x4矩阵按键x1; 键位对应图&#xff1a; 1&#xff0c; 2&#xff0c; 3&#xff0c; 4------------- 切换页面 设置…

Dynamics 365 on-premise 隐藏高级查找导出按钮

提示 着急可以直接看结果代码部分 背景 Dynamics 365 on-premise中有个高级查找的功能,查询的结果支持导出,如下图 业务反馈这个有数据安全风险,要修改显示规则。 一开始想着能用RibbonWorkbench改,就很爽快得答应了业务。结果用RibbonWorkbench改不了。 反复尝试 既…

AI早班车2024.5.21

先一步知道AI未来&#xff01; 全球AI新闻速递 1.中国电信&#xff1a;发布万亿参数语义模型 Tele-FLM-1T。 2.蚂蚁图数据库再获LDBC权威测试世界第一。 3.昇腾 AI 算力性能已超英伟达 A100&#xff0c;近半中国大模型选择昇腾技术路线。 4.中国气象局发布“风清”、“风雷”…

造价信息网工程造价信息最新明细

提供造价信息网工程造价信息、厂商报价市场价&#xff0c;交通工程造价信息&#xff0c;电网工程造价信息&#xff0c;园林苗木绿化造价信息&#xff0c;工程定额免费资源可在 祖国建材通 www.zgjct.com 查询获取下载 造价信息网工程造价信息更新明细如下&#xff1a; 直辖市 …

深入浅出通信原理 | 通信系统模型中的基带信号的发送与接收

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 本节目录 一、通信系统模型中的信…

使用ERNIE Bot SDK和一条核心prompt开发AI“划拳“游戏!

项目背景 划拳是汉族民间饮酒时一种助兴取乐的游戏,起源于汉代。 游戏规则如下&#xff1a; 两人同时伸出一只手&#xff0c;先攥起拳头&#xff0c;代表财不外露。再伸出一到五个手指&#xff0c;表示从零到五之间的数字&#xff0c;同时嘴里大声喊出两人所出数字之和。 两人…

构建智慧高速公路:软件管理平台业务架构解析

随着交通网络的不断完善和技术的快速发展&#xff0c;智慧高速公路正成为交通领域的重要发展方向。在智慧高速公路系统中&#xff0c;软件管理平台扮演着关键的角色&#xff0c;它不仅是管理各种设备和系统的核心&#xff0c;还承担着数据监控、故障诊断、维护管理等重要任务。…

Linux 一键部署Nginx+ModSecurity

前言 ModSecurity 是 Apache 基金会的一个开源、高性能的 Web 应用程序防火墙(WAF),它提供了强大的安全规则引擎,用于检测和阻止各种攻击行为,如 SQL 注入、XSS 跨站点脚本攻击等。而 nginx 是一个高性能的 Web 服务器,常用于处理大量的并发请求,具有很高的负载均衡能力…

智慧在线医疗在线诊疗APP患者端+医生端音视频诊疗并开处方

智慧在线医疗&#xff1a;音视频诊疗新纪元 &#x1f310; 智慧医疗新篇章 随着科技的飞速发展&#xff0c;智慧医疗正逐步走进我们的生活。特别是在线医疗&#xff0c;凭借其便捷、高效的特点&#xff0c;已成为许多患者的首选。而其中的“智慧在线医疗患者端医生端音视频诊疗…

SQLCMD完全指南:掌控 SQL Server

SQL Server 拥有被广泛认可的一流管理工具——SQL Server Management Studio&#xff08;简称 SSMS&#xff09;。它提供了丰富的功能&#xff0c;极大地简化了开发人员和数据库管理员&#xff08;DBA&#xff09;的工作。 目录 SQLCMD 入门使用 SQLCMD 连接 SQL ServerSQLCMD …

[信号与系统]有关滤波器的一些知识背景

前言 最近在看FIR和IIR&#xff0c;本文作为前置&#xff0c;需要在理解这两种滤波器之前阅读。 本文内容会详细讲述一下有关滤波器的技术要求。 选频滤波器的频率响应 选频滤波器&#xff08;Selective Frequency Filter&#xff09;的频率响应是描述该滤波器在不同频率下…

浙江保融科技2025实习生校招校招笔试分享

笔试算法题一共是有4道&#xff0c;第一道是手搓模拟实现一个ArrayList&#xff0c;第二道是判断字符串是否回文&#xff0c;第三道是用代码实现1到2种设计模式。 目录 一.模拟实现ArrayList 二.判断字符串是否回文 ▐ 解法一 ▐ 解法二 ▐ 解法三 三.代码实现设计模式 一…