对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如:

class Base{public:Base(): m_a(0), m_b(0){ }Base(int a, int b): m_a(a), m_b(b){ }
private:int m_a;int m_b;
};int main(){int a = 10;int b = a;  //拷贝Base obj1(10, 20);Base obj2 = obj1;  //拷贝return 0;
}

b 和 obj2 都是以拷贝的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存,这种默认的拷贝行为就是浅拷贝,这和调用 memcpy() 函数的效果非常类似。

对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。

我们知道,有些较老的编译器不支持变长数组, 这会给编程带来不便,自定义 Array 类来实现变长数组。

#include <iostream>
#include <cstdlib>
using namespace std;//变长数组类
class Array{public:Array(int len);Array(const Array &arr);  //拷贝构造函数~Array();
public:int operator[](int i) const { return m_p[i]; }  //获取元素(读取)int &operator[](int i){ return m_p[i]; }  //获取元素(写入)int length() const { return m_len; }
private:int m_len;int *m_p;
};Array::Array(int len): m_len(len){m_p = (int*)calloc( len, sizeof(int) );
}Array::Array(const Array &arr){  //拷贝构造函数this->m_len = arr.m_len;this->m_p = (int*)calloc( this->m_len, sizeof(int) );memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}Array::~Array(){ free(m_p); }//打印数组元素
void printArray(const Array &arr){int len = arr.length();for(int i=0; i<len; i++){if(i == len-1){cout<<arr[i]<<endl;}else{cout<<arr[i]<<", ";}}
}int main(){Array arr1(10);for(int i=0; i<10; i++){arr1[i] = i;}Array arr2 = arr1;arr2[5] = 100;arr2[3] = 29;printArray(arr1);printArray(arr2);return 0;
}

运行结果:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9

本例中我们显式地定义了拷贝构造函数,它除了会将原有对象的所有成员变量拷贝给新对象,还会为新对象再分配一块内存,并将原有对象所持有的内存也拷贝过来。这样做的结果是,原有对象和新对象所持有的动态内存是相互独立的,更改一个对象的数据不会影响另外一个对象,本例中我们更改了 arr2 的数据,就没有影响 arr1。

这种将对象所持有的其它资源一并拷贝的行为叫做深拷贝,我们必须显式地定义拷贝构造函数才能达到深拷贝的目的

标准模板库(STL)中的 string、vector、stack、set、map 等也都必须使用深拷贝。

大家如果希望亲眼目睹不使用深拷贝的后果,可以将上例中的拷贝构造函数删除,那么运行结果将变为:

0, 1, 2, 29, 4, 100, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9

可以发现,更改 arr2 的数据也影响到了 arr1。这是因为,在创建 arr2 对象时,默认拷贝构造函数将 arr1.m_p 直接赋值给了 arr2.m_p,导致 arr2.m_p 和 arr1.m_p 指向了同一块内存,所以会相互影响。

注意:printArray() 函数的形参为引用类型,这样做能够避免在传参时调用拷贝构造函数;又因为 printArray() 函数不会修改任何数组元素,所以我们添加了 const 限制,以使得语义更加明确。

到底是浅拷贝还是深拷贝

如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以。

另外一种需要深拷贝的情况就是在创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等,请看下面的例子:

#include <iostream>
#include <ctime>
#include <windows.h>  //在Linux和Mac下要换成 unistd.h 头文件
using namespace std;class Base{public:Base(int a = 0, int b = 0);Base(const Base &obj);  //拷贝构造函数
public:int getCount() const { return m_count; }time_t getTime() const { return m_time; }
private:int m_a;int m_b;time_t m_time;  //对象创建时间static int m_count;  //创建过的对象的数目
};int Base::m_count = 0;Base::Base(int a, int b): m_a(a), m_b(b){m_count++;m_time = time((time_t*)NULL);
}Base::Base(const Base &obj){  //拷贝构造函数this->m_a = obj.m_a;this->m_b = obj.m_b;this->m_count++;this->m_time = time((time_t*)NULL);
}int main(){Base obj1(10, 20);cout<<"obj1: count = "<<obj1.getCount()<<", time = "<<obj1.getTime()<<endl;Sleep(3000);  //在Linux和Mac下要写作 sleep(3);Base obj2 = obj1;cout<<"obj2: count = "<<obj2.getCount()<<", time = "<<obj2.getTime()<<endl;return 0;
}

运行结果:

obj1: count = 1, time = 1488344372
obj2: count = 2, time = 1488344375

运行程序,先输出第一行结果,等待 3 秒后再输出第二行结果。Base 类中的 m_time 和 m_count 分别记录了对象的创建时间和创建数目,它们在不同的对象中有不同的值,所以需要在初始化对象的时候提前处理一下,这样浅拷贝就不能胜任了,就必须使用深拷贝了。

C++ 深拷贝和浅拷贝相关推荐

  1. Python 精选笔试面试习题—类继承、方法对象、包管理、闭包、可变类型作为默认参数、列表引用、sort与sorted、 append 和 extend、深拷贝和浅拷贝

    1. 类继承 如下代码 class A(object):def show(self):print 'This is calss A'class B(A):def show(self):print 'T ...

  2. C++深拷贝与浅拷贝

    浅拷贝就是成员数据之间的一一赋值:把值赋给一一赋给要拷贝的值.但是可能会有这样的情况:对象还包含资源,这里的资源可以值堆资源,或者一个文件..当 值拷贝的时候,两个对象就有用共同的资源,同时对资源可以 ...

  3. python的深拷贝与浅拷贝

    对于list, set, dict来说, 直接赋值. 其实是把内存地址交给变量. 并不是复制⼀份内容. 两个变量的内容其实为一个地址,如果要在复制的同时分配新的地址则需要用到深拷贝和浅拷贝的命令 ls ...

  4. 详谈Javascript中的深拷贝和浅拷贝

    数据复制是我们编程中经常会使用到的技术,对于普通数值数据来说,复制很简单,但是对于复杂类型比如对象的复制,就会有很多需要考虑的东西,比如我们经常说到的深拷贝和浅拷贝. 浅拷贝 复制的对象和原始对象属性 ...

  5. python怎么避免浅拷贝_详谈Python中的深拷贝和浅拷贝

    在平时工作中,经常涉及到数据的传递,在数据传递使用过程中,可能会发生数据被修改的问题.为了防止数据被修改,就需要在传递一个副本,即使副本被修改,也不会影响原数据的使用.为了生成这个副本,就产生了拷贝. ...

  6. c++深拷贝和浅拷贝

    C++中类的拷贝有两种:深拷贝,浅拷贝:当出现类的等号赋值时,即会调用拷贝函数 一:两个的区别 1 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数--即浅拷贝,它能够完成成员的一一复制.当 ...

  7. 更清晰的认识对象——深拷贝与浅拷贝

    对象在引用的时候引用的是对象的地址,所以导致如果修改其中一个对象,就会对其他引用这个地址的对象进行修改,这种结果并不是我们想要的,这个时候我们就要用到深拷贝和浅拷贝去解决这个问题了. var a = ...

  8. 深拷贝与浅拷贝~动态内存释放有关

    浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如: class A{ public: A(int ...

  9. 深入浅出的“深拷贝与浅拷贝”

    js中的浅拷贝与深拷贝,只是针对复杂数据类型(object, Array)的复制问题.浅拷贝和深拷贝都可以实现在已有对象上再生出一份的作用.但是对象的实例是存储在堆内存中然后通过一个引用值只操作对象, ...

  10. Javascript中的深拷贝和浅拷贝

    文章目录 JavaScript中的变量类型 深拷贝和浅拷贝的理解 深拷贝和浅拷贝的实现方式 为什么需要深拷贝和浅拷贝 JavaScript中的变量类型 (1).基本类型 JavaScript中的基本类 ...

最新文章

  1. Struts2 关于返回type=chain的用法.
  2. 【转】造成segment fault,产生core dump的可能原因
  3. 微博深度学习平台架构和实践
  4. MYSQL 开启root远程登录权限
  5. 优胜劣汰有利于整个团购行业服务的提升
  6. (转)Three challenges you’re going to face when building a chatbot
  7. openeim 成片的蝴蝶兰盛开在绿树之间
  8. 诗和远方:无题(五十一)
  9. 利用XML文件的一个写日志的类!!!!!
  10. 贪心算法的几种经典例题
  11. 京东智能客服言犀启发式问答技术探秘
  12. 松翰单片机之汇编编程
  13. 离散数学 06.05 同构及同态
  14. Swagger API文档Responses中Object类型无法显示,求指引
  15. 广义相对论和狭义相对论到底是讲什么的?
  16. Django邮件应用--QQ邮箱、网易邮箱(二)
  17. 全面公测|Grafana服务:一张图表胜过千行指标日志
  18. Map集合以及Map集合的实现类Stream流的使用
  19. R语言实现 朴素贝叶斯分类
  20. ERNIE: Enhanced Representation through Knowledge Integration(百度)论文翻译

热门文章

  1. linux tcp ip c,Linux下TCP/IP编程--TCP实战(select)
  2. java中的成员变量和局部变量的区别_java中成员变量与局部变量区别分析
  3. c语言atof字母,C语言字符转换之atof()
  4. 钉钉怎么查看收到的文件 钉钉查看文件的方法
  5. C/C++如何快速区分指针数组|数组指针|函数指针|指针函数
  6. tomcat7 加载el表达式 报错 使用tomcat8得以解决
  7. python数字对应车站_python爬虫查询车站信息
  8. 怎么两边同时取ln_男生“两边铲光”发型out了?试试这4款吧,剪完清爽又帅气...
  9. flink checkpoint 恢复_Flink解析 | Apache Flink结合Kafka构建端到端的ExactlyOnce处理
  10. 构造函数和实例化原理