【C++程序员的自我修炼】拷贝构造函数

心存希冀

追光而遇目有繁星

沐光而行


目录

拷贝构造函数概念

拷贝构造的特征

无穷递归的解释

浅拷贝

总结:

 深拷贝

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

总结 

契子

在生活中总有很多琐事,不做不行做了又怕麻烦,有时候想要是有个和自己一模一样的人就好了

可以帮我上早读晚修~

就像以上的两个安妮娅一样,可以一个上学一个宅在家看电视
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
答曰 ~ 当然可以,你在现实中办不到了事情,C++都可以帮你做到,不管是对象还是 -- 分身💞

拷贝构造函数概念

拷贝构造函数只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存 在的类类型对象创建新对象时由编译器自动调用
简单来说就是: 使用同类型的对象拷贝初始化

为了能让各位老铁更清楚的认识 拷贝构造函数 的概念,我们先小小的举个栗子~

#include<iostream>
using std::cout;
using std::endl;

class Date
{
public:
	Date(int year = 2024, int month = 4, int day = 13)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << " year = " << _year << " month = " << _month << " day = " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 4, 14);
	d1.Print();
	Date d2(d1);
	d2.Print();
	return 0;
}

Date d2(d1);

以上是拷贝构造的一种写法,以下提供另一种写法跟 赋值 很像

Date d2 = d1;

我们发现此时的 d2 已经具备了 d1 的所有特征

拷贝构造的特征

我们来总结一下拷贝构造的特点:

拷贝构造函数 是构造函数的一个重载形式
拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错
因为会引发无穷递归调用

为什么会引发 无穷递归 呢?

我们来看

无穷递归的解释

#include<iostream>
using std::cout;
using std::endl;

class Date
{
public:
	Date(int year = 2024, int month = 4, int day = 13)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date & d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << "year = " << _year << " month = " << _month << " day = " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void Fun(Date d)
{
	d.Print();
}

int main()
{
	Date d1(2024, 4, 15);
	Fun(d1);
	return 0;
}

我们发现在上面这段程序中竟然发生了 拷贝构造 

调用 Fun 得先传参而拷贝构造也是在传参中触发的

总结:

自定义类型对象传值传参要调用拷贝构造来完成

那么有没有什么方法有不调用拷贝构造呢 ?还真有而且有两种

传指针

void Fun(Date* d)
{
	d->Print();
}
int main()
{
	Date d1(2024, 4, 15);
	Fun(&d1);
	return 0;
}

为什么传指针就可以呢? 

因为此时传的是 d1 的地址,内置类型相当于我把地址拷贝给你

但是可以归可以,但是这样做了的话,就已经不叫拷贝构造函数了,而就是一个以指针变量为形参的构造函数

传引用

void Fun(Date& d)
{
	d.Print();
}
int main()
{
	Date d1(2024, 4, 15);
	Fun(d1);
	return 0;
}

这里相当于给 d1 取了别名 d ,实际还是那块地址空间

我们画个图来理解一下:

 步骤:

<1>当一个对象以值方式传递时,编译器会生成代码调用它的拷贝构造函数生成一个复本

<2>当我们以传值的方式传递时,我们给出一个类实例的实参,然后我们都知道我们会得到一个一样的形参
<3>创建这个形参时,编译器会自动调用类的拷贝构造函数来完成,于是在调用创建形参的拷贝函数时,又需要再次创建另外的形参,于是就一直循环下去

浅拷贝

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
简单来说就是:不写拷贝构造编译器自动提供的就叫浅拷贝
#include<iostream>
using std::cout;
using std::endl;

class Date
{
public:
	Date(int year = 2024, int month = 4, int day = 13)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "year = " << _year << " month = " << _month << " day = " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 4, 15);
	Date d2(d1);
	d2.Print();
	return 0;
}

这个不写拷贝构造函数依然可以实现我们的拷贝构造~

注意:

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的

既然那么爽不写也能用,那么我还写拷贝构造干嘛呢???

我们来看(这其实有局限性)~ 举个例子 -- 栈

错误示范

#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
using std::endl;
using StackDataUsing = int;
class Stack
{
public:
	Stack(int n);
	~Stack();
	void push(StackDataUsing x);
	StackDataUsing Top();
	void Pop();
	bool Empty();
private:
	StackDataUsing* data;
	int top;
	int capacity;
};

Stack::Stack(int n = 4)
{
	StackDataUsing* newNode = new StackDataUsing[n];
	if (newNode == nullptr)
	{
		perror("new1");
		exit(-1);
	}
	data = newNode;
	capacity = n;
	top = 0;
}

Stack::~Stack()
{
	free(data);
	data = nullptr;
	top = capacity = 0;
}

void Stack::push(StackDataUsing x)
{
	if (capacity == top)
	{
		StackDataUsing* newNode = new StackDataUsing[2 * capacity];
		if (newNode == NULL)
		{
			perror("new2");
			exit(-1);
		}
		data = newNode;
		capacity *= 2;
	}
	data[top++] = x;
}

StackDataUsing Stack::Top()
{
	return data[top - 1];
}
void Stack::Pop()
{
	assert(top > 0);
	top--;
}
bool Stack::Empty()
{
	return top == 0;
}

int main()
{
	Stack st1;
	st1.push(1);
	st1.push(2);
	st1.push(3);
	Stack st2(st1);
	return 0;
}

我们想拷贝栈中 st1 的元素能成功吗

我们从监视的角度看,好像看起来已经成功了 st1 所有的特性都对的上

但是~

不知道各位老铁有没有发现他们的空间是完全一样

 🌤️报了个完美的错误!!!

浅拷贝,也就是值拷贝是一个字节一个字节的拷贝,但是涉及到资源申请时,还需要慎重考虑

举个栗子:

C语言中结构体传参都是传地址,一旦是传值传参,就是浅拷贝,有可能会出现 bug ,因为如果传了 malloc 开辟的地址,结构体传过来修改了,就可能改了原来的结构体。这是C语言的 bug

栈也是这样~

我们的 top、capacity 这样拷贝没问题,但是问题出在了 data 上,因为这里是指针也就是说将 st1data 所指向的空间地址拷贝给了 st2 ,它们两的 data 指向同一块空间,程序结束后自然要析构的,这样就导致对同一块空间析构了两次,致使程序报错

总结:

类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写

 深拷贝

那么以上栈的代码该怎么拷贝构造呢?

C++的命名风格很独特~有浅拷贝自然就有深拷贝,涉及资源申请的拷贝都要用深拷贝

先做个小总结:

浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化
深拷贝开辟和原来一样大的空间,并将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变
栈的深拷贝写法:
Stack(const  Stack& st)
{
	data = (StackDataUsing*)malloc(sizeof(StackDataUsing) * st.capacity);
	if (data == nullptr)
	{
		perror("malloc");
		return;
	}
	memcpy(data, st.data, sizeof(StackDataUsing) * st.top);
	top = st.top;
	capacity = st.capacity;
}

这样就没有任何报错了~我们来看是两个不同的空间哎!!!

这也就证明了深拷贝就是:开辟和原来一样大的空间,并将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变

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

使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象
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/557591.html

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

相关文章

机器学习和深度学习-- 李宏毅(笔记于个人理解)Day 21

Day 21 Self- Attention 选修部分 ​ 学完自适应 再回来看看 Sequence Labling 假如我们现在有一个需要读完全部句子才能解的问题&#xff0c; 那么red window 就需要变得是最大的&#xff08;最长的句子&#xff09;&#xff1b; 其实这里大家有没有想过&#xff0c;这个玩意…

【机器学习】数据变换---小波变换特征提取及应用案列介绍

引言 在机器学习领域&#xff0c;数据变换是一种常见且重要的预处理步骤。通过对原始数据进行变换&#xff0c;我们可以提取出更有意义的特征&#xff0c;提高模型的性能。在众多数据变换方法中&#xff0c;小波变换是一种非常有效的方法&#xff0c;尤其适用于处理非平稳信号和…

maridb双数据源联查解决方案:联合存储引擎(Federated Storage Engine)

本地MySQL数据库要访问远程MySQL数据库的表中的数据, 必须通过FEDERATED存储引擎来实现. 有点类似Oracle中的数据库链接(DBLINK)。使用FEDERATED存储引擎的表,本地只存储表的结构信息,数据都存放在远程数据库上,查询时通过建表时指定的连接符去获取远程库的数据返回到本地。操作…

爬虫机试题-爬取新闻网站

之前投简历时遇到了这样的一个笔试。本以为会是数据结构算法之类的没想到直接发了一个word直接提需求&#xff0c;感觉挺有意思就写了这篇文章&#xff0c;感兴趣的朋友可以看看。 拿到urllist 通过分析页面结构我们得以知道&#xff0c;这个页面本身没有新闻信息&#xff0c;是…

计算机软考流程介绍

笔者来介绍一下软考流程 1、考试简介 计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff1a;简称 计算机软考 认证&#xff1a; 国家人力资源和社会保障部 国家工业和信息化部 目的&#xff1a; 科学、公正地对全国计算机与软件专业技术人员进行职业资格…

Hotcoin 热门资产上新速报:以太坊互操作性基础设施Omni Network(OMNI)

Hotcoin持续为全球600万用户发掘优质潜力资产&#xff0c;热门币种交易上热币。一文快速了解今日上新资产:Omni Network&#xff08;OMNI&#xff09; 推荐指数 8.4 交易对 OMNI/USDT 交易时间 4月17日 GMT8 20&#xff1a;30 资产赛道 Layer1 项目简介 Omni 是以太坊…

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台级联时,下级平台未发流是什么原因?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

黑洞路由、 DDoS 攻击 、 环路

黑洞路由 DDoS 攻击 DDoS 攻击是一种针对服务器、服务或网络的恶意行为。DDoS 攻击通过向目标发送大量流量&#xff0c;使其不堪重负&#xff0c;导致资源和带宽被耗尽。因此&#xff0c;目标可能会变慢或崩溃&#xff0c;无法正常处理合法的流量。DDoS 攻击通常是由僵尸网络…

Jmeter 性能-内存溢出问题定位分析

1、堆内存溢出 ①稳定性压测一段时间后&#xff0c;Jmeter报错&#xff0c;日志报&#xff1a; java.lang.OutOfMemoryError.Java heap space ②用jmap -histo pid命令dump堆内存使用情况&#xff0c;查看堆内存排名前20个对象。 看是否有自己应用程序的方法&#xff0c;从…

CentOS7下安装mysql8或者mysql5.7

mysql8 1、下载 访问mysql官网下载mysql8软件包 https://dev.mysql.com/downloads/mysql/ 选择相应的版本如&#xff1a;RPM Bundle mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar RPM Bundle 8.0.33 下载地址&#xff1a;https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.…

电脑桌面便签软件哪个好?好用的电脑桌面便签

电脑作为我们日常工作的重要工具&#xff0c;承载着大量的任务和项目。当工作任务繁重时&#xff0c;如何在电脑桌面上高效管理这些任务就显得尤为重要。这时&#xff0c;选择一款优秀的桌面便签软件&#xff0c;无疑会给我们带来极大的便利。 一款好的桌面便签软件&#xff0…

【React】Ant Design自定义主题风格及主题切换

Ant Design 的自定义主题&#xff0c;对于刚入手的时候感觉真是一脸蒙圈&#xff0c;那今天给它梳理倒腾下&#xff1b; 1、自定义主题要点 整体样式变化&#xff0c;主要两个部分&#xff1a; 1.1、Design Token https://ant.design/docs/react/customize-theme-cn#theme 官…

ffmpeg入门

ffmpeg入——安装 Fmpeg地址 FFmpeg源码地址&#xff1a;https://github.com/FFmpeg/FFmpeg FFmpeg可执行文件地址&#xff1a;https://ffmpeg.org/download.html Windows平台 Windows平台下载解压后如图所示&#xff08;文件名称以-share结尾的是开发库&#xff09; FFmpeg…

Eagle for Mac v1.9.13注册版:强大的图片管理工具

Eagle for Mac是一款专为Mac用户设计的图片管理工具&#xff0c;旨在帮助用户更高效、有序地管理和查找图片资源。 Eagle for Mac v1.9.13注册版下载 Eagle支持多种图片格式&#xff0c;包括JPG、PNG、GIF、SVG、PSD、AI等&#xff0c;无论是矢量图还是位图&#xff0c;都能以清…

AndroidStudio AGP 7+, 编译aar并输出到本地仓库

1 编写构建gradle脚本代码 1.1 配置publication和repository 在指定moudle目录下新建名为"maven-publish.gradle"文件&#xff0c;其声明的publication和repository如下所示&#xff1a; apply plugin: maven-publish// This creates a task called publishReleas…

《星光对话》系列直播:带你入门数据要素

2020年12月9日&#xff0c;财政部提出企业数据资源可作为资产列入财务报表&#xff0c;打响数据要素“1N”的第一枪&#xff1b; 2022年12月2日&#xff0c;《关于构建数据基础制度更好发挥数据要素作用的意见》“数据二十条”通过提出构建数据产权、流通交易、收益分配、安全治…

维护SQLite的私有分支(二十六)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite、MySQL 和 PostgreSQL 数据库速度比较&#xff08;本文阐述时间很早比较&#xff0c;不具有最新参考性&#xff09;&#xff08;二十五&#xff09; 下一篇&#xff1a;SQLite数据库中JSON 函数和运算符 1…

# 从浅入深 学习 SpringCloud 微服务架构(三)注册中心 Eureka(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;三&#xff09;注册中心 Eureka&#xff08;1&#xff09; 段子手168 1、微服务的注册中心 注册中心可以说是微服务架构中的”通讯录”&#xff0c;它记录了服务和服务地址的映射关系。 在分布式架构中服务会注册到这里&am…

美易官方:美债美元黄金继续涨?

全球金融市场波动加剧&#xff0c;投资者对避险资产的需求不断升温。在这一背景下&#xff0c;“投行老将”们纷纷发表观点&#xff0c;认为避险情绪尚未结束&#xff0c;美债、美元和黄金等避险资产有望继续上涨。 巴克莱一位资深投资银行家表示&#xff0c;由于担心中东冲突升…

在Linux系统中搜索当前路径及其子目录下所有PDF文件中是否包含特定字符串

目录标题 方法一&#xff1a;pdfgrep方法二&#xff1a;使用find和xargs与pdftotext&#xff08;将PDF转换为文本&#xff09;组合&#xff0c;然后用grep搜索 方法一&#xff1a;pdfgrep pdfgrep -ri "rockchip" .方法二&#xff1a;使用find和xargs与pdftotext&am…