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

news2024/12/23 14:53:32

心存希冀

追光而遇目有繁星

沐光而行


目录

拷贝构造函数概念

拷贝构造的特征

无穷递归的解释

浅拷贝

总结:

 深拷贝

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

总结 

契子

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

可以帮我上早读晚修~

就像以上的两个安妮娅一样,可以一个上学一个宅在家看电视
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
答曰 ~ 当然可以,你在现实中办不到了事情,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;
}

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

总结 

如果没有管理资源,一般情况不需要写拷贝构造,默认生成的拷贝构造就可以
如果都是自定义类型成员,内置类型成员没有指向资源,也类似默认生成的拷贝构造就可以
一般情况下,不需要显示写析构函数,就不需要写拷贝构造
如果内部有指针、一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝

先介绍到这里啦~

有不对的地方请指出💞

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

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

相关文章

功能测试_订购单检查_判定表

画判定表的步骤&#xff1a; 列出条件 列出动作

[大模型]Yi-6B-chat WebDemo 部署

Yi-6B-chat WebDemo 部署 Yi 介绍 由60亿个参数组成的高级语言模型 Yi LLM。为了促进研究&#xff0c;Yi 已经为研究社区开放了Yi LLM 6B/34B Base 和 Yi LLM 6B/34B Chat。 环境准备 在autodl平台中租一个3090等24G显存的显卡机器&#xff0c;如下图所示镜像选择PyTorch–…

【Linux】磁盘与文件系统管理

目录 一、 磁盘结构 1. 数据结构 2. 物理结构 3. 硬盘的接口类型 二、 如何使用Linux中的磁盘 三、 文件系统 四、 磁盘分区 1. MBR分区 2. 分区的优缺点 3. 磁盘及分区的管理工具 五、格式化与挂载 1. 格式化 2. 挂载 六、实例演示 1. 演示分区格式化挂载 2. …

Springboot+Vue项目-基于Java+MySQL的旅游网站系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

OpenCV轻松入门(六)——简单图片处理【马赛克、毛玻璃、浮雕效果】

马赛克效果 马赛克指现行广为使用的一种图像&#xff08;视频&#xff09;处理手段&#xff0c;此手段将影像特定区域的色阶细节劣化并造成色块打乱的效果&#xff0c;因为这种模糊看上去有一个个的小格子组成&#xff0c;便形象的称这种画面为马赛克。其目的通常是使之无法辨…

麒麟v10安装mysql-8.0.35

因为要修复漏洞的原因&#xff0c;这两天将麒麟v10操作系统的服务器上的MySQL版本由5.7.27升级到8.0.35&#xff08;mysql安装包下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions)&#xff09;&#xff0c;mysql的安装过程主要参考了这个博主…

Qlik在数据隐私计划中利用人工智能和分析

在技术快速变革的时代&#xff0c;政府正在努力追赶技术发展和我们日常生活中产生的个人身份信息&#xff08;“PII”&#xff09;数量不断增加的步伐。规范 PII 使用的隐私法不断加强&#xff08;Gartner估计&#xff0c;虽然到 2020 年&#xff0c;全面的隐私法将覆盖全球 10…

MQ:延迟队列

6.1场景&#xff1a; 1.定时发布文章 2.秒杀之后&#xff0c;给30分钟时间进行支付&#xff0c;如果30分钟后&#xff0c;没有支付&#xff0c;订单取消。 3.预约餐厅&#xff0c;提前半个小时发短信通知用户。 A -> 13:00 17:00 16:30 延迟时间&#xff1a; 7*30 * 60 *…

【央国企专场】——国家电网

国家电网目录 一、电网介绍1、核心业务2、电网组成 二、公司待遇三、公司招聘1、招聘平台2、考试安排2.3 考试内容 一、电网介绍 1、核心业务 国家电网公司&#xff08;State Grid Corporation of China&#xff0c;简称SGCC&#xff09;是中国最大的国有企业之一&#xff0c…

【漏洞预警】Linux kernel权限提升漏洞(CVE-2024-1086)

一、漏洞概述 漏洞名称 Linux kernel权限提升漏洞 CVE ID CVE-2024-1086 漏洞类型 Use-After-Free 发现时间 2024-03-28 漏洞评分 7.8 漏洞等级 高危 攻击向量 本地 所需权限 低 利用难度 低 用户交互 无 PoC/EXP 已公开 在野利用 未知 Netfilte…

突破编程_前端_SVG(rect 矩形)

1 rect 元素的基本属性和用法 在SVG中&#xff0c;<rect> 元素用于创建矩形。 <rect> 元素有一些基本的属性&#xff0c;可以用来定义矩形的形状、位置、颜色等。以下是这些属性的详细解释&#xff1a; x 和 y &#xff1a;这两个属性定义矩形左上角的位置。 x …

学习数通HCIE选择誉天有什么优势?

誉天数通课程亮点 课程内容详实&#xff0c;千万级实训环境 涵盖数通技术全场景热门技术&#xff0c;涉及传统园区网&#xff0c;虚拟化园区网&#xff0c;广域互联技术&#xff0c;数据中心网络&#xff0c;网络自动化运维 专业机房环境&#xff0c;全真机教学演示&#xf…

Linux、Docker、Brew、Nginx常用命令

Linux、Docker、Brew、Nginx常用命令 Linuxvi编辑器文件操作文件夹操作磁盘操作 DockerBrewNginx参考 Linux vi编辑器 Vi有三种模式。命令模式、输入模式、尾行模式&#xff0c;简单的关系如下&#xff1a; i -- 切换到输入模式&#xff0c;在光标当前位置开始输入文本。&a…

Pytorch Windows EOFError: Ran out of input when num_workers>0

关于深度学习的一些学习框架,我使用过pytorch,caffe,caffe2,openchatkit,oneflow等,最近我将长达几十万字的报错手册重新进行了整理,制作出一个新的专栏,主要记录这几种常见的开发框架在安装和使用过程中常见的报错,以及我是如何解决掉的,以此来帮助更多的深度学习开…

生成式AI对UiPath来说是机遇还是挑战?

企业争相通过技术革新来领跑市场&#xff0c;机器人流程自动化&#xff08;RPA&#xff09;技术更是将企业的效率和成本控制推向了新的高度。但当人工智能&#xff08;AI&#xff09;的最新进展——生成式AI登上舞台时&#xff0c;它不仅带来了变革的可能&#xff0c;还提出了一…

Matlab之过球面一点的平面方程

这篇文章描述2件事情&#xff1a; 1、已知球面上任意点&#xff0c;求过该点、地心、与北极点的平面方程&#xff08;即过该点的经线平面方程&#xff09;&#xff1b; 2、绕过球心的任意轴旋转平面得到新平面的方程 一、已知球面上任意点&#xff0c;求过该点、地心、与北极点…

字体体积压缩

环境:python3 关键步骤: pip install fontTools目录详情: 执行 pyftsubset.exe SourceHanSansCN-Medium.ttf --text-file3500.txt然后打开:TTF To Woff2,选择文件上传,等待处理,下载,使用 附常用汉字,字体文件请善用百度 3500.txt 工才下寸丈大与万上小口山巾千乞川亿个…

Python项目2 数据可视化

生成数据 数据可视化 指的是通过可视化表示来探索数据&#xff0c;它与数据挖掘 数据挖掘 紧密相关&#xff0c;而数据挖掘指的是使用代码来探索数据集的规律和关联。数据集可以是用一行代码就能表 示的小型数字列表&#xff0c;也可以是数以吉字节的数据。 漂亮地呈现数据关…

P4631 [APIO2018] 选圆圈

题目传送门https://www.luogu.com.cn/problem/P4631 代码传送门https://www.luogu.com.cn/record/155489748 本弱鸡抄的~

在mysql中如何更新数据呢?

如何更新一条数据&#xff1f; 在 MySQL 中&#xff0c;更新一条数据可以使用 UPDATE 语句。以下是更新一条数据的基本语法&#xff1a; UPDATE table_name SET column1 value1, column2 value2,... WHERE condition;其中&#xff1a; table_name&#xff1a;要更新的表的…