C++ 类和对象篇(六) 拷贝构造函数

news2024/11/20 6:23:40

目录

一、 概念

1. 拷贝构造函数是什么?

2. 为什么要有拷贝构造函数?

3. 怎么用拷贝构造函数?

3.1 创建拷贝构造函数

3.2 调用拷贝构造函数

二、特征

三、编译器生成的默认拷贝构造函数

四、什么时候需要显示的写拷贝构造函数?

拓、在C++中实现深拷贝

1. 自己开辟一个新空间,然后再赋值。

2. 借助浅拷贝的构造函数来实现深拷贝。

3. 重载= 


一、 概念

1. 拷贝构造函数是什么?

        拷贝构造函数是一个特殊的构造函数,也是用来初始化对象的,不过它是用已经存在的对象来初始化同类对象。

2. 为什么要有拷贝构造函数?

        在创建新对象时,可否用已经存在的同类对象来初始化这个新对象呢?能否快速拷贝出一个对象的副本呢?为解决以上问题,C++中引入了拷贝构造函数:拷贝构造函数用于实现对象的复制和初始化。

3. 怎么用拷贝构造函数?

3.1 创建拷贝构造函数

        和构造函数一样,函数名和类名相同,且没有返回值,但拷贝构造函数的参数必须是本类类型的引用或是指向本类类型的指针。

class Date
{
public:
	Date(int year = 2022, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //Date(const Date d) // 错误写法:编译报错,会引发无穷递归。
    // 正确写法,拷贝构造函数的参数必须是本类类型的引用或是指向本类类型的指针。	
    Date(const Date& d)  
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	Date(const Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}
    // 拷贝构造函数可以有多个参数,但一般只以一个对象为副本进行拷贝,所以只写一个参数。
    Date(const Date& d1,const Date& d2,const Date* d3)
	{
		_year = d1._year;
		_month = d2._month;
		_day = d3->_day;
	}
private:
	int _year;
	int _month;
	int _day;
};

所以拷贝构造函数的参数必须是本类类型的引用或是指向本类类型的指针,不然会引起以下拷贝构造函数传值传参带来的无穷递归问题

        拷贝构造函数如使用传值传参的方式,会引发无穷递归调用编译器会直接报错。因为使用传值传参时,编译器要调用拷贝构造函数用实参初始化形参,使用拷贝构造函数就必须得先传参,又会调用拷贝构造函数,这样就造成了造成无穷递归。


在传引用传参或指针传参时,对于像拷贝构造函数的形参这类不需要修改的参数,建议加上一个const。这样做即能避免误操作导致实参被修改,也能误操作时给我们一个提示,让我们快速定位错误。

3.2 调用拷贝构造函数

a. 调用拷贝参数是本类类型的引用的拷贝构造函数

int main()
{
	Date a(1,1,1);
	Date b(a);
	return 0;
}


b. 调用拷贝参数是指向本类类型的指针的拷贝构造函数

int main()
{
	Date c(3, 3, 3);
	Date d(&c);
	return 0;
}


c. 调用多个参数的拷贝构造函数

拷贝构造函数可以有多个参数,但一般只以一个对象为副本进行拷贝只写一个参数。

int main()
{
	Date a(1, 1, 1);
	Date b(2, 2, 2);
	Date c(3, 3, 3);
	Date d(a, b, &c);
	return 0;
}


二、特征

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

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数必须是本类类型的引用或是指向本类类型的指针。使用传值传参方式编译器会直接报错,因为会引发无穷递归调用。

3. 拷贝构造函数可以有多个参数,但一般只以一个对象为副本进行拷贝,所以只写一个参数。

4. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。


三、编译器生成的默认拷贝构造函数

若未显式定义,编译器会生成默认的拷贝构造函数。

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

编译器生成的默认拷贝构造函数引发的“双重删除”问题:

编译器生成的默认拷贝构造函数只能进行浅拷贝,无法对申请的资源(如动态开辟的空间)进行拷贝,看下面的例子:

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()
	{
		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;
}


        在以上例子中我们发现,默认的拷贝构造函数会令拷贝的类和被拷贝的类中的指针变量指向同一块空间,这样会造成同一块空间被析构函数析构两次,这通常被称为“双重删除”或“重复删除”,这是一个严重的问题,会导致程序崩溃。这个时候需要显示的写一个构造函数,并在里面完成深拷贝。


四、什么时候需要显示的写拷贝构造函数?

        编译器生成的默认拷贝构造函数只能进行浅拷贝,无法对申请的资源(如动态开辟的空间)进行拷贝。

        所以类中如果没有涉及资源申请时,拷贝构造函数是否写都可以。一旦涉及到资源申请时,一定要显示的写拷贝构造函数,否则就是浅拷贝,可能导致“双重删除”问题。


拓、在C++中实现深拷贝

实现深拷贝的关键是自己开辟一个新空间,然后再赋值。以下以string类为例,演示如何实现深拷贝:


1. 自己开辟一个新空间,然后再赋值。


2. 借助浅拷贝的构造函数来实现深拷贝。

但这有个小问题,就是当前对象未初始化,直接交换数值可能会导致程序崩溃,所以加上初始化列表,在交换数值前先初始化。


3. 重载= 

如果不重载,依靠编译器自动生成的 operator=,还是会出现浅拷贝问题,这次是拷贝对象中的str指向和原对象的str的同一块空间。(析构仍会出问题)

  

注意,更改指针指向前一定要释放原空间,不然会造成内存泄漏。

上面样写没有考虑到,自己给自己赋值的问题,所以还要加一个判断。

这样写就完美了吗?不,还没有考虑new失败后直接跳出函数同时抛出异常。(需要的内存过大可能会导致new失败)

这里我们先开辟空间给一个临时变量,这样如果new失败了,原来的str也还没有被销毁。

当然也可以写成:

类似于拷贝构造,直接用目标对象构造一个临时变量,再彻底交换,临时变量结束时也会释放原有的空间。(注,这里的swap是需要自己实现的对象交换函数)

注,里面的swap是库里定义的全局域中的swap函数,故要在前面加上 : : 表明是全局域中的函数。

 那在实现函数时为什么不像下面一样直接调用库里的swap函数进行交换呢?

因为库中的swap也会用到 = ,这样会造成死循环

再简洁一点就是


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

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

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

相关文章

【案例分享】配置设备作为PPPoE Client,实现接入Internet

【微|信|公|众|号:厦门微思网络】 【微思网络www.xmws.cn,成立于2002年,专业培训21年,思科、华为、红帽、ORACLE、VMware等厂商认证及考试,以及其他认证PMP、CISP、ITIL等】 组网需求 企业用户希望把一个站点上的多台…

AtCoder Beginner Contest 228(A-Ex)

A - On and Off (atcoder.jp) (1)题意 高桥每天在S点钟打开他房间的灯,并在T点钟关灯,指示灯亮起时,日期可能会发生改变,判断是否在X点过后30分时亮着。 (2)思路 直接模拟即可。 &am…

《机器学习实战》学习记录-ch3

第3章 分类 PS: 个人记录,抄书系列,建议看原书 原书资料:https://github.com/ageron/handson-ml2 目录 第3章 分类3.1 MNIST 数据集3.2 训练二元分类器3.2.1 随机梯度下降 SGD 3.3 性能测量3.3.1 使用交叉验证测量准确率3.3.2 混淆矩阵3.3.3…

在CSV文件读取时id读取之后成了‘锘縤d‘,该怎么修改

问题描述 准备用python实现一个小想法,里边数据可能会有点多,也为了代码能成块展示,所以打算写一个读取文件的函数。此时考虑到python中可以直接将CSV格式文件读取成字典的形式,所以尝试了一下。但是中间出现了错误,第…

【源码】hamcrest 源码阅读 泛型 extends 和迭代器模式

文章目录 前言1. 泛型参数和自定义迭代器1.1 使用场景1.2 实现 2. 值得一提 前言 官方文档 Hamcrest Tutorial 上篇文章 Hamcrest 源码阅读及空对象模式、模板方法模式的应用 本篇文章 迭代器模式 1. 泛型参数和自定义迭代器 hamcrest 作为一个matcher库,把某个…

【Spring内容介绍 | 第一篇】什么是事务管理

前言: 当今软件开发行业中,事务管理是一个不可或缺的重要组成部分。随着企业应用的复杂性和数据交互的增加,确保数据的一致性和完整性变得越来越关键。Spring框架作为一种全功能的应用程序开发框架,为我们提供了强大而灵活的事务管…

腾讯云服务器简介_CVM优势_常见问题解答

腾讯云服务器CVM提供安全可靠的弹性计算服务,腾讯云明星级云服务器,弹性计算实时扩展或缩减计算资源,支持包年包月、按量计费和竞价实例计费模式,CVM提供多种CPU、内存、硬盘和带宽可以灵活调整的实例规格,提供9个9的数…

Dubbo-CVE-2020-1948

APache Dubbo简介 Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式…

四.Docker容器技术

课程内容 DevOps和云原生Docker基础命令Docker安装软件Docker项目部署 一.DevOps和云原生 1.微服务的痛点 再来看一下我们的微服务架构 , 每个组件都需要服务器去部署,加起来可能需要几十个甚至上百个服务器。 这样的微服务项目在部署上会遇到什么问…

【重拾C语言】六、批量数据组织(一)数组(数组类型、声明与操作、多维数组;典例:杨辉三角、矩阵乘积、消去法)

目录 前言 六、批量数据组织——数组 6.1 成绩统计——数组类型 6.1.1 数组类型 6.1.2 数组声明与操作 6.1.3 成绩统计 6.2 统计多科成绩——多维数组 6.3 程序设计实例 6.3.1 杨辉三角形 6.3.2 矩阵乘积 6.3.3 消去法 6.4 线性表——分类与检索 前言 ChatGPT C语…

Unity 致社区公开信,调整 runtime fee 政策

导读面对此前制定的 "Funtime Fee" 收费政策所引发的用户争议与不满,Unity Create 负责人 Marc Whitten 正式发布了一封致社区的道歉公开信,并详细解释了其定价策略的变更。 我想以此开始:对不起。 在宣布新的 Runtime Fee 政策之…

【Spring内容进阶 | 第三篇】AOP进阶内容

前言: 在前面我们已经粗略的介绍了什么是AOP以及各种基础知识点,而本篇我们将聚焦于AOP的细节,详细的讲解一下AOP中的通知类型,通知顺序,切入点表达式以及连接点。通过对AOP的熟练掌握,我们可以快速编写出低…

【类加载子系统】

文章目录 1. 内存结构2. 类加载器与类的加载过程3. 类加载器分类4. 双亲委派机制 1. 内存结构 Class 文件类加载子系统运行时数据区 方法区堆程序计数器虚拟机栈本地方法栈 执行引擎本地方法接口本地方法库 2. 类加载器与类的加载过程 类加载器子系统负责从文件系统或者网络中加…

基于树种优化的BP神经网络(分类应用) - 附代码

基于树种优化的BP神经网络(分类应用) - 附代码 文章目录 基于树种优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.树种优化BP神经网络3.1 BP神经网络参数设置3.2 树种算法应用 4.测试结果:5.M…

旅美经济学家向凌云在美获对冲基金之父威廉·雅克颁授美国文化奖

2023年9月22日,第三届LABA偶像奖(LABA ICON AWARDS )「美国文化奖」颁奖典礼在美国洛杉矶比佛利山庄四季酒店举办。美国本土、欧洲、亚洲都有众多艺术家、经济学家和各行业优秀人才积极参与其中。 对冲基金之父威廉雅克(William Jacques)向经济学家Dr. Lingyun Xiang 向凌云颁发…

【进程管理】认识系统调用函数fork

目录 前言 一.查看进程的pid 二.父子进程 三.查看进程的第二种方式 四.代码创建进程——fork 1.fork的功能 2.fork的返回值 3.fork代码的一般写法 五.对于fork的理解 1.fork干了些什么? 2.fork为什么给子进程返回0,给父进程返回子进程的pid&…

大数据与Hadoop入门理论

一、大数据的3种数据类型 1、结构化数据 可定义,有类型、格式、结构的强制约束 如:RDBMS(关系型数据库管理系统) 2、非结构化数据 没有规律没有数据约束可言,很复杂难以解析 如:文本文件,视…

第86步 时间序列建模实战:Transformer回归建模

基于WIN10的64位系统演示 一、写在前面 这一期,我们介绍Transformer回归。 同样,这里使用这个数据: 《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Sy…

Casdoor系统static任意文件读取漏洞

文章目录 Casdoor系统static任意文件读取漏洞复现0x01 前言0x02 漏洞描述0x03 影响平台0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 Casdoor系统static任意文件读取漏洞复现 0x01 前言 免责声明:请勿利用文章内的相关技术从事非法测试,由…

郁金香2021年游戏辅助技术中级班(七)

郁金香2021年游戏辅助技术中级班(七) 058-C,C写代码HOOK分析封包数据格式A059-C,C写代码HOOK分析封包数据格式B-detours劫持060-C,C写代码HOOK分析封包数据格式C-过滤和格式化061-C,C写代码HOOK分析封包数据格式D-写入配置文件062-C,C写代码HOOK分析封包…