当前位置: 首页 > news >正文

string的深浅拷贝问题

深浅拷贝

  • 问题引入
  • 浅拷贝
  • 深拷贝
  • 总结

问题引入

对于一个普通的string类:

class String {
public:String(const char* str = ""){//构造函数if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);}~String(){//析构函数if (_str) {delete[] _str;_str = nullptr;}}
private:char* _str;
};

浅拷贝

对于上述的 String 类来说,浅拷贝也就是将一个对象原封不动的复制粘贴到新的对象当中,当类中没有给定拷贝构造函数时系统会默认生成一个拷贝构造函数,但默认生成的拷贝构造函数就属于浅拷贝:

在这里插入图片描述
当前系统默认生成的拷贝构造函数类似于:

在这里插入图片描述
那么,浅拷贝是否合理存在?

由上述运行截图我们可以观察到使用系统默认生成的拷贝构造函数或是代码中展现的拷贝构造方法构造出来的对象与原对象内容地址是完全一致的,也就类似于复制粘贴了一个相同的对象,那么它会产生什么问题?

在前边类与对象章节中,我们知道了在创建的类对象使用结束之后系统会自动调用相应的析构函数来释放资源空间(先构造的后释放,后构造的先释放),因此在该类中,首先要进行释放的是 s2,后释放 s1,我们来看看现象:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

不知道,读者是否发现了问题所在?

解析

在使用 s2(s1),s1拷贝构造 s2 对象时,我们发现创建出来的 s2 对象完全是复制粘贴 s1 对象而形成的,不仅对象内容完全相同,对象的地址空间也完全相同,这就导致了两个对象是共享同一份地址空间了,故在进行析构时,产生了同一份地址空间二次释放而发生异常提示

在这里插入图片描述

同理,在 String 类中倘若用户没有显式定义赋值运算符重载函数,则系统会默认生成一个赋值运算符重载函数,我们来看看会有什么问题?

在这里插入图片描述
显然,同样产生了浅拷贝(复制粘贴),则在进行空间销毁时候定会产生空间的二次释放异常,那么我们应该如何来解决这种问题?

介绍两种技术:深拷贝(重点)、写时拷贝

深拷贝

产生浅拷贝的主要是因为在进行拷贝构造或是赋值运算符重载时,直接将就对象的内容原封不动的粘贴给新对象,因而在空间释放时形成了多次释放问题,因此深拷贝主要就是解决不同对象分配不同地址空间的问题:

String(const String& s):_str(nullptr){//s2(s1)//首先需要给s2对象开辟一份独立的新空间_str = new char[strlen(s._str) + 1];//其次将s1对象中的内容拷贝到s2空间当中strcpy(_str, s._str);}

在这里插入图片描述

同理,赋值运算符也是一样的:

String& operator=(const String& s){if (this != &s)  //判断不是自己给自己赋值{char* tmp = new char[strlen(s._str) + 1];strcpy(tmp, s._str);delete[] _str;  //释放旧空间_str = tmp;}return *this;}

在这里插入图片描述

显然,两个对象的地址空间是不相同的,则释放空间时不会发生同一份空间多次释放的问题。

现代新写法(注意理解)

String(const String& s):_str(nullptr){//拷贝构造函数String tmp(s._str);  //采用当前对象的内容构造出一个新对象tmpswap(_str,tmp._str);   //进行资源交换,将临时空间 tmp 的内容与 当前对象的内容进行交换------------临时对象在函数周期结束自动析构释放}String& operator=(String s) {  //以值的方式传参,需要调用一次拷贝构造函数,构造出一个新的对象 s//赋值运算符重载if (this != &s) {swap(_str, s._str);   //直接进行交换}return *this;}

总结

在类中,倘若用户没有显式定义拷贝构造函数以及赋值运算符重载函数,则编译器会自动生成一份,但在使用时属于浅拷贝会造成同一份空间多个对象共享,在对象使用结束释放空间时会发生多次释放问题产生,其次在赋值运算符重载函数中,不仅会使得多个对象共享空间,还会导致空间丢失问题:

	String s1("hello");   //调用构造String s2 = "world";//使用系统默认生成的赋值运算符重载函数会导致原有的 s2 对象空间泄漏s2 = s1;

因此, 我们应该在使用时进行显式的定义,防止发生浅拷贝问题的产生

写时拷贝技术

写时拷贝技术同样是针对浅拷贝问题的解决方式

允许多个对象共享同一份地址空间以及空间中的内容,并定义一个计数器来统计当前空间所共享的对象个数,并在对象使用结束之后在析构函数内部进行计数器 - 1,当析构一个对象之后,计数器变为 0,说明当前对象是共享空间的最后一个使用者,则需要对这块空间进行释放--------------------------------但同样存在一个问题:倘若某一个对象对这块共享空间的内容进行了修改,则其他对象指向的这块共享空间内容也发生了变化

因此,写时拷贝技术是在对象需要对共享的内存空间进行修改时拷贝构造出独立的空间来进行修改,这么做就不会影响到其他对象对共享空间的访问。

ps:
博文内容为原创,欢迎各位共同学习~~

http://www.lryc.cn/news/2461.html

相关文章:

  • C++中的万能头文件
  • Java 8 Lambda 表达式 Stream
  • 【VictoriaMetrics】VictoriaMetrics单机版部署(二进制版)
  • SCI论文阅读-使用基于图像的机器学习模型对FTIR光谱进行功能组识别
  • 双11大型互动游戏“喵果总动员” 质量保障方案总结
  • 剑指Offer专项突击版题解一
  • Django框架之模型
  • OSACN-Net:使用深度学习和Gabor心电图信号谱图进行睡眠呼吸暂停分类
  • 使用开源实时监控系统 HertzBeat 5分钟搞定 Mysql 数据库监控告警
  • 插件 sortablejs:HTML元素可拖动排序
  • libVLC 视频裁剪
  • LAMP架构介绍及配置
  • Android图形显示流程简介
  • 4.5.3 ArrayList
  • 十二、Linux文件 - fseek函数讲解
  • Python3.10新特性之match语句示例详解
  • 虎牙盈利能力得到改善,但监管风险对其收入产生负面影响
  • HBase 分布式搭建
  • 【Python】修改枚举的取值及链式调用
  • 复现篇--zi2zi
  • 153、【动态规划】leetcode ——416. 分割等和子集:滚动数组(C++版本)
  • linux head命令(head指令)(获取文件或管道输出结果前n行,默认前10行)与sed命令区别
  • Mysql数据库09——分组聚合函数
  • 第43章 菜单实体及其约束规则的定义实现
  • OpenAI最重要的模型【CLIP】
  • 分享112个JS菜单导航,总有一款适合您
  • MySQL 3:MySQL数据库基本操作 DQL
  • sql语句的优化
  • Shell脚本之——自动安装JDK
  • 大数据---Hadoop安装Hadoop简易版