C++浅拷贝和深拷贝

article/2025/10/5 14:24:49


1、浅拷贝

浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。 举个简单的例子,你的小名叫西西,大名叫冬冬,当别人叫你西西或者冬冬的时候你都会答应,这两个名字虽然不相同,但是都指的是你。

假设有一个String类,String s1;String s2(s1);在进行拷贝构造的时候将对象s1里的值全部拷贝到对象s2里。

我们现在来简单的实现一下这个类:

#include <iostream>
#include<cstring>
using namespace std;class STRING 
{
public:STRING( const char* s = "" ) :_str( new char[strlen(s)+1] ){strcpy_s( _str, strlen(s)+1, s );}STRING( const STRING& s ){_str = s._str;}STRING& operator=(const STRING& s){if (this != &s){this->_str = s._str;}return *this;}~STRING(){cout << "~STRING" << endl;if (_str){delete[] _str;_str = NULL;}}void show(){cout << _str << endl;}private:char* _str;
};int main()
{STRING s1("hello linux");STRING s2(s1);s2.show();return 0;
} 

其实,这个程序是存在问题的,什么问题呢?
我们想以下,创建s2的时候程序必然会去调用拷贝构造函数,这时候拷贝构造函数仅仅只是完成了值拷贝,导致两个指针指向了同一块区域。随着程序程序的运行结束,又去调用析构函数,先是s2去调用析构函数,释放了它指向的内存区域,接着s1又去调用析构函数,这时候析构函数企图释放一块已经被释放的内存区域,程序将会崩溃。

s1和s2的关系就是这样的:
在这里插入图片描述
进行调试时发现s1和s2确实指向了同一块内存:
在这里插入图片描述

浅拷贝问题:

(1)如果类中叧包含简单数据成员,没有指向堆的指针, 可以使用编译器提供的默认复制构造函数。
(2)如果类中包含指向堆中数据的指针,浅复制将出现 严重问题

  • 浅复制直接复制两个对象间的指针成员,导致两个指针 指向堆中同一坑内存区域。
  • 一个对象的修改将导致另一个对象的修改。
  • 一个对象超出作用域,将导致内存释放,使得另一个对 象的指针无效,对其访问将导致程序异常。

那么这个问题应该怎么去解决呢?这就引出了深拷贝。


2、深拷贝

2.1 深拷贝定义

深拷贝,拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指向了不同的内存位置。 并且里面的内容是一样的,这样不但达到了我们想要的目的,还不会出现问题,两个指针先后去调用析构函数,分别释放自己所指向的位置。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。

深拷贝实际上是这样的:
在这里插入图片描述

2.2 深拷贝的拷贝构造函数和赋值运算符的重载传统实现

STRING( const STRING& s )
{//_str = s._str;_str = new char[strlen(s._str) + 1];strcpy_s( _str, strlen(s._str) + 1, s._str );
}STRING& operator=(const STRING& s)
{if (this != &s){//this->_str = s._str;delete[] _str;this->_str = new char[strlen(s._str) + 1];strcpy_s(this->_str, strlen(s._str) + 1, s._str);}return *this;
}

这里的拷贝构造函数我们很容易理解,先开辟出和源对象一样大的内存区域,然后将需要拷贝的数据复制到目标拷贝对象,那么这里的赋值运算符的重载是怎么样做的呢?
在这里插入图片描述
这种方法解决了我们的指针悬挂问题,通过不断的开空间让不同的指针指向不同的内存,以防止同一块内存被释放两次的问题.


2.3 深拷贝的现代写法

STRING( const STRING& s ):_str(NULL)
{STRING tmp(s._str);// 调用了构造函数,完成了空间的开辟以及值的拷贝swap(this->_str, tmp._str); //交换tmp和目标拷贝对象所指向的内容
}STRING& operator=(const STRING& s)
{if ( this != &s )//不让自己给自己赋值{STRING tmp(s._str);//调用构造函数完成空间的开辟以及赋值工作swap(this->_str, tmp._str);//交换tmp和目标拷贝对象所指向的内容}return *this;
}

我们先来分析拷贝构造是怎么实现的:
在这里插入图片描述
拷贝构造调用完成之后,会接着去调用析构函数来销毁局部对象tmp,按照这种思路,不难可以想到s2的值一定和拷贝构造里的tmp的值一样,指向同一块内存区域,通过调试可以看出来:

在拷贝构造函数里的tmp:
在这里插入图片描述
调用完拷贝构造后的s2:(此时tmp被析构)
在这里插入图片描述
可以看到s2的地址值和拷贝构造里的tmp的地址值是一样。

关于赋值运算符的重载还可以这样来写:

STRING& operator=(STRING s)
{swap(_str, s._str);return *this;
}
#include <iostream>
#include<cstring>
using namespace std;class STRING 
{
public:STRING( const char* s = "" ) :_str( new char[strlen(s)+1] ){strcpy_s( _str, strlen(s)+1, s );}//STRING( const STRING& s )//{//    //_str = s._str; //浅拷贝的写法//    cout << "拷贝构造函数" << endl;//    _str = new char[strlen(s._str) + 1];//    strcpy_s( _str, strlen(s._str) + 1, s._str );//}//STRING& operator=(const STRING& s)//{//    cout << "运算符重载" << endl;//    if (this != &s)//    {//        //this->_str = s._str; //浅拷贝的写法//        delete[] _str;//        this->_str = new char[strlen(s._str) + 1];//        strcpy_s(this->_str, strlen(s._str) + 1, s._str);//    }//    return *this;//}STRING( const STRING& s ):_str(NULL){STRING tmp(s._str);// 调用了构造函数,完成了空间的开辟以及值的拷贝swap(this->_str, tmp._str); //交换tmp和目标拷贝对象所指向的内容}STRING& operator=(const STRING& s){if ( this != &s )//不让自己给自己赋值{STRING tmp(s._str);//调用构造函数完成空间的开辟以及赋值工作swap(this->_str, tmp._str);//交换tmp和目标拷贝对象所指向的内容}return *this;}~STRING(){cout << "~STRING" << endl;if (_str){delete[] _str;_str = NULL;}}void show(){cout << _str << endl;}
private:char* _str;
};int main()
{//STRING s1("hello linux");//STRING s2(s1);//STRING s2 = s1;//s2.show();const char* str = "hello linux!";STRING  s1(str);STRING s2;s2 = s1;s1.show();s2.show();return 0;
}

http://chatgpt.dhexx.cn/article/wYxomDPL.shtml

相关文章

彻底理解Python中浅拷贝和深拷贝的区别

目录 前言 1. 浅拷贝和深拷贝的概念 2. is和的区别 3. 赋值操作 4. copy模块里面的copy()方法 5. copy模块里面的deepcopy()方法 6.字典自带的copy方法 7.切片表达式拷贝 8.总结 前言 Python 的所有变量其实都是指向内存中的对象的一个指针&#xff0c;这确实和之前学…

如何理解java的回调函数?

对于技术问题&#xff0c;会用是一回事&#xff0c;理解这个技术问题的来龙去脉、设计者当初为什么要设计这个功能、这个技术问题有哪些优势、适用哪些场景又是另外回事了。 前者照猫画虎得其形&#xff0c;后者形神兼备得其意&#xff0c;这也是所谓青铜与王者的区别。 会使…

java使用回调函数

java回调函数 回调函数&#xff08;callback Function&#xff09;&#xff0c;顾名思义就是用来回调的函数。在两个类A、B中&#xff0c;A在调用B接口的同时B也在调用A 回调函数也常用于线程中的异步获取消息。 举个简单的例子&#xff0c;公司中老板分发任务给员工A&#…

java中回调函数的实现

在java的事务中&#xff0c;有时候可能会遇到以下情况&#xff0c;第一步是更新某某表&#xff0c;中间可能要更新不确定的多步&#xff0c;最后一步是更新缓存&#xff0c;结构大致如下&#xff1a; &#xff08;1&#xff09;updateA(); &#xff08;2&#xff09;updateXX…

什么是java回调函数

回调函数 一&#xff1a;故事背景二&#xff1a;概念三&#xff1a;回调函数的作用四&#xff1a;java中如何进行回调4.1 类图4.2 定义回调接口4.3 实现回调接口4.4 调用方法使用回调函数4.5 Main函数调用4.6 总结描述 五&#xff1a;回调函数的优点5.1 灵活性5.2 解耦性5.3 异…

简单举例JAVA回调函数的实现

来自维基百科的对回调&#xff08;Callback&#xff09;的解释&#xff1a;In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execut…

java回调函数机制

Java回调函数机制 参考了网上的一些资料&#xff0c;下面也做出一些总结&#xff0c;供初学者了解学习。 一、 概述 软件模块之间总是存在着一定的接口&#xff0c;从调用方式上&#xff0c;可以把他们分为三类&#xff1a;同步调用、回调、异步调用 。 同步调用&#xff1a;一…

java中如何实现回调函数

最近工作需要研究了一会别人写的库&#xff0c;其中充满着各种"回调函数"&#xff0c;因此把自己理解给记录下来&#xff0c;存档。 首先我们来看看回调函数 这个概念的具体由来&#xff0c;百度百科的示义如下&#xff1a; 回调函数就是一个通过函数指针调用的函数。…

Java回调函数 + 使用案例

文章目录 前言什么是回调函数第0个版本第1个版本第2个版本第3个版本第4个版本第5个版本第6个版本回头解析前言描述的问题1. MethodIntrospector.selectMethods()2. 抽象类MethodIntrospector3. 方法selectMethods()4. 成员变量USER_DECLARED_METHODS5. 方法doWithMethods()6. d…

Java-回调函数

什么是回调 函数调用可以分为三种模式&#xff0c;串行调用、异步调用、回调。这三种都是日常开发中常见到的方式。 一个方法执行完&#xff0c;再执行下一个&#xff0c;串行逻辑会阻塞线程执行流程&#xff0c;等到所有逻辑执行结束&#xff0c;线程才会结束。 异步执行是…

java回调函数(callBack)

最近有个新同事给我写了个接口&#xff0c;说是用到了回调&#xff0c;我等了半天发现结果才返回回来&#xff0c;把我都整急了。最后我看了他的代码&#xff0c;目瞪口呆。他还信誓旦旦的说没错&#xff0c;按网上的例子来写的。 我一搜&#xff0c;网上例子真一大堆&#xff…

java回调函数的作用以及运用

模块之间总是存在这一定的接口&#xff0c;从调用方式上看&#xff0c;可以分为三类&#xff1a;同步调用、回调和异步调用。同步调用是一种阻塞式调用&#xff0c;也是我们在写程序中经常使用的&#xff1b;回调是一种双向的调用模式&#xff0c;也就是说&#xff0c;被调用的…

Java回调函数理解和应用

#Java回调函数理解和应用 所谓回调&#xff1a;就是A类中调用B类中的某个方法C&#xff0c;然后B类中反过来调用A类中的方法D&#xff0c;D这个方法就叫回调方法&#xff0c;这样子说你是不是有点晕晕的。 在未理解之前&#xff0c;我也是一脸懵逼&#xff0c;等我理解之后&…

java回调函数(全干货)

产生接口回调的场景 产生接口回调的场景很简单,比如A叫B帮忙做一件事,交代完后A去忙别的事&#xff0c;然后B做完这件事之后会通知A, 通知A的这个动作就是接口回调的动作。接口回调 接口回调的意义是通过接口来实现解耦的的前提下调用另一个类的方法&#xff0c;也就是B为A准…

深入理解Java回调函数

废话不多说&#xff0c;像许多网上介绍回调机制的文章一样&#xff0c;我这里也以一个现实的例子开头&#xff1a;假设你公司的总经理出差前需要你帮他办件事情&#xff0c;这件事情你需要花些时间去做&#xff0c;这时候总经理肯定不能守着你做完再出差吧&#xff0c;于是就他…

java 回调函数解读

模块间调用 在一个应用系统中&#xff0c;无论使用何种语言开发&#xff0c;必然存在模块之间的调用&#xff0c;调用的方式分为几种&#xff1a; &#xff08;1&#xff09;同步调用 同步调用是最基本并且最简单的一种调用方式&#xff0c;类A的方法a()调用类B的方法b()&…

java中的回调函数

CALLBACK&#xff0c;即回调函数&#xff0c;是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用为调用它所指向的函数时&#xff0c;我们就说这是回调函数。回调函数不是由该函数的实现方直接…

Java回调函数详解

什么是回调函数&#xff08;CallBack&#xff09; 在编写程序时&#xff0c;有时候会调用许多API中实现实现的函数&#xff0c;但某些方法需要我们传入一个方法&#xff0c;以便在需要的时候调用我们传入进去的函数。这个被传入的函数称为回调函数&#xff08;Callback functi…

numpy 数据类型转换

参考NumPy 数据类型 - 云社区 - 腾讯云 首先需要导入numpy模块 import numpy as np 首先生成一个浮点数组 a np.random.random(4) dtype的用法 看看结果信息&#xff0c;左侧是结果信息&#xff0c;右侧是对应的python语句 我们发现这个数组的type是float64&#xff0c;…

javascript学习之数据类型转换

⭐️⭐️⭐️ 作者&#xff1a;船长在船上 &#x1f6a9;&#x1f6a9;&#x1f6a9; 主页&#xff1a;来访地址船长在船上的博客 &#x1f528;&#x1f528;&#x1f528; 简介&#xff1a;资深前端开发工程师&#xff0c;专注前端开发&#xff0c;欢迎咨询交流&#xff0…