深浅拷贝和写时拷贝

拷贝:  
     事实是,在对象拷贝过程中,如果没有自定义拷贝构造函数系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。

浅拷贝:
      缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标--浅拷贝。

浅拷贝的代码
class String
{
public:
       String(const char* str = "")
              :_str(new char[strlen(str) + 1])
       {
              strcpy(_str, str);
       }
       //拷贝构造函数,它是构造函数的重载
       String(const String& s)
       {
              //浅拷贝的情况下,只复制了指针本身,对指针所指向的内存并没有进行拷贝
              _str = s._str;
       }
       String& operator=(const String& s)
       {
              if (this != &s)
              {
                     _str = s._str;
              }
              return *this;
       }
       ~String()
       {
              if (_str)
              {
                     //指针指向相同的内存空间,再次释放一块已经释放的空间将会出现错误
                     delete[] _str;
              }
       }
private:
       char* _str;
};



浅拷贝出现的问题:
     (1)在进行对象复制后,事实上s1、s2里的成员指针m_psz都指向了一块内存空间(即内存空间共享了),在s1析构时,delete了成员指针m_psz所指向的内存空间,而s2析构时同样指向(此时已变成野指针)并且要释放这片已经被s1析构函数释放的内存空间,这就让同样一片内存空间出现了重复释放 ,从而出错。
     (2)而浅拷贝还存在着一个问题,因为一片空间被两个不同的子对象共享了,只要其中的一个子对象改变了其中的值,那另一个对象的值也跟着改变了

深拷贝:
      为每个对象都分配内存空间,防止析构时出现错误。

深拷贝的代码:
class String
{
public:
       String(const char* str = "")
              :_str(new char[strlen(str) + 1])
       {
              strcpy(_str, str);
       }
       String(const String& s)
       {
              //内存的拷贝先要创建出一块内存
              _str = new char[strlen(s._str)+1];
              //然后将指针所指向的内容拷贝一份
              strcpy(_str, s._str);
       }
       //赋值运算符的重载
       String& operator=(const String& s)
       {
              //防止自身对自身赋值(因为会delete掉原来的内存空间,如果是自己给自己赋值,那么delete以后就找不到了)
              if (this != &s)
              {
                     delete[] _str;
                     _str = new char[strlen(s._str) + 1];
                     strcpy(_str, s._str);
              }
              return *this;
       }
       ~String()
       {
              if (_str)
              {
                     delete[] _str;
              }
       }
private:
       char* _str;
};



深浅拷贝的不同:
      深浅拷贝的主要区别在于是否共用空间。两者的代码只有拷贝构造函数和赋值运算符重载时,深拷贝需要重新开辟空间,而浅拷贝仅仅改变了指针的指向。

深拷贝的缺点:
     重复的去开辟空间和释放空间效率是很低下的。

写时拷贝的引入:
     大家都知道,重复的开辟空间和释放内存是很耗时的,效率也很低下,相对于写时拷贝,深拷贝就很耗费时间了,效率自然没有写时拷贝好。
     写时拷贝是用一个计数器记住当前空间被多少个指针所指向,每次调用析构函数的时候,只需要看看自减后引用计数是否为零(是否只被最后一个指针所指向)。如果是,则销毁空间,如果不是,那么只需要引用计数减减即可,无需释放空间。
     写时拷贝是一种高性能的写法,不过这种写法如果不注意就会把引用计数弄错,导致错误。所以在写时拷贝的写法的时候,一定要小心改写引用计数。

执行下面的一段代码,写时拷贝写法的指针应该如何指向呢?
void test()
{
       String s1("hello");
       String s2(s1);
       String s3("world");
       s1 = s3;
}


图解上面的代码:

写时拷贝的代码:
class String
{
public:
       String(const char* str = "")
              :_str(new char[strlen(str)+1+4])//+1表示字符串后面要放一个'\0',+4表示多开辟一个空间存放引用计数
       {
              _str += 4;//_str指向数据存放区
              strcpy(_str, str);
              _GetCount() = 1;//刚开始这里写成了_GetCount++,其实是不对的,因为引用计数所指向的这a块空间并没有初始化,他的值并不是0,是一个随机值,随机值++还是随机值
       }
       String(const String& s)
              :_str(s._str)
       {
              _GetCount()++;
       }
       String& operator=(String& s)
       {
              if (this != &s)
              {
                     if (--_GetCount() == 0)
                     {
                           delete[] (_str-4);
                     }
                     ++s._GetCount();
                     _str = s._str;
              }
              return *this;
       }
       ~String()
       {
              if (--_GetCount() == 0)
              {
                     delete[] (_str-4);
              }
       }
public:
       int& _GetCount()
       {
              return *((int*)_str-1);
       }
private:
       char* _str;
};


    

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值