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

对于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;
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值