深拷贝&浅拷贝&写时拷贝

来源:互联网 发布:2016年淘宝刷单权重 编辑:程序博客网 时间:2024/06/08 00:34

对于C++中的深拷贝与浅拷贝,就类似一个人去模仿一个人
浅拷贝:(影子复制,也称影子克隆),就算怎样去模仿那个人,可是也不是成为本尊,只是与本尊极为相似的一个人。
深拷贝:(深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的.就像对一个人通过克隆技术,实现他的另一个。
下面我们就对浅拷贝与深拷贝做一个具体的分析吧!
1.管理字符串string
2.深拷贝与浅拷贝中,构造函数与析构函数是一致的,不影响函数的运行。
一、浅拷贝
浅拷贝:当类中有指针对象时,拷贝构造和赋值运算符重载只进行值拷贝,两个对象指向同一个空间,当对象销毁之时,该空间被释放了两次,因此程序崩溃。

下面我们就来抛一段具体的代码来分析吧!

//浅拷贝,,只拷贝内存中的值,如果调用这个函数的话,会使一块内存被释放两次,,从而导致程序崩溃      //导致两个对象共用同一块一块空间      //反例      String(const String &s)            :_pStr(s._pStr)      {}      String&operator=(const String &s)      {            if (this != &s)            {                  _pStr = s._pStr;            }            return *this;      }

在此处调用拷贝构造函数之时,由于两个对象甚至更多的对象同时指向同一块内存,因此在释放之时,由于多次释放,因此使得程序崩溃。
这里写图片描述
对于赋值运算符的重载,由于与拷贝构造函数一致,在上面已经给出分析,因此在这里就不在给出他的分析了。
总结:如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝。
二、深拷贝
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
深拷贝:构造s2之时,拷贝一块与s1指向数据一样大的数据块,并将值拷贝下来,这样s1和s2指向各自的数据块,析构时各自释放各自的。如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
下面,上代码

//深拷贝      String(const String& s)//拷贝构造(普通版)            :_pStr(new char[strlen(s._pStr) + 1])//为传递过来的函数开辟空间,防止两块空间共用一块空间      {            strcpy(_pStr, s._pStr);      }String& operator=(const String& s)//(普通版)1.开辟新空间2.拷贝元素3.释放旧空间      {            if (this != &s)//如若不相等,则对齐进行赋值            {                  char *tmp = new char[strlen(s._pStr) + 1];//s传过来就是一个指向第一个元素的指针,开辟新空间                  strcpy(tmp, s._pStr);                  delete[] _pStr;                  _pStr = tmp;            }            return *this;      }

深拷贝与浅拷贝最大的区别就是,在深拷贝之时,会为对象重新分配内存,使其指向新的内存空间,因此使得其在释放之时,不会因为一块空间被释放多次而使得程序崩溃。

深拷贝的简洁版:

String(const String& s)//拷贝构造(简洁版)      :_pStr(NULL)//防止_pStr称为野指针,,为其开辟空间{      String tmp(s._pStr);//创建临时变量,调用构造函数      swap(_pStr, tmp._pStr);}String& operator=(const String& s){      if (this != &s)      {            String tmp(s._pStr);//调用构造函数            swap(_pStr, tmp._pStr);      }      return *this;}String& operator=( String s)//调用拷贝构造{      swap(_pStr, s._pStr);      return *this;}String& operator=(const String& s){      if (this != &s)      {            String tmp(s);            swap(_pStr, tmp._pStr);      }      return *this;}

总结(深拷贝与浅拷贝)
浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象 中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。
深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一 个新的和原是对象中对应字段相同(内容相同)的字段,也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对 象中对应字段的内容。

三、浅拷贝与写时拷贝的结合体
如果不想为对象开辟新的内存空间,但是也不想使得程序崩溃,因此在此处我们就需要定义引用计数的指针,他的任务就是指向对象使用的这块空间,如果这块内存一直在使用,因此就不释放他,如果已经是最后一个对象在使用,在由此对其进行释放。

class String{public:      String(char* pStr = "")//构造函数            :_pCount(new int(1))//为引用计数开辟空间      {            if (pStr == NULL)//传过来的字符串为空            {                  _pStr = new char[1];//此处这样申请的原因就是,为了释放时的安全,防止内存泄漏,为了达到释放空间时的匹配                  *_pStr = '\0';//将内容放在_pStr中            }            else            {                  _pStr = new char[strlen(pStr) + 1];                  strcpy(_pStr, pStr);//此处拷贝,连同“\0”一起拷贝            }      }      String(const String &s)            :_pStr(s._pStr)//字符串空间的共用            , _pCount(s._pCount)      {            ++(*_pCount);//如果共用一块空间,则让其引用计数自增      }      String&operator=(const String &s)      {            if (this != &s)            {                  if (0 == --(*_pCount))//当前空间只有一个对象在使用s3=s2;赋值方式                  {                        delete _pStr;                        _pStr = NULL;                  }                  _pStr = s._pStr;//s2=s3;这种赋值方式                  _pCount = s._pCount;                  ++(*_pCount);            }            return *this;      }      ~String()//析构函数      {            if (_pStr&&(0 == --(*_pCount)))//当当前空间只有一个对象在使用之时,就将此空间释放            {                  delete _pStr;                  _pStr = NULL;            }      }private:      char* _pStr;      int *_pCount;//定义引用计数的空间};

再利用此种方式之时,或许有的人就会有人有疑问,为什么不将计数定义为普通变量或者静态变量,现在我们就一一对其作出解答。
1、为什么不将其定义为普通变量
定义为普通变量之时,在析构函数之时,只是对其一个对象中的计数进行改变,,从而使得对象一直不能被销毁,从而导致程序的崩溃。
2.既然要实现共享,那为什么不使用静态变量(static)
在使用静态变量之时,虽然实现了对象的共享,但是在又重新创建另一个对象之时,又将计数的值置为初始值,从而使得被共用的内存不能得到适合的释放,从而又变成了简单的浅拷贝了,从而使得程序崩溃。

因此在引用计数之时,才定义一个指针变量,让其为每个对象均创建一个指针,实时监控着内存的变化,从而实现了引用计数的目的。

四、写时拷贝
利用上面引用计数的方法,虽然可以很好的实现利用浅拷贝实现正确的拷贝,但是在维护起两个指针有些困难,有时会忘了释放某个动态开辟的内存,从而使得内存泄漏,因此,我们就想出一个更好的办法,用来实现浅拷贝,从而更加方便了我们的管理。

class String{public:      String(char* pStr = "")//构造函数      {            if (pStr == NULL)//传过来的字符串为空                  pStr = '\0';//将内容放在_pStr中                  _pStr = new char[strlen(pStr) + 1 + 4];//在此处,多开辟4个字节,将引用计数放在空间的起始位置,真正放字符串的空间,则在内存中的起始偏移4个字节                  strcpy(_pStr, pStr);                  GetReference() = 1;      }      String(const String &s)            :_pStr(s._pStr)//字符串空间的共用      {            ++GetReference();      }      String& operator=(const String &s)      {            if (this != &s)            {                  Release();//将其旧空间释放                  _pStr = s._pStr;                  ++GetReference();            }            return *this;      }      char &operator[](size_t index)      {            if (GetReference() > 1)//判断是否共用空间            {                  String str(_pStr);//为要修改的空间新开辟另一段空间                  swap(_pStr, str._pStr);//在交换期间,又为引用计数加为原来的数,所以在这个函数体内,不用对引用计数的改变。            }            return _pStr[index];      }      ~String()//析构函数      {            Release();      }private:      int& GetReference()      {            return *(int*)(_pStr - 4);//找到起始位置,堆上的空间      }      void Release()//释放空间      {            if (_pStr && (0 == --GetReference()))            {                  _pStr -= 4;                  delete[] _pStr;                  _pStr = NULL;            }      }private:      char* _pStr;};int main(){      String s1("hello");      String s2(s1);      String s3(s2);      s1[0] = 'w';      String s4;      s4 = s2;      //s2 = s4;      system("pause");      return 0;}

虽然用这种方法,极好的实现了浅拷贝,但是在需要改变哪个字符串中的值时,还有些不方便,因此才有了上面的[]运算符的重载,仅仅只改变某个对象中的某个字符,从而不至于形成“牵一发而动全身”的后果。

下面给出测试代码:

int main(){    String s1("hello");    String s2(s1);    String s3(s2);    String s4;    s1[0]='w';    s4 = s2;    //s2 = s4;    system("pause");    return 0;}

对于深拷贝与浅拷贝的内容,大概就这么多了,希望我们一起学习,一起进步。
有什么问题,还请指正!!!!谢谢大家的几分钟。

原创粉丝点击