文章目录

  • 前言
  • 浅拷贝
  • 拷贝构造
    • 传统写法
    • 现代写法
  • 赋值重载
    • 传统写法
    • 现代写法
  • 尾声

前言

hello,大家好啊,今天做题时无意间碰到深浅拷贝问题,遂去学习了一番,并整理一下笔记。

浅拷贝

若未显示定义拷贝构造函数,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;namespace yzq
{class String{public:String(const char* str = ""):_str(new char[strlen(str) + 1]) // +1是为\0开的空间{strcpy(_str, str);// \0也拷过去的了}// 浅拷贝String(const String& s):_str(s._str){}String& operator=(const String& s){if (this != &s){_str = s._str;}return *this;}~String(){if (_str){delete[] _str;}_str = nullptr;}const char* c_str() const{return _str;}char& operator[](size_t pos){assert(pos < strlen(_str));return _str[pos];}size_t size(){return strlen(_str);}private:char* _str;};
}void test_string1()
{yzq::String s1("hello world");cout << s1.c_str() << endl;s1[0] = 'a';// 本质就是s1.operator[](0) = 'x'cout << s1.c_str() << endl;
}void test_string2()
{yzq::String s1("hello world");yzq::String s2(s1);cout << s1.c_str() << endl;cout << s2.c_str() << endl;
}

调试可发现 s1 s2 地址相同,也就是指向同一片空间。
浅拷贝,把s1的值拷贝给s2

s2 后创建,会先析构,析构时虽然先 delete[] _str;_str= nullptr;但操作的只是 s2 的 _str,并没有影响到 s1 的_str

而且调用s1的析构函数时,又会delete[] _str;,一块空间被 delete 了2次

而且 s1 和 s2 指向的是同一片空间,其中一个对象插入删除数据,会导致另一个对象也会被影响。

程序会崩溃。

拷贝构造

传统写法

class string
{public:string(const char* str = ""): _str(new char[strlen(str) + 1]){strcpy(_str, str); //拷贝时 \0也会一起拷贝}// s2(s1) 去开辟和s1一样大的空间,再把内容拷贝过去string(const string& s): _str(new char[strlen(s._str)] + 1){strcpy(_str, s._str);}~string(){cout << "~string()" << endl;delete[] _str;_str = nullptr;}
private:char* _str;
};
void Test()
{yzq::String s1("hello");yzq::String s2(s1);
}

地址不一样了,已经不是一块空间了。

对于拷贝构造函数来说,s2先开一块和s1一样大小的空间即可。

而对于赋值运算符重载函数来说s2已经存在,则必须先释放s2的空间然后才让s2开与s1一样大的空间

然后再让s2指向这片空间,再拷贝s1的数据到s2里面。

现代写法

void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}// 传统写法的拷贝构造深拷贝,老老实实去干活,该开空间就开空间,该拷贝数据就拷贝数据
// 现代写法:一种剥削行为,安排别人去干活
string(const string& s):_str(nullptr),_size(0),_capacity(0){// 就是去利用传过来的s的_str去构造新的tmp// tmp是个临时对象,出作用域时会销毁// 而swap过去的指针是随机值,delete时可能会崩溃,因此自身的成员变量最好初始化一下string tmp(s._str);/*swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);*/// 利用自己写的swap函数swap(tmp);}void test_string13()
{yzq::string s1("hello world");yzq::string s2(s1);cout << s2 << endl;yzq::string s3;s3 = s1;cout << s3 << endl;
}

赋值重载

传统写法

针对已经定义出来的对象的赋值拷贝。

赋值运算符的重载也是一个默认成员函数,我们不写,编译器也会自动生成。
默认生成的赋值运算符,特性和拷贝构造一致。

  1. 针对内置类型,会完成浅拷贝。
  2. 针对自定义类型,会调用它的赋值运算符重载完成拷贝。

赋值重载需要考虑空间不够或者空间浪费的问题,因此最好先把目的空间delete一下。
又考虑到开辟空间失败的问题,如果开辟失败了,目的的空间却也已经被释放了,不合适,因此先开辟空间再释放目的地空间。

将s3赋值给s1。

难道直接拷过去就行了吗?
万一s1空间不够了,那不就还得先扩容再拷贝吗?
扩容有性能消耗,且有可能产生空间浪费。那我们还是重新开辟个一样大小的空间好了。

#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;namespace yzq
{class String{public:String(const char* str = ""):_str(new char[strlen(str) + 1]) // +1是为\0开的空间{strcpy(_str, str);}//s2(s1) s1传给s,s2传给this指针String(const String& s):_str(new char[strlen(s._str) + 1]){strcpy(_str, s._str);}// s1 = s3 也就是s1.operator=(&s1, s3)String& operator=(const String& s){if (this != &s) // 避免自己赋值给自己{/* 万一开空间失败了,s1却已经被释放了delete[] _str;//释放s1的空间_str = nullptr;_str = new char[strlen(s._str) + 1];// 开辟和s3一样大的空间strcpy(_str, s._str);*/// 先开空间比较合适char* tmp = new char[strlen(s._str) + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;}return *this; // 为了支持连续赋值,返回左操作数}// 如果用传值返回,又是一个深拷贝,代价比较大。~String(){if (_str){delete[] _str;}_str = nullptr;}const char* c_str() const{return _str;}char& operator[](size_t pos){assert(pos < strlen(_str));return _str[pos];}size_t size(){return strlen(_str);}private:char* _str;};
}void test_string2()
{yzq::String s1("hello world");yzq::String s2(s1);cout << s1.c_str() << endl;cout << s2.c_str() << endl;yzq::String s3("123456");s1 = s3;cout << s3.c_str() << endl;
}int main()
{try{test_string2();}catch (const std::exception& e){cout << e.what() << endl;}return 0;
}

现代写法

其实就是调用其他函数来实现自己的功能。

用 s1 拷贝构造一个 s2 对象s2(s1)可以通过构造函数将 s1 里的指针_str构造一个临时对象 tmp(构造函数不仅会开空间还会将数据拷贝至tmp),此时 tmp 就是我们想要的那个对象,然后将tmp 的指针_str与自己的指针进行交换。

void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}// 赋值重载现代写法1 s3 = s1
string& operator=(const string& s)
{if (this != &s){string tmp(s._str);swap(tmp);}return *this;
}
// 原来的s3交换给tmp了,tmp是临时对象,出作用域时顺带xiao'h
// 现代写法2 更加剥削 s3 = s1
// s1传过来直接就是拷贝构造 拷贝构造完成深拷贝后 再直接交换
string& operator=(string s)
{swap(s);return *this;
}

尾声

初步探索C++深浅拷贝相关推荐

  1. 了解一下ES6: 函数简述深浅拷贝

    标准开头 今天我们来看一下ES6的函数部分知识 函数 函数初始值 有时候,函数的非必填参数,我们可以给予其默认值.保证程序完整不会出错 在早期,我们赋初始值可能是这样做的: // 早期ES5方法 fu ...

  2. Redis主从复制架构初步探索 http://www.sxt.cn/info-1750-u-324.html#SXT_h2_11

    Redis主从复制架构初步探索 目录http://www.sxt.cn/info-1750-u-324.html#SXT_h2_11 ·  一.主从复制架构简介 ·  1.1 源于关系数据库的读写分离 ...

  3. 关于Java中的HashMap的深浅拷贝的测试与几点思考

    0.前言 工作忙起来后,许久不看算法,竟然DFA敏感词算法都要看好一阵才能理解...真是和三阶魔方还原手法一样,田园将芜,非常可惜啊. 在DFA算法中,第一步是需要理解它的数据结构,在此基础上,涉及到 ...

  4. 9012年,当我们讨论js深浅拷贝时我们在说些什么?

    前言: 本文主要阅读对象:对深浅拷贝印象模糊对初级前端,想对js深浅拷贝聊一聊的中级前端.如果是对这些有完整对认知体系和解决方法的大佬,可以选择略过. 复制代码 正文: 讨论深浅拷贝,首先要从js的基 ...

  5. python基础(三元运算+深浅拷贝+函数参数)

    三元运算 三元运算,又称三目运算,主要作用是减少代码量,是对简单的条件语句的缩写. 1 书写格式: 2 result = 值1 if 条件 else 值2 3 即如果条件成立,则将值1赋给result ...

  6. python——赋值与深浅拷贝

    结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~ 预备知识一--python的变量及其存储 在详细的了解python中赋值.copy和deepcopy之前,我们还是要花一点时间来 ...

  7. python之深浅拷贝

    对于 数字 和 字符串 而言,赋值.浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址. import copy # ######### 数字.字符串 ######### n1 = 123 # n1 ...

  8. Java中深浅拷贝之List

    List的浅拷贝 浅拷贝将引用复制.指向的是同一个地址,改变A和B中的任何一个,另一个都会随之发生变化. List浅拷贝(针对List里 装的是对象-----"引用"[基础类型不分 ...

  9. Python的深浅拷贝讲解!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:皮钱超,厦门大学,Datawhale原创作者 本文约3000字,建 ...

  10. 【Python基础】Python的深浅拷贝讲解

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 前言 在很多语言中都存在深浅拷贝两种拷贝数据的方式,Python中 ...

最新文章

  1. java栈 迷宫_利用栈实现迷宫的求解
  2. java-在应用中获取spring定义的bean
  3. 用神经网络测量训练集的半衰期
  4. HDU1328 ZOJ1240 IBM Minus One【水题】
  5. html area标签用例,CSS中的html – tabindex
  6. 【转帖】LoadRunner监控Linux与Windows方法
  7. EVE上传Dynamips、IOL和QEMU镜像
  8. c语言编写词库_藏拙简易中文分词服务器(C语言开发+词库+源代码)
  9. 资产配置那些事-标准普尔家庭资产象限图
  10. 【前端面试题】数据类型-js
  11. 《十二生肖运程图》网站欣赏
  12. Avant浏览器的插件妙用
  13. 以一次失败的沟通,来聊聊技术人员沟通中常见的几个问题
  14. java 法定节假日_【java】如何获得每年的法定节假日期以及全年的日历
  15. Java面试题(上)
  16. 设计模式|职责链模式--流程状态审批(枚举实现)
  17. 中国100句绝美爱情诗
  18. CAD机械图纸转PNG图片怎么设置输出的色彩和背景颜色—迅捷CAD转换器
  19. 解决PC端的的TIM群聊界面无法显示公告、文件、记录栏,不显示群消息
  20. 解三元一次方程组的计算机,解三元一次方程组

热门文章

  1. matlab信道容量函数,一般信道容量迭代算法1
  2. Android studio Android源码开发环境搭建
  3. 轻轻一扭,迎来温暖柔风,告别手脚冰凉,卡蛙桌面暖风机上手
  4. 如何让网站被百度快速收录
  5. 工业镜头景深计算及工业镜头技术分析
  6. android修改渠道,Android 多渠道定制化打包
  7. opencv检测中线
  8. python考拉兹猜想_Python练习题 042:Project Euler 014:最长的考拉兹序列
  9. 计算机学院学生王鹏,王鹏-计算机与控制工程学院
  10. 2021年全球排烟扇收入大约181百万美元,预计2028年达到198.7百万美元