【C++杂货铺】拷贝构造函数

news2024/11/27 12:46:47

在这里插入图片描述

📖定义
拷贝构造函数是构造函数的一个重载,它的本质还是构造函数,那就意味着,只有在创建对象的时候,编译器才会自动调用它,那他和普通的构造函数有什么区别呢?

拷贝构造函数,是创建对象的时候,用一个已存在的对象,去初始化待创建的对象。简单来说,就是在我们创建对象的时候,希望创建出来的对象,和一个已存在的对象一模一样,此时就应该用拷贝构造函数,而不是普通的构造函数。拷贝构造函数有一点类似于克隆技术。
在这里插入图片描述

Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//会去调用拷贝构造函数

int a = 10;
int b = a;//不会调用拷贝构造

上面代码,首先定义了一个日期类对象d1,接着想创建第二个日期类对象d2,并且希望d2d1一模一样,也就是用d1去克隆出d2d2相当于是d1的一份拷贝。所以在创建d2对象的时候,参数列表直接传递了d1

小Tips:拷贝构造函数是针对自定义类型的,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数。内置类型不涉及拷贝构造函数,如上,用a去创建b,是由编译器直接把a所表示的空间中的内容直接拷贝到b所表示的空间,并不涉及拷贝构造函数。

📖拷贝构造函数的错误写法
有了上面的分析,可能很多朋友会觉得,那我直接在类里面再写一个构造函数,把它的形参设置成日期类对象,不就行了嘛,于是便得到了下面的代码:

Data(Data d)//错误的拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

是不是觉得很简单?创建d2对象的时候,实参把d1传过来,然后用d接收,最后再把d的所有值赋值给this指针(当前this指针就指向d2),这一切堪称完美,但是我想告诉你,这种写法是大错特错的。

📖为什么是错的
问题出现在传参,就是实参d1传递给形参d的时候,上面代码中的形参d,既不是指针也不是引用,说明是值传递,值传递就意味着,形参d是实参d1的一份拷贝,注意:是拷贝,就是说,形参d要和实参d1一模一样,怎么才能让dd1一摸一样?调用拷贝构造函数呀。
在这里插入图片描述

形参d在接收实参d1的时候,又要去调用拷贝构造来创建d,这次调用拷贝构造,又会有一个形参d,这个形参d又需要调用拷贝构造才能创建,相信到这里,小伙伴们已经看出问题所在了———无穷递归,形参在接收的时候,会无穷无尽的去调用拷贝构造函数,就像套娃一样。
在这里插入图片描述
为了避免出现这种无穷递归,编译器会自行检查,如果拷贝构造函数的形参是值传递,编译时会直接报错。

在这里插入图片描述

📖必须是引用
为了打破上面的魔咒,拷贝构造函数的形参只能有一个,并且必须是类类型对象的引用。下面才是正确的拷贝构造函数:

Data(Data& d)//正确的拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//

此时创建d2的时候,传递d1调用拷贝构造函数,形参d是一个日期类的引用,因为引用时区别名,意味着dd1的一个别名,此时就不会再去无穷无尽的调用拷贝构造啦。

📖建议加const
因为存在用一个const对象去初始化创建一个新对象这种场景,所以建议在拷贝构造函数的形参前面加上const,此时普通的对象能用,const对象也能用。

Data(const Data& d)//正确的拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
const Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);

📖编译器生成的拷贝构造干了什么?
上一节提到,拷贝构造是一种默认成员函数,我们不写编译器会自动生成。编译器生成的默认拷贝构造函数,对内置类型按照字节方式直接拷贝(也叫值拷贝浅拷贝),对自定义类型是调用其拷贝构造函数完成拷贝

class Time//定义时间类
{
public:
	Time()//普通构造函数
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)//拷贝构造函数
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private://成员变量
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

在这里插入图片描述

在这里插入图片描述
📖什么是浅拷贝
上面提到,编译器生成的拷贝构造函数,会对内置类型完成浅拷贝,浅拷贝就是以字节的方式,把一个字节里的内容直接拷贝到另一个字节中。
在这里插入图片描述

📖拷贝构造函数可以不写嘛?
通过上面的分析可以得出:编译器自己生成的构造函数对内置类型和自定义类型都做了处理。那是不是意味着我们就可以不写拷贝构造函数了呢?答案是否定的,对于日期类,我们确实可以不写,用编译器自己生成的,但是对于一些需要深拷贝的对象,构造函数是非写不可的。栈就是一个典型的需要我们自己写构造函数的例子

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(s1);
return 0;
}

上面定义了一个栈类Stack,我们没有写它的拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,栈中的成员变量都是内置类型,默认的拷贝构造函数会对这三个成员变量都完成值拷贝(浅拷贝)。
在这里插入图片描述
此时浅拷贝的问题在于:对象s1和对象s2中的_array存的是同一块空间的地址,他俩指向了同一块空间,当程序退出,往s1s2中的任意一个对象push值,另一个也会跟着改变。s1s2要销毁,s2先销毁,s2销毁时调用析构函数,已经将0X11223344这块空间释放了,但是s1并不知道,到s1销毁的时候,会将0X11223344这块空间再释放一次,一块内存空间多次释放,最终就会导致程序崩溃。

📖深拷贝
通过上面的分析可以看出,简单的浅拷贝不能满足栈的需求,因此,对于栈,我们需要自己写一个拷贝构造函数,来实现深拷贝,深拷贝就是去堆上重新申请一块空间,把s1_array指向的空间中的内容,拷贝到新申请的空间,再让s2中的_array指向该空间。
在这里插入图片描述

//自己写的拷贝构造函数,实现深拷贝
Stack(const Stack& st)
{
	DataType* tmp = (DataType*)malloc(sizeof(DataType) * st._capacity);
	if (nullptr == tmp)
	{
		perror("malloc申请空间失败");
		return;
	}
	memcpy(tmp, st._array, sizeof(DataType) * st._size);
	_array = tmp;
	_size = st._size;
	_capacity = st._capacity;
}

📖总结:
类中如果没有涉及资源申请时,拷贝构造函数写不写都可以;一旦涉及到资源申请时,拷贝构造函数是一定要写的,否则就是浅拷贝,最终析构的时候,就会释放多次,造成程序崩溃。

📖拷贝构造函数典型的调用场景:

  • 使用已存在对象创建新对象。
  • 函数参数类型为类类型对象。
  • 函数返回值为类类型对象。
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		cout << "调用构造函数:" << this << endl;
		cout << endl;
		_year = year;
		_month = month;
		_day = day;
	}

	Data(const Data& d)
	{
		cout << "调用拷贝构造:" << this << endl;
		cout << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	~Data()
	{
		cout << "~Data()" << this << endl;
		cout << endl;
	}
private:
	int _year;
	int _month;
	int _day;

	//可以不用写析构,因为全是自定义类型,并且没有动态申请的空间,这三个成员变量会随着对象生命周期的结束而自动销毁
};
Data Text(Data x)
{
	Data tmp;
	return tmp;
}

int main()
{
	Data d1(2023, 4, 29);
	Text(d1);
	return 0;
}

在这里插入图片描述
📖总结:
自定义类型在传参的时候,形参最好用引用来接收,这样可以避免调用拷贝构造函数,尤其是深拷贝的时候,会大大的提高效率,函数返回时,如果返回的对象在函数栈帧销毁后还在,最好也用引用返回。


🎁结语:
 今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!
在这里插入图片描述

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

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

相关文章

Ubuntu系统开发环境搭建和常用软件

目录 安装PHP7.3 安装MySQL5.7 安装Nginx 配置Nginx支持PHP 安装Jetbrains全家桶 将程序加入到桌面和收藏夹 安装Navicat15 安装 redis和客户端工具 截图工具 终端修改 其它软件 当前我的系统是Ubuntu22.04&#xff1a; 安装PHP7.3 如果使用 apt install php 默认应…

一文讲透 Redis 事务 (事务模式 VS Lua 脚本)

准确的讲&#xff0c;Redis 事务包含两种模式 : 事务模式 和 Lua 脚本。 先说结论&#xff1a; Redis 的事务模式具备如下特点&#xff1a; 保证隔离性&#xff1b; 无法保证持久性&#xff1b; 具备了一定的原子性&#xff0c;但不支持回滚&#xff1b; 一致性的概念有分歧…

BI-SQL丨XML PATH

XML PATH 在SQL Server中&#xff0c;XML数据类型的应用范围是非常宽泛的&#xff0c;除了可以使用value和nodes处理一行拆多行的情况&#xff0c;我们还可以使用PATH处理多行合并成一行。 使用实例 例子&#xff1a;使用PATH处理多行合并成一行。 创建一张表&#xff0c;表…

在vsCode 中执行Electron 项目时,出现中文乱码问题

问题&#xff1a;vscode 中执行Electron 项目时&#xff0c;控制台出现乱码 解决方法&#xff1a; 在 terminal 修改编码格式&#xff1a;65001代表UTF-8&#xff0c;936代表GBK

freeswitch的mod_xml_cdr模块

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 在语音呼叫的过程中&#xff0c;话单是重要的计价和结算依据&#xff0c;话单的产生需要稳定可靠&#xff0c;可回溯。 fs中的mod_xml_cdr模块提供了基本话单功能之外的选择&#xff0c;可以输出XML格式的本地话单或通…

arm day4

.text .global _start _start: /**********LED1点灯**************/bl rcc_initbl led_initbl led1_initbl led2_initloop:bl led_onbl delay_1sbl led_offbl delay_1sbl led1_onbl delay_1sbl led1_offbl delay_1sbl led2_onbl delay_1sbl led2_offbl delay_1sb looprcc_init…

数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)

目录 算法概述 图示 伪代码 选主元 子集划分 小规模数据的处理 算法实现 算法概述 图示 快速排序和归并排序有一些相似&#xff0c;都是用到了分而治之的思想&#xff1a; 伪代码 通过初步的认识&#xff0c;我们能够知道快速排序算法最好的情况应该是&#xff1a; 每…

前端 | ( 九)尚品汇实操练习 | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 文章目录 &#x1f4da;顶部导航条&#x1f4da;头部&#x1f4da;主导航&#x1f4da;内容区_侧边导航&#x1f4da;内容区_侧边二级菜单⭐️&#x1f4da;内容区_右侧尚品快报&am…

docker安装mysql8.0+

文章目录 1.docker仓库找到需要的镜像版本2.安装Mysql镜像3.创建mysql配置文件4.创建mysql容器并运行5.建立软连接6.开放3306端口7.登录mysql8.修改mysql密码9.查看mysql日志10.重启mysql10.外部如何访问mysql 1.docker仓库找到需要的镜像版本 镜像仓库 2.安装Mysql镜像 找到所…

Redis九种数据类型及其持久化机制:探索数据存储的奇妙世界

目录 一、9种数据类型 3.1 Key操作 3.1.1 相关命令 练习&#xff1a; 3.2 String 3.2.1 结构图 3.2.2 相关命令 练习&#xff1a; 3.3 List(双向的链表) 3.3.1 结构图 3.3.2 相关命令 练习&#xff1a; 3.4 Set&#xff08;无序集合&#xff09; 3.4.1 结构图 3.4…

【GeoDa实用技巧100例】010:制作平滑地图

文章目录 一、平滑地图介绍二、加载实验数据三、平滑地图制作四、注意事项一、平滑地图介绍 平滑地图(Smooth,或称滑动平均地图)是以“平滑”的观测值(简称平滑值),而非实际的观测值编制的地图。某个地域单位的所谓平滑值是指该地域单位与周围地区观测值的平均值。地图平滑化…

精通正则表达式 - 打造高效正则表达式

目录 一、典型示例 1. 稍加修改——先迈最好使的腿 2. 效率 vs 准确性 3. 继续前进——限制匹配优先的作用范围 4. “指数级”匹配 二、全面考察回溯 1. 传统 NFA 的匹配过程 2. POSIX NFA 需要更多处理 3. 无法匹配时必须进行的工作 4. 看清楚一点 5. 多选结构的代…

zabbix 企业级监控 (5) Zabbix监控nginx

目录 简介 配置yum仓库 Web Zabbix端添加主机 启用之前自动发现的规则及动作 简介 nginx在生产环境中的应用越来越广泛&#xff0c;所以需要对nginx的性能状态做一些监控来发现出来出现的问题。zabbix监控nginx&#xff0c;首先确认nginx的监控指标&#xff0c;主要有&#…

HTML中常用的标签

注释标签&#xff1a;<!--内容--> 标题标签&#xff1a;<h1></h1>;<h2></h2>;<h3></h3>;<h4></h4>;<h5></h5>;<h6></h6> 段落标签&#xff1a;<p></p> 没有<p></p>时…

数据备份和恢复练习

创建数据库db create database db&#xff1b; 创建student和score表并插入数据 mysql> select *from student-> ; --------------------------------------------------------------- | id | name | sex | birth | department | address | ----…

qt 5.12.6配置 msvc2015 32bit

qt 5.12.6配置 msvc2015 32bit 1.添加临时档案库2.安装 msvc20153. 配置 qmake 环境4.修改系统环境变量5.问题修改1.qt没有被正确的安装,请运行make install2.QT编译出错&#xff1a;rc不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。3.QT License check fai…

【iOS】—— block,KVC,KVO,Category等问题解答

文章目录 block1.block的原理是怎样的&#xff1f;本质是什么&#xff1f;2.__block的作用是什么&#xff1f;有什么使用注意点&#xff1f;3.block的属性修饰词为什么是copy&#xff1f;使用block有哪些使用注意&#xff1f;4.block在修改NSMutableArray&#xff0c;需不需要添…

使用langchain与你自己的数据对话(一):文档加载与切割

LangChain是一个基于大语言模型&#xff08;如ChatGPT&#xff09;用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互&#x…

简单理解TCP,UDP,HTTP

我们都知道TCP、UDP、HTTP内部有很复杂的过程&#xff0c;很多人没办法理解的那么深&#xff0c;只想知道这是个什么鬼。 1、TCP、UDP、HTTP 是什么? TCP/IP是个协议组&#xff0c;可分为三个层次&#xff1a;网络层、传输层和应用层。在网络层有IP协议、ICMP协议、ARP协议、…

50 Matplotlib Visualizations, Python实现,源码可复现

详情请参考博客: Top 50 matplotlib Visualizations 因编译更新问题&#xff0c;本文将稍作更改&#xff0c;以便能够顺利运行。 0 Introduction 新建项目文件夹为matplotlib_visualizations&#xff0c;以下所有的.py文件均默认在该位置。 0.2 Setup Setup.py文件内容如下&…