有關深淺丶寫時拷貝的那點事

1.所謂淺拷貝,就是由默認的拷貝構造函數實現的數據成員逐一賦值。通常默認的拷貝構造函數是能夠勝任此工作的,但若是類中含有指針類型的數據,這種按數據成員逐一賦值的方法將會產生錯誤。
有關淺拷貝的例子:

class sudent
{
public:
  student(char*name1,float scdre1);
  ~student();
private:
  char *name;
  float score;
};
student(char* name1,float score1)
{
 cout<<"xingming"<<name1<<endl;
 name=new char[strlen(name1)+1];
  if(name!=0)
  {
   strcpy(name,name1);
   score=score1;
  }
}
~student()
{
  cout<<destruct...<<name<<endl;
  name[0]='\0';
  delete[]name;
}
int main()
{
  student s1("zhangsan",90);//定義類student的對象s1。調用默認的拷貝構造函數
  student s2=s1;
  return 0;
}

這裏寫圖片描述
運行程序後發現程序錯誤,這是因爲主程序結束時對象逐個被撤銷,先撤銷s2,第一次調用析構函數用delete釋放動態分配的內存空間,撤銷s1時第二次調用析構函數,此時指針name所指的空間已被釋放,執行delete[]name時,企圖釋放同一空間,導致同一空間的兩次釋放,這是不被允許的。
2.爲了解決淺拷貝出現的錯誤,必須顯示的定義一個自定義的拷貝構造函數,使之不但複製數據成員,而且爲對象s1和s2分配各自內存空間,這就是所謂的深拷貝。

  student(student & stu)  
   {
    name=new char[strlen(stu.name)+1];
    if(name!=0)
     {
     strcpy(name,name1);
     score=score1;
     }
   }

這裏寫圖片描述
構造s2時拷貝一塊跟s1指向數據塊一樣大的數據塊,並將值拷貝下來,這樣s1和s2指向各自的數據塊,析構時釋放各自的數據塊。
3.string類的寫時拷貝
寫時拷貝:是在寫的時候(即改變字符串的時候)纔會真正的開闢空間拷貝(深拷貝),如果只是對數據的讀時,只會對數據進行淺拷貝(只想讀取文件時,深拷貝不管三七二十一 ,就直接給你開闢空間供str讀)。

方案1:我們爲每個內存的字符數組添加一個引用計數count,即就是在構造函數申請空間的時候多申請出來4個字節。表示有多少個對象使用這塊內存,有多少個對象使用,就讓count值加1,當對象被析構的時候,讓count值減1,當count值爲0的時候,將這塊內存釋放掉。當然count也要實現內存共享,所以它也是一個堆中的數據,每個對象都有一個指向它的指針。

#include <iostream>
using namespace std;

class String
{
public:
    String(char* str = "")//構造
        :_str(new char[strlen(str)]+1)
        , _refCount(new int(1))
    {
        strcpy(_str, str);
    }
   String(const String& str)//拷貝構造----淺拷貝 
        : _str(str._str)
        ,_refCount(str._refCount)
    {
        (*_refCount)++;
    }

    ~String()//析構
    {
        if(--(*refCount)==0)
      {
       delete[]  _str;
       delete _refCount;
      }
    }
    String& operator = (const String& s)//賦值運算符重載
{
      if(_str != s._str)//判斷是否爲自己給自己賦值
      {
           if(--(*_refCount)==0)
          {
            delete[] _str;
            delete _refCount;
          }
          _str = s._str;
          _refCount = s._refCount;
          (*refCount)++;
      }
      return *this;
}
private:
    char* _str;
    int* _refCount;
};

這裏寫圖片描述

方案2:開闢一個空間,前面4個字節爲計數器count,剩下的爲字符串_str的空間
(當有新指針指向這片空間時引用計數++,要釋放空間時引用計數–,直到減爲0時才真正釋放這片空間 當有指針要改變這片空間是再爲這個指針開闢一塊新的空間讓其進行操作,舊的空間count-1,新的空間count+1)
這裏寫圖片描述

class String
{
public:
    String(const char* str="")//構造
        :_str(new char[strlen(str)+1+4])
    { 
        _str+=4;//走到數據區首地址
        strcpy(_str,str);
        GetCount()=1;//計數器區域賦值爲1
    }

    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)
        {
            cout<<"釋放"<<endl;   
            delete[] (_str-4);
            _str=NULL;
        }
    }
       int& GetCount()
    {
        return *((int*)_str - 1);
    }

private:
    char* _str;
};

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章