当前位置: 首页 > 编程日记 > 正文

C++深拷贝与浅拷贝

浅拷贝就是成员数据之间的一一赋值:把值赋给一一赋给要拷贝的值。但是可能会有这样的情况:对象还包含资源,这里的资源可以值堆资源,或者一个文件。。当 值拷贝的时候,两个对象就有用共同的资源,同时对资源可以访问,这样就会出问题。深拷贝就是用来解决这样的问题的,它把资源也赋值一次,使对象拥有不同的 资源,但资源的内容是一样的。对于堆资源来说,就是在开辟一片堆内存,把原来的内容拷贝。

如果你拷贝的对象中引用了某个外部的内容(比如分配在堆上的数据),那么在拷贝这个对象的时候,让新旧两个对象指向同一个外部的内容,就是浅拷贝;如果在拷贝这个对象的时候为新对象制作了外部对象的独立拷贝,就是深拷贝

引用和指针的语义是相似的,引用是不可改变的指针,指针是可以改变的引用。其实都是实现了引用语义。
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。

COW语义是“深拷贝”与“推迟计算”的组合,仍然是深拷贝,而非浅拷贝,因为拷贝之后的两个对象的数据在逻辑上是不相关的,只是内容相同。

无论深浅,都是需要的。当深拷贝发生时,通常表明存在着一个“聚合关系”,而浅拷贝发生时,通常表明存在着一个“相识关系”。
举个简单的例子:
当你实现一个Composite Pattern,你通常都会实现一个深拷贝(如果需要拷贝的话),很少有要求同的Composite共享Leaf的;
而当你实现一个Observer Pattern时,如果你需要拷贝Observer,你大概不会去拷贝Subject,这时就要实现个浅拷贝。
是深拷贝还是浅拷贝,并不是取决于时间效率、空间效率或是语言等等,而是取决于哪一个是逻辑上正确的

//--------------------------------------------------------------------------------


在学习这一章内容前我们已经学习过了类的构造函数和析构函数的相关知识,对于普通类型的对象来说,他们之间的复制是很简单的,例如:

int a = 10;
int b =a;


自己定义的类的对象同样是对象,谁也不能阻止我们用以下的方式进行复制,例如:

#include <iostream>
usingnamespacestd;

classTest
{
public:
Test(inttemp)
{
p1=temp;
}
protected:
intp1;

};

voidmain()
{
Test a(99);
Test b=a;
}


普通对象和类对象同为对象,他们之间的特性有相似之处也有不同之处,类对象内部存在 成员变量,而普通对象是没有的,当同样的复制方法发生在不同的对象上的时候,那么系统对他们进行的操作也是不一样的,就类对象而言,相同类型的类对象是通 过拷贝构造函数来完成整个复制过程的,在上面的代码中,我们并没有看到拷贝构造函数,同样完成了复制工作,这又是为什么呢?因为当一个类没有自定义的拷贝 构造函数的时候系统会自动提供一个默认的拷贝构造函数,来完成复制工作。

下面,我们为了说明情况,就普通情况而言(以上面的代码为例),我们来自己定义一个与系统默认拷贝构造函数一样的拷贝构造函数,看看它的内部是如何工作的!


代码如下:

#include <iostream>
usingnamespacestd;

classTest
{
public:
Test(inttemp)
{
p1=temp;
}
Test(Test &c_t)//这里就是自定义的拷贝构造函数
{
cout<<"进入copy构造函数"<p1=c_t.p1;//这句如果去掉就不能完成复制工作了,此句复制过程的核心语句
}
public:
intp1;
};

voidmain()
{
Test a(99);
Test b=a;
cout<cin.get();
}


上面代码中的Test(Test &c_t)就是我们自定义的拷贝构造函数,拷贝构造函数的名称必须与类名称一致,函数的形式参数是本类型的一个引用变量,且必须是引用。

当用一个已经初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝 构造函数就会被自动调用,如果你没有自定义拷贝构造函数的时候系统将会提供给一个默认的拷贝构造函数来完成这个过程,上面代码的复制核心语句就是通过 Test(Test &c_t)拷贝构造函数内的p1=c_t.p1;语句完成的。如果取掉这句代码,那么b对象的p1属性将得到一个未知的随机值;

下面我们来讨论一下关于浅拷贝和深拷贝的问题。

就上面的代码情况而言,很多人会问到,既然系统会自动提供一个默认的拷贝构造函数来 处理复制,那么我们没有意义要去自定义拷贝构造函数呀,对,就普通情况而言这的确是没有必要的,但在某写状况下,类体内的成员是需要开辟动态开辟堆内存 的,如果我们不自定义拷贝构造函数而让系统自己处理,那么就会导致堆内存的所属权产生混乱,试想一下,已经开辟的一端堆地址原来是属于对象a的,由于复制 过程发生,b对象取得是a已经开辟的堆地址,一旦程序产生析构,释放堆的时候,计算机是不可能清楚这段地址是真正属于谁的,当连续发生两次析构的时候就出 现了运行错误。

为了更详细的说明问题,请看如下的代码。

#include <iostream>
usingnamespacestd;

classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"载入构造函数"<strcpy(Internet::name,name);
strcpy(Internet::address,address);
cname=newchar[strlen(name)+1];
if(cname!=NULL)
{
strcpy(Internet::cname,name);
}
}
Internet(Internet &temp)
{
cout<<"载入COPY构造函数"<strcpy(Internet::name,temp.name);
strcpy(Internet::address,temp.address);
cname=newchar[strlen(name)+1];//这里注意,深拷贝的体现!
if(cname!=NULL)
{
strcpy(Internet::cname,name);
}
}
~Internet()
{
cout<<"载入析构函数!";
delete[] cname;
cin.get();
}
voidshow();
protected:
charname[20];
charaddress[30];
char*cname;
};
voidInternet::show()
{
cout<}
voidtest(Internet ts)
{
cout<<"载入test函数"<}
voidmain()
{
Internet a("中国软件开发实验室","www.cndev-lab.com");
Internet b =a;
b.show();
test(b);
}


上面代码就演示了深拷贝的问题,对对象b的cname属性采取了新开辟内存的方式避免了内存归属不清所导致析构释放空间时候的错误,最后我必须提一下,对于上面的程序我的解释并不多,就是希望读者本身运行程序观察变化,进而深刻理解。

深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷贝。

浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错,这点尤其需要注意!


以前我们的教程中讨论过函数返回对象产生临时变量的问题,接下来我们来看一下在函数中返回自定义类型对象是否也遵循此规则产生临时对象!

先运行下列代码:

#include <iostream>
usingnamespacestd;

classInternet
{
public:
Internet()
{

};
Internet(char*name,char*address)
{
cout<<"载入构造函数"<strcpy(Internet::name,name);
strcpy(Internet::address,address);
}
Internet(Internet &temp)
{
cout<<"载入COPY构造函数"<strcpy(Internet::name,temp.name);
strcpy(Internet::address,temp.address);
cin.get();
}
~Internet()
{
cout<<"载入析构函数!";
cin.get();
}
protected:
charname[20];
charaddress[20];
};
Internet tp()
{
Internet b("中国软件开发实验室","www.cndev-lab.com");
returnb;
}
voidmain()
{
Internet a;
a=tp();
}


从上面的代码运行结果可以看出,程序一共载入过析构函数三次,证明了由函数返回自定义类型对象同样会产生临时变量,事实上对象a得到的就是这个临时Internet类类型对象temp的值。

这一下节的内容我们来说一下无名对象。

利用无名对象初始化对象系统不会不调用拷贝构造函数。

那么什么又是无名对象呢?

很简单,如果在上面程序的main函数中有:

Internet ("中国软件开发实验室","www.cndev-lab.com");

这样的一句语句就会产生一个无名对象,无名对象会调用构造函数但利用无名对象初始化对象系统不会不调用拷贝构造函数!

下面三段代码是很见到的三种利用无名对象初始化对象的例子。

#include <iostream>
usingnamespacestd;

classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"载入构造函数"<strcpy(Internet::name,name);
}
Internet(Internet &temp)
{
cout<<"载入COPY构造函数"<strcpy(Internet::name,temp.name);
cin.get();
}
~Internet()
{
cout<<"载入析构函数!";
cin.get();
}
public:
charname[20];
charaddress[20];
};

voidmain()
{
Internet a=Internet("中国软件开发实验室","www.cndev-lab.com");
cout<cin.get();
}


上面代码的运行结果有点“出人意料”,从思维逻辑上说,当无名对象创建了后,是应该调用自定义拷贝构造函数,或者是默认拷贝构造函数来完成复制过程的,但事实上系统并没有这么做,因为无名对象使用过后在整个程序中就失去了作用,对于这种情况c++会把代码看成是:

Internet a("中国软件开发实验室","www.cndev-lab.com");

省略了创建无名对象这一过程,所以说不会调用拷贝构造函数。

最后让我们来看看引用无名对象的情况。

#include <iostream>
usingnamespacestd;

classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"载入构造函数"<strcpy(Internet::name,name);
}
Internet(Internet &temp)
{
cout<<"载入COPY构造函数"<strcpy(Internet::name,temp.name);
cin.get();
}
~Internet()
{
cout<<"载入析构函数!";
}
public:
charname[20];
charaddress[20];
};

voidmain()
{
Internet &a=Internet("中国软件开发实验室","www.cndev-lab.com");
cout<cin.get();
}


引用本身是对象的别名,和复制并没有关系,所以不会调用拷贝构造函数,但要注意的是,在c++看来:


Internet &a=Internet("中国软件开发实验室","www.cndev-lab.com");


是等价与:

Internet a("中国软件开发实验室","www.cndev-lab.com");

的,注意观察调用析构函数的位置(这种情况是在main()外调用,而无名对象本身是在main()内析构的)。

摘自:http://blog.chinaunix.net/u/25952/showart_274326.html

转载于:https://www.cnblogs.com/phoenixzq/archive/2010/12/04/1896014.html

相关文章:

【OpenCV】使用过的函数汇总

1、类 Mat&#xff1a;矩阵matrix&#xff0c;opencv2中主要用来封装图片数据 InputArray&#xff1a;输入参数 &#xff0c;约等于Mat OutputArray&#xff1a;输出参数&#xff0c;约等于Mat Rect&#xff1a;表示矩形 2、函数 imread();//从文件中读取图片到Mat中 imwrit…

春招来袭!程序员如何拿下硅谷顶级公司200万年薪?

还记得那个在去年&#xff0c;用 6 天时间参加了 LinkedIn、Yelp、Apple、亚马逊、Facebook 和 Google 的面试&#xff0c;并拿下了 6 份 Offer 的“别人家的程序员”吗&#xff1f;之后&#xff0c;他又在这几份工作中进行了选择&#xff0c;一步步谈下了 30 万美元&#xff0…

wake_lock_timeout的使用方法【转】

本文转载自&#xff1a;http://blog.csdn.net/liuxd3000/article/details/44224849 今天有用到用ec43_GPIO的中断来唤醒系统&#xff0c;将系统从深度休眠中唤醒并保证系统wakup 一段时间用过了&#xff0c;方法如下&#xff0c;有同样使用的童鞋可以参考一下&#xff01;1. …

函数05 - 零基础入门学习C语言36

第七章&#xff1a;函数05 让编程改变世界 Change the world by program 函数的嵌套调用 嵌套定义就是在定义一个函数时&#xff0c;其函数体内又包含另一个函数的完整定义。 然而&#xff0c;&#xff23;语言不能嵌套定义函数&#xff0c;但可以嵌套调用函数&#xff0c;…

【linux】可执行程序执行时报错-sh: ./mxc_v4l2_tvin.out: No such file or directory的解决方法

问题 在imx6开发板上执行 ./mxc_v4l2_tvin.out时报错&#xff1a; -sh: ./mxc_v4l2_tvin.out: No such file or directory 原因查找 1、路径问题&#xff1a;确定在当前目录下有该程序&#xff0c;没问题&#xff1b; 2、使用ls -l查看可以执行权限&#xff0c;没问题&…

展望2018:WebRTC大规模商用元年

历经6年长跑&#xff0c;WebRTC终于在去年迎来了1.0标准&#xff08;candidate recommendation&#xff09;的发布&#xff0c;而它也将成为2018年视频通信商业应用场景爆发的主要技术推动力。一站式WebRTC通信技术提供商Zealcomm公司创始人、CEO冯昶对WebRTC在国内外发展历程、…

暴雪游戏遭遇AI“实力”坑队友:四处游走,还不参与战斗

作者 | 琥珀 出品 | AI科技大本营&#xff08;ID: rgznai100&#xff09; 60s测试&#xff1a;你是否适合转型人工智能&#xff1f; https://edu.csdn.net/topic/ai30?utm_sourcecxrs_bw “打游戏 AI 将完胜人类&#xff01;&#xff1f;” 抱歉&#xff0c;这个 Flag 还是不…

linux/nginx 安全增强

这有一篇很好的文章. 评论中有好的补充 http://www.cyberciti.biz/tips/linux-unix-bsd-nginx-webserver-security.html转载于:https://www.cnblogs.com/pengxl/archive/2010/12/08/1900175.html

十年程序员的告诫:千万不要重写代码!

对重写代码说不。 作者 | Roman Luzgin 译者 | 苏本如 责编 | 屠敏 出品 | CSDN&#xff08;ID&#xff1a;CSDNNews&#xff09; 以下为译文&#xff1a; 重写代码消耗了12个月&#xff01; 我们从头开始重写代码浪费的时间。 你能想象在软件行业&#xff0c;12个月的时…

RabbitMQ 实战(四)消费者 ack 以及 生产者 confirms

2019独角兽企业重金招聘Python工程师标准>>> 这篇文章主要讲 RabbitMQ 中 消费者 ack 以及 生产者 confirms。 如上图&#xff0c;生产者把消息发送到 RabbitMQ&#xff0c;然后 RabbitMQ 再把消息投递到消费者。 生产者和 RabbitMQ&#xff0c;以及 RabbitMQ 和消费…

【imx6】/dev中fb和video的对应关系

imx6q关于fb和video的设备信息 设备节点 rootmyzr:/unit_tests# ls /dev/fb* -l lrwxrwxrwx 1 root root 3 Jan 1 1970 /dev/fb -> fb0 crw-rw---- 1 root video 29, 0 Jan 1 1970 /dev/fb0 crw-rw---- 1 root video 29, 1 Jan 1 1970 /dev/fb1 crw-rw---- 1 r…

flash绘图API:恋上你的CD

早上&#xff0c;我无意间碰撞到一个女孩&#xff0c;那时候&#xff0c;她匆匆忙地走了。从她的口袋里面掉下了一本陈旧的书&#xff0c;在哪里我看到她藏在书中的那封陈旧的信和cd。我好奇打开它&#xff0c;一边听着她那张cd&#xff0c;一边看她的写的信&#xff0c;忽然间…

【Ubuntu】ubuntu工具 记录shell终端的内容到文件中:script

###用法 $ script -h Usage: script [options] [file] Options: -a, --append append the output -c, --command run command rather than interactive shell -r, --return return exit code of the child process -f, --flush run flush after each write –force use outpu…

弃Java、Swift于不顾,为何选Python?

作者 | JACE HARR译者 | 姜松浩转载自 CSDN&#xff08;ID&#xff1a;CSDNNews&#xff09;以下为译文&#xff1a;刚入行的程序员总是询问他们应该从哪种语言开始&#xff0c;我告诉他们&#xff0c;他们应该首先学习 Python。以下是使用 Python 开始自学编程去探险的一些原因…

iOS事件处理,看我就够了~

该文章属于<简书 — 刘小壮>原创&#xff0c;转载请注明&#xff1a; <简书 — 刘小壮> https://www.jianshu.com/p/b0884faae603 好久没写博客了&#xff0c;前后算起来刚好有一年了。这期间博客也不是一直没变化&#xff0c;细心的同学应该能发现&#xff0c;我一…

ISO9000机房管理办法

1 总则<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1.1制定目的(1) 规范公司机房管理以及网管相关工作。1.2适用范围公司网络机房以及资讯组人员。1.3权责单位(1) 资讯组负责本办法制定、修改、废止之起草工作。(2) 总…

1400小时开源语音数据集,你想要都在这儿

整理 | 一一出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;3 月 1 日&#xff0c;由 Mozilla 基金会发起的 Common Voice 项目&#xff0c;发布新版语音识别数据集&#xff0c;包括来自 42000 名贡献者&#xff0c;超过 1400 小时的语音样本数据&#xff0c;涵盖包括…

【VirtualBox】VirtualBox使用现有的虚拟盘文件(如VHD)创建虚拟机时,报错:打开虚拟硬盘失败,“UUID already exist”的解决方法

###0、问题描述 使用现有的虚拟盘文件&#xff08;如VHD&#xff09;创建虚拟机时&#xff0c;报错&#xff1a;打开虚拟硬盘失败&#xff0c;“UUID already exists”的错误。 ###1、参考博客 https://www.cnblogs.com/xqzt/p/5053338.html https://jingyan.baidu.com/articl…

JDK10 EA版特性速览

今天收到一封邮件组的邮件&#xff0c;是关于JDK 10 First Release Candidate的&#xff0c; JDK10 b43版将作为第一个JDK10的RC版。 b43版特性包括&#xff1a; 286: Local-Variable Type Inference296: Consolidate the JDK Forest into a Single Repository304: Garbage-Col…

linux主机常用管理命令

1.杀掉MYSQL SHELL ps aux|grep mysql|grep -v grep|awk {print $2}|xargs kill -9 2.删除当前目录下0字节的文件 find -type f -size 0 -exec rm -rf {} \; 3.匹配当data里包含"donald"&#xff0c;输出第4列 awk /donald/ {print $4} data 扩展1&#xff1a; awk /…

【Qt】新安装的虚拟机,使用QtCreator第一次编译时报错:g++: Command not found

1、问题描述 新安装的虚拟机&#xff0c;使用QtCreator第一次编译时报错&#xff1a;g: Command not found &#xff08;或着报&#xff0c;make执行失败之类的错误&#xff09; 2、原因分析 新安装的虚拟机中&#xff0c;没有g。一般默认情况是&#xff0c;只安装了gcc 3…

爬一爬那些年你硬盘存过的“老师”

作者 | PayneLi 转载自Python全家桶&#xff08;ID: python-0321&#xff09; 人工智能的现状及今后发展趋势如何&#xff1f; https://edu.csdn.net/topic/ai30?utm_sourcecsdn_bw 最近在Github发现一个基于google浏览器的爬虫项目&#xff0c;此项目是由美国大神2018年开源…

python 打印调用栈

import tracebackdef BBQ():traceback.print_stack() 引入 traceback 包&#xff0c;在某个函数中执行 traceback.print_stack()。 转载于:https://www.cnblogs.com/yourstars/p/8448471.html

(转)修改ETM,用Ogre实现《天龙八部》地形与部分场景详解(附源码)

本文主要讲的是《天龙八部》游戏的地形和一部分场景的具体实现&#xff0c;使用C, Ogre1.6&#xff0c;我摸索了段时间&#xff0c;可能方法用的并不是最好的&#xff0c;但好歹实现了。文章可能讲得有点罗嗦&#xff0c;很多简单的东西都讲了。我是修改了ETM&#xff08;Edita…

【Qt】错误GL/gl.h: No such file or directory的解决方法(以及cannot find -lGL解决方法)

1、问题描述 QtCreator第一次编译时&#xff0c;报错GL/gl.h: No such file or directory 错误信息如下&#xff1a; /home/Qt5.6.3/5.6.3/gcc_64/include/QtGui/qopengl.h:136: error: GL/gl.h: No such file or directory include <GL/gl.h> ^2、原因分析 说明系统里…

java并发之同步辅助类CyclicBarrier和CountDownLatch

CyclicBarrier 的字面意思是可循环使用&#xff08;Cyclic&#xff09;的屏障&#xff08;Barrier&#xff09;。它要做的事情是&#xff0c;让一组线程到达一个屏障&#xff08;也可以叫同步点&#xff09;时被阻塞&#xff0c;直到最后一个线程到达屏障时&#xff0c;屏障才会…

投稿近2000,NAACL 2019接收率仅为22.6%|附录取论文名单

整理 | 若名 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 人工智能的现状及今后发展趋势如何&#xff1f; https://edu.csdn.net/topic/ai30?utm_sourcecsdn_bw 最近真是学术界公布论文产出结果的火热时期&#xff0c;距离计算机视觉领域的顶级盛会 CVPR 2019…

Lucene.Net无障碍学习和使用:索引篇

项目中可能需要再次用到Lucene.Net&#xff0c;利用空闲时间写了个demo&#xff0c;主要涉及到索引的创建、删除、更新和一个简单查询。在本文示例中&#xff0c;Lucene.Net的版本是2.4.0&#xff0c;某些类和方法与最新版本或者较旧的版本有较多不同&#xff0c;希望您阅读顺利…

【ubuntu】vim中鼠标选中时变成 可视模式,不能复制的解决方法

1、问题描述 配置好vim后&#xff0c;打开一个文件&#xff0c;鼠标选中文本时&#xff0c;选中的内容变成可视模式。 可视模式&#xff0c;不能将选中内容复制到剪切板 2、解决方法 在用户根目录下&#xff0c;打开 .vimrc ~$ vi .vimrc 将set mousea 删除或注释掉 3、我的…

Ruby11 拾遗

Agenda LoopExpressionFile Read/WriteDebugProcess & ThreadLoop while a 10 while a > 0puts aa - 1 enduntil a 100until a 0puts aa - 1 endloop a 10loop dobreak if a < 0puts aa - 1 end循环控制 breaknextbreak for x in 1..10break if x 5puts x endne…