1. 程式人生 > >C++——string的深拷貝與淺拷貝

C++——string的深拷貝與淺拷貝

在c++中,基本所有的類都要考慮深拷貝,淺拷貝與寫時拷貝,根據不同的定義,選擇適合自己的拷貝方式。時間類就可以用淺拷貝,而二叉樹,string類就需要深拷貝。
string類在vs編譯器下使用的深拷貝,在Linux下使用的淺拷貝。
為什麼會存在深淺拷貝的問題呢?
string的淺拷貝是讓兩個不同的指標指向同一塊空間,而這在析構的時候會出現將一塊空間釋放兩次,程式會崩潰,因此我們才需要進行深拷貝,即第二個指標開闢和第一個指標一樣大小空間,然後將內容複製過去。
深拷貝:

class string
{
public:
    string(char* pstr)    //建構函式
        :str
(new char[strlen(pstr) + 1]) //開闢新的空間 { if (pstr == NULL)// { str = new char[1]; //開闢一個 存放‘\0’ *str = '\0'; } else { str = new char[strlen(pstr) + 1]; for (size_t i = 0; i < strlen(pstr); i++) //深複製 { str
[i] = pstr[i]; } //strcpy(str,pstr); //memcpy(str,pstr,strlen(pstr)+1);淺複製 } } //拷貝建構函式 string(const string& pstr) :str(new char[strlen(pstr.str)+1]) { for (size_t i = 0; i < strlen(pstr.str); i++) { str[i] = pstr.str
[i]; } //strcpy(str,pstr.str); //memcpy(str,pstr.str,strlen(pstr.str)+1); } //賦值運算子過載 string& operator = (const string & pstr) { if (&str == &pstr.str) //檢查是否是自己給自己賦值 return *this; delete[] str; //釋放臨時空間 str = new char[strlen(pstr.str) + 1]; for (size_t i = 0; i < strlen(pstr.str); i++) { str[i] = pstr.str[i]; } //strcpy(str,pstr.str); //memcpy(str,pstr.str,strlen(pstr.str)+1); return *this; } /*現代寫法: 根據拷貝建構函式讓系統自己開闢空間 拷貝建構函式 string(const string& pstr) :str=null; 必須置為空,要不然訪問地址非法化 { string tmp (pstr); swap(tmp.str,str); return *this; } 賦值運算子過載 string& operator = (const string& pstr ) { string tmp(pstr); swap(tmp.str, str); return *this; }*/ //解構函式 ~string() { delete[] str; str = NULL; } private: char* str; }; int main() { string a ("12345"); string b(a); cout << b << endl; }

淺拷貝,當我們需要改變新的空間的內容的時候,才會重新開闢空間呢?
1)判斷空間使用的次數來選擇析構,增加一個類成員 count,但是這樣造成的後果是每一個成員都有一個不同的count 在析構的時候就很混亂還是會出錯
2)然後呢我們會想到使用靜態成員的辦法,因為此時 static int count 是放在靜態區,它是所有物件共享的,不會為每個物件單獨生成一個count,可是當我們有多個不同的成員共同管理一塊空間,而此時我們又用建構函式建立一個物件時候,count又會變為1,所以這種方法還是不行 。
3)使用引用計數,把引用計數放在字串的前四個位元組裡,每個創建出來的物件都有不同的引用計數

class string
{
public:
    string(char* pstr)    //建構函式
        :str(new char[strlen(pstr) + 1+4])//多開闢的4個位元組存放引用計數
    {
        if (pstr == NULL)
        {
            str = new char[1];
            *str = '\0';
        }
        else
        {
            str = new char[strlen(pstr) + 1+4];
            int* count = (int* )str;
            *count = 1;
            count ++;  //後移拷貝資料
            str = (char*)count;
            for (size_t i = 0; i < strlen(pstr); i++)  //深複製
            {
                str[i] = pstr[i];
            }
            //strcpy(str,pstr);
            //memcpy(str,pstr,strlen(pstr)+1);淺複製
        }
    }
    //拷貝建構函式
    string(const string& pstr)
    {
        str = pstr.str;
        int * count = (int *)(str-4) ;
        ++*count;

    }
    //賦值運算子過載
    string& operator = (const string & pstr)
    {
        if (&str == &pstr.str)
            return *this;
        destroy();//判斷是否釋放空間
        str = pstr.str;
        int * count = (int *)(str-4) ;
        ++*count;
    }
void destroy()
{
    int * count = (int *)(str-4); //獲取引用計數
    if(*count == 1)
    {
       delete[](str-4);
       str = NULL;
    }
    else
      --*count;
}
//解構函式
~string()
{
   destroy();
}

private:
    char* str;
    int * count;
};

寫時拷貝:
在淺拷貝中,假設有多個名稱引用了同一個字串,當其中一個需要修改字串時,這就會導致引用的值發生改變,這明顯不是我們所期望的,所以出現了寫時拷貝,(可能會修改字串的時候使用深拷貝將其與大部隊分離,這樣就保護了其他引用成員)
則我們只需要過載[] 就可以實現

const string& operator[]const 
{
        string tmp(pstr);
        swap(tmp.str, str);
        return *this;
}