【C++从练气到飞升】04---拷贝构造函数

 

 🎈个人主页:库库的里昂
收录专栏:C++从练气到飞升
🎉鸟欲高飞先振翅,人求上进先读书

目录

⛳️推荐

一、拷贝构造函数的引入

1. 以日期类为例:进行的值拷贝是不会发生错误的

2. 以栈类为例:进行的值拷贝会发现发生错误

二、拷贝构造函数

1. 拷贝构造函数的概念

2. 拷贝构造函数的特征

3. 拷贝构造函数针对自定义类型,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数

4. 拷贝构造函数的典型调用场景


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站

一、拷贝构造函数的引入

对于上章节的学习我们认识并了解了两大默认成员函数:构造函数和析构函数。构造函数主要用来进行对象的成员变量初始化操作,而析构函数主要用来对战斗后的战场做清理工作。当我们不写这些函数时,编译器会自动生成默认的构造与析构函数,但有时候,编译器生成的并不能满足我们对代码的需求,这就需要我们自己去写了(比如Stack类),所以要根据情况的不同而去选择性的写。
此外就引入一个问题,假设我们需要创建一个对象和已经存在的对象一模一样那应该怎么办呢?显然易见的答案就是拷贝,但真的只是简简单单的拷贝吗?通过对比如下两种类的拷贝:

1. 以日期类为例:进行的值拷贝是不会发生错误的

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream> 
#include<assert.h>
using namespace std;
class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Printf()
	{
		cout << _year<<"/" << _month << "/" << _day << endl;
	}
private:
	int _year = 1;
	int _month;
	int _day;
};

void func1(Date d)
{
	d.Printf();
}


int main()
{
	Date d1(2023, 9, 12);
	func1(d1);
	return 0;
}

2. 以栈类为例:进行的值拷贝会发现发生错误

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream> 
#include<assert.h>
using namespace std;

class Stack
{
public:
	Stack(size_t n = 4)
	{
		cout << "Stack(size_t n=4)" << endl;
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a)
			{
				cout << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
	int Top()
	{
		return a[top - 1];
	}
	void Pop()
	{
		assert(top > 0);
		--top;
	}
	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
	bool Empty()
	{
		return top == 0;
	}
private:
	int* a;
	int top;
	int capacity;
};


void func2(Stack s)
{
	
}

int main()
{
	Stack s1;
	func2(s1);
	return 0;
}

报错原因

🌟同样的拷贝方式为什么对于日期类不会报错,而对于栈类就会报错呢?

解决方式:

  • 采用引用
void func2(Stack& s)
{
	引用没有值拷贝的问题,s就是s1别名,没有两个对象指向同一块空间的这种说法(这是一个对象)
}

🌟这里有一个误解:采用引用它不是也会析构吗? —> 同一个对象不会析构两次,s是s1的别名,s不析构,不调用析构函数

但是采用引用,s的修改也会影响s1,那如何让s改变且不影响s1?这就需要引入拷贝构造函数

二、拷贝构造函数

1. 拷贝构造函数的概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

2. 拷贝构造函数的特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  • 🌏拷贝构造函数是构造函数的一个重载形式。
  • 🌏拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
#include<iostream> 
using namespace std;
class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		cout << "Date(Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Printf()
	{
		cout << _year<<"/" << _month << "/" << _day << endl;
	}
private:
	int _year = 1;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	以下两种写法是等价的
	Date d2(d1);  调用拷贝构造
	Date d3 = d1;   调用拷贝构造
	定义了一个日期类对象d1,然后想再创建一个和d1一模一样的日期类对象d2,也就是用d1去拷贝d2
	return 0;
}

😽 注意—>拷贝构造的错误写法:引发无穷递归

对于下述代码按常规理解就是创建d2对象的时候,把d1传过去,然后用形参d接收,再把d的值赋值给this指针(this指针指向的是d2,也就是赋值给了d2)并不会发现有任何错误

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

其实编译器是会报错因为底层发生了错误

😽根据下述图解来探索一下引发无穷递归的原因

如上图所示,执行date d2(d1);调用拷贝构造函数, d1传参给拷贝构造的形参d, 形参d在接收实参d1的时候,又要去调用拷贝构造来创建d,所以会出现 date d(d1),而拷贝的过程中又会调用自身的拷贝构造函数,会无休止的递归下去。


😽 对于上述的错误可以采用下述方法进行规避:
采用引用的方法:Date d2(d1);调用拷贝构造,d1传给了d,且d是d1的别名,this指针就是d2,这样d1就拷贝给了d2,此时就不会再去无穷无尽的调用拷贝构造

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

3. 拷贝构造函数针对自定义类型,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数

  • 🌏若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
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;
}

😽注意:建议写拷贝构造函数时加上const

Date(const Date& d)//d是d1的别名,权限缩小
	{
		cout << "Date(Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

表明拷贝构造函数中没有对传递进来的对象做任何修改,也是防止拷贝构造函数对对象进行修改,实际上不加const也是可以照常运行的。不过还是建议:不需要改变对象时,传引用时加上const

  • 🌏编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类(浅拷贝)这样的类是没必要的。但是对于栈类(深拷贝)的对象,是必须要显示写的,不然会出现析构两次的问题
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;
}
 
 

  • s1对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间然后里面存了4个元素12 3 4
  • s2对象使用s1拷贝构造,而Stack类没有显式定义拷贝构造函数,则编译器会给Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是按照值拷贝的,即将s1中内容原封不动的拷贝到s2中。因此s1和s2指向了同一块内存空间。
  • 当程序退出时,s2和s1要销毁。s2先销毁,s2销毁时调用析构函数,已经将0x11223344的空间释放了,但是s1并不知道,到s1销毁时,会将0x11223344的空间再释放一次,一块内存空间多次释放,肯定会造成程序崩溃。

😽注意:
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
😽综上所述栈类是要自己写拷贝构造的具体如下:

深拷贝就是去堆上重新申请一块空间,把s1中_array指向的空间中的内容,拷贝到新申请的空间,再让s2中的_array指向该空间

	Stack(const Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
		//深拷贝
		_array = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_array, s._array, sizeof(DataType)*s._size );
		_size = s._size;
		_capacity = s._capacity;
	}

😽总结:
我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性不一样:

  • 内置类型,值拷贝(浅拷贝)
  • 自定义的类型,调用他的拷贝
  • Date不需要我们实现拷贝构造,默认生成就可以用

  • Stack需要我们自己实现深拷贝的拷贝构造,默认生成会出问题(析构两次)

4. 拷贝构造函数的典型调用场景

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
class Date
{
public:
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Test(Date d)
{
	Date temp(d);
	return temp;
}
int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

   

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。


  •  

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

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

相关文章

AI论文速读 |(Mamba×时空图预测!) STG-Mamba:通过选择性状态空间模型进行时空图学习

&#xff08;来了来了&#xff0c;虽迟但到&#xff0c;序列建模的新宠儿mamba终于杀入了时空预测&#xff01;&#xff09; 论文标题&#xff1a;STG-Mamba: Spatial-Temporal Graph Learning via Selective State Space Model 作者&#xff1a;Lincan Li, Hanchen Wang&…

Java中的I/O讲解(超容易理解)(中篇)

如果想观看更多Java内容 可上我的个人主页关注我&#xff0c;地址 子逸爱编程-CSDN博客https://blog.csdn.net/a15766649633?spm1000.2115.3001.5343 使用工具 IntelliJ IDEA Community Edition 2023.1.4 使用语言 Java8 代码能力快速提升小方法&#xff0c;看完代码自己…

学几招静态路由配置技巧,让你事半功倍!

中午好&#xff0c;我的网工朋友。 静态路由是在网络设备上手动配置的路由信息&#xff0c;用于指定数据包的传输路径。 无论是项目中交换机的静态路由配置&#xff0c;还是在公司网络中路由器的静态路由设置&#xff0c;都有非常多的应用。 与动态路由协议不同&#xff0c;…

java每日一题——幸运囚犯(合集遍历,查询数据练习)

前言&#xff1a; 合集基本学完了&#xff0c;做做题巩固下知识点。打好基础&#xff0c;daydayup! 题目如下&#xff1a; 目前有100名囚犯&#xff0c;每个囚犯的编号是1-200之间的随机数。现在要求依次随机生成100名囚犯的编号&#xff08;要求这些囚犯的编号是不能重复的&a…

DCDC电源管理芯片MC34063A,内含温度补偿的参考电压源(1.25V)、比较器、能有效限制电流及控制工作周期的振荡器,驱动器及大电流输出开关管等

MC34063A 为一单片 DC-DC 变换集成电路&#xff0c;内含温度补偿的参考电压源&#xff08;1.25V&#xff09;、比较器、能有效限制电流及控制工作周期的振荡器&#xff0c;驱动器及大电流输出开关管等。外配少量元件&#xff0c;就能组成升压、降压及电压反转型 DC-DC 变换器。…

基于net的医院病历管理系统

摘 要 伴随着我国社会的发展&#xff0c;人民生活质量日益提高。互联网逐步进入千家万户&#xff0c;改变传统的管理方式&#xff0c;医院病历管理系统以互联网为基础&#xff0c;利用net技术&#xff0c;和SQL Server数据库开发设计一套医院病历管理系统&#xff0c;提高工作…

C++第九弹---类与对象(六)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 日期类 1、日期类的分析和设计 1.1、日期类的功能说明 1.2、日期类的分析和设计 1.2.1、数据结构的分析 1.2.2、文件结构设计 2、日期类的结构分析…

Blender 3D建模要点

3d模型可以为场景的仿真模拟带来真实感&#xff0c;它还有助于更轻松地识别场景中的所有内容。 例如&#xff0c;如果场景中的所有对象都是简单的形状&#xff0c;如立方体和圆形&#xff0c;则很难在仿真中区分对象。 1、碰撞形状与视觉形状 像立方体和球体这样的简单形状&a…

火灾自动报警及消防联动控制系统主机的九个主要组成部分

关于火灾报警联动系统的主机组成&#xff0c;一般有两种不同的概括&#xff0c;下面分别讨论。 一&#xff1a; 火灾报警主机的组成部分较多&#xff0c;主要包括以下消防设备&#xff1a;主电源、联动电源、打印机、驱动器、直接控制板、总线控制板、消防广播、消防电话主机…

免费Web应用防火墙:uuWAF

一款国产的由社区驱动的免费、高性能、高扩展顶级Web应用安全防护产品-南墙。南墙 WEB应用防火墙&#xff08;简称&#xff1a;uuWAF&#xff09;是有安科技推出的一款全方位网站防护产品。通过有安科技专有的WEB入侵异常检测等技术&#xff0c;结合有安科技团队多年应用安全的…

多功能、功耗低。工作温度范围宽(-40℃~+80℃),性价比高,并可与MAXIM、AD等公司的uP监控产品兼容的国产芯片——D706

概 述 近年来&#xff0c;微处理器在IT业控制领域和智能化产品中得到了广泛的应用。在系统和产品的开发设计过程中&#xff0c;为了提高其抗干扰能力&#xff0c;使用uP监控是首选技术措施之一。监控芯片可为系统提供上电、掉电复位功能&#xff0c;也可提供其它功能&#x…

【感悟《剑指offer》典型编程题的极练之路】01数组篇!

​​​​​​​ ​​​​​​​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ ​​​​​​​ 文章所属专栏&#xff1a;《剑指offer》典型编程题的极练之路 ​​​​​​​ ​​​​​​​ …

CSS其他属性

文章目录 1. vertical-align1.1. 概念1.2. 常用值1.3. 作用1.4. 出现的情况一1.4.1. 原因1.4.2. 解决方案 1.5. 出现情况二1.5.1. 解决方案一1.5.2. 解决方案二1.5.3. 解决方案三 1.6. 出现情况三1.6.1. 原因1.6.2. 解决方案 2. 溢出效果2.1. 作用2.2. 属性名 3. 隐藏效果3.1. …

买卖股票的最佳时机1,2,3

买卖股票的最佳时机 力扣题目链接 dp[i][0] 表示第i天持有股票所得最多现金 定义二维数组 两列 &#xff1a;0代表持有股票 1代表不持有股票 行代表第几天 dp[i][0] max(dp[i - 1][0], -prices[i]); 第i天持有股票&#xff1a;两种情况 第一种是昨天就已经持有股票了 所…

NVM使用教程

文章目录 ⭐️写在前面的话⭐️1、卸载已经安装的node2、卸载nvm3、安装nvm4、配置路径以及下载源5、使用nvm下载node6、nvm常用命令7、全局安装npm、cnpm8、使用淘宝镜像cnpm9、配置全局的node仓库&#x1f680; 先看后赞&#xff0c;养成习惯&#xff01;&#x1f680;&#…

探索AI+电商领域应用与发展

AI火的已经一塌糊涂了&#xff0c;已经有很大一部分的企业和个人已经坐上了这趟超音速列车&#xff0c;但对于电商领域具体都有哪些助理&#xff0c;目前为止还是比较散&#xff0c;今天来顺一下AIGC之与电商到底带来了些什么&#xff1f; 一、什么是AIGC AIGC是内容生产方式…

个人开发者上架App流程

摘要 个人开发者完全可以将自己开发的App上传至应用商店进行上架。本文将介绍上架流程的通用步骤&#xff0c;包括确定App功能和定位、准备相关资料、开发App、提交审核、发布App和宣传推广等内容。 引言 个人开发者在如今的移动应用市场中也有机会将自己的作品推向更广泛的…

C++之模版详解

一.array与vector对比 由图发现&#xff0c;使用array数组是必须提前开好空间&#xff0c;而vector是顺序表&#xff0c;可以实现动态开辟空间 array也支持迭代器&#xff0c;如下&#xff1a; int main() {array<int, 10> arr{ 1,2,3,4,5,6,7,8,9,10 };auto it arr.be…

重生奇迹MU 的全部地图

卓越首饰类&#xff1a;火项链、毒戒指——海1小巴、美人鱼、卡2门口&#xff0c;卡1最里面&#xff0c;地3等等 雷项链&#xff0c;冰戒指——海1蓝翼怪&#xff0c;卡2龙虾以上&#xff0c;失落3&#xff08;门口黄金点哦&#xff0c;盛产冰戒指&#xff09;等等 冰项链——…

第十二届蓝桥杯省赛CC++ 研究生组-货物摆放

还是整数分解问题,注意n本身也是约数 #include <iostream> int main(){printf("2430");return 0; }#include <iostream> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const ll n 2021041820210418LL…