「C/C++ 01」 深拷贝和浅拷贝

目录

一、概念 

1. 浅拷贝

2. 深拷贝

3. 深浅拷贝问题

4. 总结 

二、在C++的类中实现深拷贝

1. 拷贝构造函数 中实现深拷贝

a. 自己开辟一个新空间,然后将内容拷贝到新空间

b. 借助构造函数来实现深拷贝

2. operator= 中实现深拷贝 

a. 自己开辟一个新空间,然后将内容拷贝到新空间

b. 借助构造函数来实现深拷贝


一、概念 

1. 浅拷贝

a. 是什么?

        浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象成员属性值的一份精确拷贝。如果成员是基本类型,拷贝的就是基本类型的值;如果属性是指针类型,拷贝的就是内存地址(即原来的指向) ,因此如果其中一个对象改变了指针指向的区域,就会影响到另一个对象。

        在浅拷贝中,只是复制了对象的引用或指针,而不是实际的数据。这意味着原始对象和复制的对象都指向同一块内存区域。因此,如果修改了其中一个对象,另一个对象也会受到影响。


b. 怎么做?

        在C语言中,如果使用赋值运算符(=)来完成结构体的拷贝就是浅拷贝;在C++中编译器默认生成的拷贝构造函数和赋值运算符重载函数都是使用浅拷贝。

2. 深拷贝

a. 是什么?

        深拷贝则完全复制了对象的数据,创建了一个全新的、独立的副本。修改其中一个对象不会影响另一个对象。


b. 怎么做?

        实现深拷贝的关键是对于原结构体或原对象中的每一个指针成员,都开辟一个新空间,然后再将原空间的内容拷贝到新空间中,然后让新结构体/对象中的指针成员指向这个新空间。

3. 深浅拷贝问题

        深浅拷贝问题主要是出现在对象或结构体的拷贝过程中。这个问题不仅仅出现在C/C++中,Java、Python等也有类似的问题。

        在C/C++中,结构体/对象的拷贝通常使用赋值运算符(=)来完成。如果结构体中包含指针,那么默认的拷贝操作是浅拷贝,通过上面的讲解可知如果直接用=来创建结构体/对象的拷贝,如果其一个结构体/对象对指针成员指向的内容进行修改就会影响到其他的结构体/对象的拷贝。因此在创建结构体/对象的拷贝需要使用深拷贝,需要手动实现数据的复制。

4. 总结 

       以代码仓库为例:浅拷贝就像是给原始代码仓库添加一个管理者让他也能操作这个仓库,但是,如果原始仓库被删除,那么所有人都无法访问这个仓库;深拷贝就是新建一个仓库,然后把原始仓库的内容拷贝到里面,这样把自己的仓库删掉了也不影响别人的仓库。


       所以实际的编程中,深拷贝通常需要更复杂的实现,因为它需要创建新的内存空间来存储复制的内容。而浅拷贝则相对简单,因为它只是复制了指针或引用,而不是实际的数据内容。


二、在C++的类中实现深拷贝

        实现深拷贝的关键是自己开辟一个新空间,然后再将原空间的内容拷贝到新空间。以下以stack类为例,演示如何实现深拷贝。

1. 拷贝构造函数 中实现深拷贝

a. 自己开辟一个新空间,然后将内容拷贝到新空间

b. 借助构造函数来实现深拷贝。

       在构造函数中自然要动态成员变量开辟空间,所以在拷贝构造函数中可以使用构造函数创建一个临时对象,然后交换对象和临时对象的动态成员


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

2. operator= 中实现深拷贝 

a. 自己开辟一个新空间,然后将内容拷贝到新空间

       如果不重载,依靠编译器自动生成的 operator=,还是浅拷贝。(析构时仍会出现“双重删除”问题)


以下将一步一步在operator= 中实现标准的深拷贝 :

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

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

错误三:还没有考虑new失败的问题。(需要的内存过大可能会导致new失败)

按照上面的写法,new失败后,不但没有成功给str赋值,str原来的内容也被销毁了。

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

最后再给new的失败加一个提示或抛异常。

b. 借助构造函数来实现深拷贝。

        当然也可以像拷贝构造一样,借助构造函数构造一个临时变量,再彻底交换,临时变量结束时也会调用析构函数释放原有的空间。(注,这里的swap是需要自己实现的对象交换函数)


【源代码】 

#include<iostream>
using namespace std;class stack
{
public:int* _arr;// 构造函数stack(int* arr = nullptr){_arr = new int();int len = sizeof(arr) / sizeof(arr[0]);copy(arr, arr + len, _arr);}~stack(){cout << "删除地址为:" << _arr << "的数组。" << endl;delete(_arr);}1. 自己开辟一个新空间,然后将内容拷贝到新空间。//stack(const stack &s)//{//	//开辟新空间//	_arr = new int();//	//拷贝内容//	int len = sizeof(s._arr) / sizeof(s._arr[0]);//	copy(s._arr, s._arr + len, _arr);//}2. 借助构造函数创建中间对象来实现深拷贝。//stack(const stack& s)//{//	//调用构造函数创建中间对象(系统会开辟好空间)//	stack tmp(s._arr);//	swap(_arr, tmp._arr);//}但这有个小问题,就是当前对象未初始化,直接交换数值可能会导致程序崩溃,所以加上初始化列表,在交换数值前先初始化。stack(const stack& s):_arr(nullptr){//调用构造函数创建中间对象stack tmp(s._arr);swap(_arr, tmp._arr);}stack& operator= (const stack& s){// 自己给自己赋值时无需开辟新空间。if (this != &s){//调用构造函数创建中间对象(系统会开辟好空间)stack tmp(s._arr);swap(_arr, tmp._arr);}return *this;}
};int main()
{int* arr = new int[5];arr[0] = 0;stack st1(arr);stack st2 = st1;cout << "st1数组的地址:" << st1._arr << endl \<< "st2数组的地址:" << st2._arr << endl;return 0;
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue 的两种实现:VSCode 中配置 vue 模板快捷方式的过程

1、创建配置文件&#xff1a; 其一、打开 VSCode &#xff0c;CtrlShiftP, 打开搜索框&#xff1a; 其二、输入&#xff1a;user, 并点击进去 Snippets:Configure User Snippets 其三、输入 vue3js 并回车&#xff1a; 其四、打开项目&#xff0c;发现配置文件 vue3js.code-sn…

提示“由于找不到mfc140u.dll,无法继续执行代码”如何解决?

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是找不到某个动态链接库文件&#xff0c;比如mfc140u.dll。这个问题可能会导致某些应用程序无法正常运行或打开。 一、关于找不到mfc140u.dll文件造成会的问题 mfc140u.dll是Visual C中的一个…

Shell三剑客:awk(格式化输出)

一、格式符 %d 十进制有符号整数 %u 十进制无符号整数 %f 浮点数 %s 字符串 %c 单个字符 %p 指针的值 %e 指数形式的浮点数 %x %X 无符号以十六进制表示的整数 %o 无符号以八进制表示的整数 %g 自动选择合适的表示法 % % 显示%自身 # [.#] 第一个数…

python脚本传参

sys.argvargparse 第一种&#xff1a;argparse 简单使用&#xff1a; import argparse # 创建一个参数解析实例 parser argparse.ArgumentParser(descriptionParameters) # 添加参数解析 parser.add_argument(--training_epoch, typeint, default3000) parser.add_argument(…

使用Python计算二维截面的惯性矩和面积,中间带洞的截面也可计算,代码

因为最近到土木行业了&#xff0c;所以要计算二维截面的惯性矩和面积&#xff0c;用迈达斯太low了&#xff0c;所以想自己写python代码计算。前言 主要思路是三角网划分&#xff0c;然后计算每个小三角的惯性矩和面积&#xff0c;求和得到最终截面的惯性矩和面积。 python中可…

Ubuntu 常用命令之 chown 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 chown 命令在 Ubuntu 系统中用于改变文件或目录的所有者和组。这个命令的基本格式是 chown [选项]... [所有者][:[组]] 文件...。 chown 命令的主要参数有 -c 或 --changes&#xff1a;类似 verbose&#xff0c;但只在发生改变时…

解决xcode 运行不老iPhone 15 iOS 17.1 设备的问题

问题 最近要查看一下ios 17.1的设备的性能&#xff0c;但是当前版本的Xcode运行不了 解决方法 1、更新Xcode版本到15.1以上 2、更新完成后&#xff0c;大概率出现这个情况 原因&#xff1a;在app Store中更新到Xcode15后,运行不了模拟器和真机.需要下载iOS 17对应的模拟器.&…

Linux基本内容学习

Linux 命令 文件命令 命令释义语法格式lslist&#xff0c;用于显示目录中文件及其属性信息ls [参数名] [文件名]cdchange directory&#xff0c;用于更改当前所处的工作目录&#xff0c;路径可以是绝对路径&#xff0c;也可以是相对路径&#xff0c;若省略不写则会跳转至当前…

前端案例—antdDesign的Select多选框组件加上全选功能

前端案例—antdDesign的Select多选框组件加上全选功能。 实现效果如下&#xff1a; Select 组件里有这个属性&#xff0c;可以利用这个对下拉菜单进行自定义。 const handleChange (e, value) > {setSelectState(e.target.checked)let arr productOptions?productOption…

HarmonyOS应用事件打点开发指导

简介 传统的日志系统里汇聚了整个设备上所有程序运行的过程流水日志&#xff0c;难以识别其中的关键信息。因此&#xff0c;应用开发者需要一种数据打点机制&#xff0c;用来评估如访问数、日活、用户操作习惯以及影响用户使用的关键因素等关键信息。 HiAppEvent 是在系统层面…

【JavaWeb学习笔记】14 - 三大组件其二 Listener Filter

API文档JAVA_EE_api_中英文对照版 Listener 一、监听器Listener 1. Listener监听器它是JavaWeb的三大组件之一。 JavaWeb的三大组件分别是: Servlet程序、Listener监听器、Filter过滤器 2. Listener是JavaEE的规范&#xff0c;就是接口 3.监听器的作用是&#xff0c;监听某…

稳定币分析的 3 个关键指标

作者&#xff1a;lesleyfootprint.network 数据源&#xff1a;The Stablecoin Dashboard 稳定币因其固有特性而在加密货币领域中独树一帜&#xff0c;它们的特点是价格与特定的参考资产&#xff08;通常是美元或欧元等法定货币&#xff09;锚定。这种锚定机制的目的是缓解数字…