C++深拷貝與淺拷貝(實現String類)

淺拷貝:

1.什麼是淺拷貝? 淺拷貝會出現什麼問題?

所謂淺拷貝,指的是在對象複製時,只是對對象中的數據成員進行簡單的複製,默認拷貝構造函數執行的也是淺拷貝。簡單的說,淺拷貝就是值傳遞,將源空間裏面的內容複製到目標空間中。

存在缺陷:多個指針可能共用管理一塊內存空間,在釋放時,導致對一塊空間的多次釋放,造成內存泄露。

深拷貝:

2. 什麼是深拷貝?

在“深拷貝”的情況下,對於對象中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間。

深拷貝與淺拷貝:

3.淺拷貝與深拷貝的不同之處:

用深動形象的語言來說,如果把拷貝看作下面這幅圖,那麼,淺拷貝只能拷走美人魚的頭,水下部分它將無法操作,而深拷貝 不僅可以拷走頭,還可以操作水下隱含的部分。
圖一

好了,說到這裏,咱們可能對深淺拷貝或多或少的有了一些認識,那麼,下面,我們將實現一個String 類,來更加具體權威的解釋深淺構造函數。

4.完成String類-普通版(淺拷貝)

#include<iostream>
using namespace std;
class String
{
    public :
      String(const char*pStr="")//構造函數
        {
          if(NULL==pStr)
            {
                _pStr=new char[1];
                *_pStr='\0';
                }
        else 
        {
             _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
        }
            }
    String(const String& s)//拷貝構造函數
            :_pStr(s._pStr)
        {    
            //s2已經釋放,但s1不知道,會對空間再次釋放
            } 
    String& operator=(const String& s)//賦值運算符重載
    {
        if(this!=&s)
        {
            _pStr=s._pStr;//內存泄露
        }
    return *this;
    }
~String ()//析構函數
{
  if(_pStr)
   {
      delete[] _pStr;
        _pStr=NULL;         
     }
}
private: 
    char* _pStr;

};
void FunTest()
{
    String s1("hello");
    //String s2(s1);
    String s3;
    s3=s1;
}

int main ()
{
    FunTest();
    /*String s1("hello");
    String s2(s1);
    String s3(NULL);
    String s4(s1);
    s3=s4;
    s3=s1;*/
    return 0;
}
該例證明了淺拷貝會存在多個對象共用同一塊空間,在調用析構函數銷燬空間時,會出現對一塊空間多次釋放的情況,導致內存崩潰

5. 完成String類深拷貝—簡潔版

#include<iostream>
using namespace std;
class String
{
public :
//構造函數************************************************
    String(const char*pStr="")  
    {
        if(NULL==pStr)
        {
            _pStr=new char[1];
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
        }
            }
//拷貝構造函數********************************************
    String(const String& s)
        :_pStr(NULL)//選擇最佳
        {
            //_pStr=new char[1];//第二選擇
            String strTemp(s._pStr );
            swap(_pStr,strTemp._pStr);
            } 
//賦值 運算符重載******************************************
//方式一:
    String& operator=(const String& s)
    {
        if(this!=&s)
        {
            String strTemp(s._pStr);
            //String strTemp(s) ;
            swap(_pStr,strTemp._pStr) ;
        }
    return *this;
    }
//方式二:
//String& operator=(const String& s)
//  {
//      String strTemp(s) ;
//      swap(_pStr,strTemp._pStr) ;
//return *this;
//}
    //方式三:
    //String& operator=( String s)
    //{
    //  swap(_pStr,s._pStr );
    //  return *this;
    //}
//三種賦值運算符重載解決方案, 第一種方式爲最優方案
//析構函數*****************************************
~String ()
{
  if(_pStr)
   {
      delete[] _pStr;
        _pStr=NULL;         
  }
}
private: 
    char* _pStr;
    int _count;
};

void  FunTest()
{
    String s1("Hello");
    String s2(s1);
    s2=s1;
}
int main ()
{
    FunTest();
    /*String s1("hello");
    String s2(s1);
    String s3(NULL);
    String s4(s1);
    s3=s4;
    s3=s1;*/
    return 0;
}

6.引用計數:

A.什麼是引用計數?
在開闢空間時,爲了記錄該空間有多少對象在共用它,也就是說有多少指針指向它,採用再開闢一個空間的方式,記錄該空間被指向的次數,這種方式被稱爲引用計數。
圖二
B.用引用計數實現String時,引用計數可以普通的成員變量?爲什麼?
解析:引用計數不可以爲普通的成員變量,因爲一旦出了作用域,該空間被銷燬,達不到想要的效果

C.用引用計數實現String時,引用計數可以類的靜態成員變量嗎? 爲什麼?
解析:類的靜態成員變量,但在需要另外開闢空間時,採用這種方式就

#include<iostream>
using namespace std;
class String
{
public :
    //構造函數
    String(const char* pStr="")
    {
        if(NULL==pStr)
        {
            _pStr=new char[1];
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
             }
    _count=1;
    }
String (const String& s)
    :_pStr(s._pStr)

   {
       _count++;
      }

~String ()
{
    if(_pStr&&(0==--*_count))
    {
        delete[] _pStr;
        _pStr=NULL;
    }
}
private: 
    char* _pStr;
    static int _count;
    };

int String::  _count=0;
void FunTest()
{
    String s1("hello");
    String s2(s1);

    String s3;
}

int main()
{
    FunTest();
    return 0;
}

爲了在釋放的時候,防止忘記釋放引用計數所開闢的空間,所以儘量採用new [ ]
的方式來開闢空間,與delete[ ]搭配使用。

7.完成引用計數版本的String類—該引用計數也屬於淺拷貝

    //************引用計數*******************************
#include<iostream>
using namespace std;
class String
{
public :
    //構造函數
    String(const char* pStr="")
        :_pCount(new int (1))
    {
        if(NULL==pStr)
        {
            _pStr=new char[1];
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
        }
    }
String (const String& s)
    :_pStr(s._pStr)
    ,_pCount(s._pCount)

   {
       ++(*_pCount);
      }

String& operator=(const String& s)
{
    if(_pStr!=s._pStr)//被賦值的對象與當前對象不是同一塊空間
    {
        if(_pStr&&0==--*_pCount)
        {
            delete [] _pStr;
            delete _pCount;
        }
        _pStr =s._pStr;
        _pCount=s._pCount;
        ++_pCount;
    }
    return *this;
}
~String ()
{
    if(_pStr&&(0==--*_pCount))
    {
        delete[] _pStr;
        _pStr=NULL;
        delete _pCount;
        _pCount=NULL;
    }
}
private: 
    char* _pStr;
     int* _pCount;
};
void FunTest()
{
    String s1("hello");
    String s2(s1);
    String s3;
}
int main()
{
    FunTest();
    return 0;
}

7. 完成COW(寫時拷貝版的String)

(COW不是奶牛)

//****寫時拷貝:如果要朝當前對象寫東西,最好使用這種方式*********
//單線程不會有問題
#include<iostream>
using namespace std;
class String
{
public :
    //構造函數
    String(const char* pStr="")
    {
        if(NULL==pStr)
        {
            _pStr=new char[1+4];
            _pStr+=4;
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1+4];
            _pStr+=4;
             strcpy(_pStr,pStr);
        }
        GetRaf()=1;
    }
String (const String& s)
    :_pStr(s._pStr)


   {
       GetRaf()++;
      }

String& operator=(const String& s)
{
    if(_pStr!=s._pStr)//被賦值的對象與當前對象不是同一塊空間
    {
        Release();
        _pStr =s._pStr;
        ++GetRaf();
    }

    return *this;
}

~String ()
{
    Release();
}

char& operator [] (size_t index)
{
    if(GetRaf()>1)//如果當前空間不止存放的一個對象
    {
        char* pTemp=new char [strlen(_pStr)+1+4];
        *(int*)pTemp=1;
        pTemp+=4;
        strcpy(pTemp,_pStr);
        --GetRaf();//一定是在改變指針指向之前
        _pStr=pTemp;
    }
        return _pStr[index];
}
const char& operator [](size_t index)const 
{
    return _pStr[index];
}
private:
    int& GetRaf()
    {
            return *((int*)_pStr-1);
    }
    void Release()
    {
        if(_pStr&&(0==--GetRaf()))
            {
            _pStr-=4;
            delete[] _pStr;
            _pStr=NULL;
            }

    }
private: 
    char* _pStr;
};

void FunTest()
{
    String s1("hello");
    String s2(s1);
    s2[0]='w';
}

int main()
{
    FunTest();
    return 0;
}

下面,在這裏提出兩點關於string類來說非常重要點:
一.熟悉庫中string類的每個接口,查文檔,熟悉庫中的string類。
二.調研vs和linux系統下string類的結構,他們是否採用用深拷貝原理實現。

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