天天看點

淺拷貝與深拷貝并實作String

1.什麼是淺拷貝,裡面存在什麼問題?

如果已定義好一個類,如果類中隻有int、double、char、bool這類基本類型的變量,由于基本變量的所占空間是已知的,

是以在編譯時編譯器就可确定所需記憶體大小而進行配置設定(靜态配置設定記憶體),對象a和對象b他們的變量存在于各自的記憶體塊中(對象記憶體空間),淺拷貝後a的所有變量都被拷貝,如果a中的變量值被修改不會影響b内的變量值。

但假如這個類是個含有一個指針變量p,其中b對象在運作時配置設定了塊記憶體(比如動态建立了數組,或讀取了檔案資料流,即動态配置設定記憶體),p指向該記憶體,淺拷貝後,a的p被指派,是以同樣指向該記憶體塊,也就是說a的指針p指向了b的指針p指向的記憶體。這就存在一個問題,兩個對象的指針都指向一塊記憶體,如果通過a的指針p修改資料,顯然就會使得b内的資料也受到影響,這不符合解耦原則而破壞了對象的封裝性。

舉一個簡單的例子:

void FunTest1()
{
  int *pTest1 = new int[10];
  int *pTest2 = pTest1;
  delete[] pTest1;
  delete[] pTest2;
}      

很明顯pTest1指向的空間被釋放了兩次,肯定會出問題,為了避免這種情況,就要用到深拷貝。

2.用深拷貝怎麼解決?深拷貝的兩種書寫方式:普通版和簡潔版

舉一個String類的例子

#define _CRT_SECURE_NO_WARNINGS 1 
#include<string.h>
#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);
    }
  }      

普通版主要是利用strcpy實作

//普通版
  //拷貝構造函數
  String(const String& s)
    :_pStr(new char[strlen(s._pStr) + 1])
  {
    strcpy(_pStr, s._pStr);
  }

  //重載運算符=
  String& operator=(const String& s)
  {
    if (this != &s)
    {
      char *pTmp = new char[strlen(s._pStr) + 1];
      strcpy(pTmp, s._pStr);
      delete[] _pStr;
      _pStr = pTmp;
    }
    return *this;
  }      

簡潔版是利用swap函數

//簡潔版
  //拷貝構造函數
  String(const String& s)
    :_pStr()
  {
    String tmp(s._pStr);
    swap(_pStr, tmp._pStr);
  }

  //重載運算符=
  String& operator=(const String& s)
  {
    if (this != &s)
    {
      String tmp(s);
      swap(_pStr, tmp._pStr);
    }
    return *this;
  }      

3、在深拷貝版本的任何一個string類中,完成以下函數

#define _CRT_SECURE_NO_WARNINGS 1 
#include<string.h>
#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(new char[strlen(s._pStr) + 1])
  //{
  //  strcpy(_pStr, s._pStr);
  //}

  重載運算符=
  //String& operator=(const String& s)
  //{
  //  if (this != &s)
  //  {
  //    char *pTmp = new char[strlen(s._pStr) + 1];
  //    strcpy(pTmp, s._pStr);
  //    delete[] _pStr;
  //    strcpy(_pStr, pTmp);
  //  }
  //  return *this;
  //}

  //簡潔版
  //拷貝構造函數
  String(const String& s)
    :_pStr()
  {
    String tmp(s._pStr);
    swap(_pStr, tmp._pStr);
  }

  //重載運算符=
  String& operator=(const String& s)
  {
    if (this != &s)
    {
      String tmp(s);
      swap(_pStr, tmp._pStr);
    }
    return *this;
  }

  //析構函數
  ~String()
  {
    if (_pStr != NULL)
    {
      delete[] _pStr;
      _pStr = NULL;
    }
  }

  size_t Size()const                       //計算字元串所占的位元組
  {
    char *tmp = _pStr;
    int n = 0;
    while (*tmp++)
    {
      n++;
    }
    n++;                          //包括\0
    return n;
  }
                                
  size_t Lengh()const                          //計算字元串長度
  {
    char *tmp = _pStr;
    int n = 0;
    while (*tmp++)
    {
      n++;
    }
    return n;
  }

  char& operator[](size_t index)                     //重載下标[]
  {
    char *tmp = _pStr + index;
    return *tmp;
  }
         //比較字元串大小
  bool operator>(const String& s)                    
  {
    if (strcmp(_pStr, s._pStr) > 0)
      return true;
    else
      return false;
  }

  bool operator<(const String& s)
  {
    if (strcmp(_pStr, s._pStr) < 0)
      return true;
    else
      return false;
  }

  bool operator==(const String& s)
  {
    if (strcmp(_pStr, s._pStr) == 0)
      return true;
    else
      return false;
  }

  bool operator!=(const String& s)
  {
    if (strcmp(_pStr, s._pStr) != 0)
      return true;
    else
      return false;
  }

  void Copy(const String& s)
  {
    delete[] _pStr;
    _pStr = s._pStr;
  }
         //尋找子串
  bool strstr(const String& s)
  {
    char *p1 = _pStr;            
    char *q = s._pStr;            
    char *p2 = p1;            
    while (*p2)
    {
      p2 = p1;
      q = s._pStr;
      while ((*p2 != '\0') && (*q != '\0'))
      {
        if (*p2++ == *q++)
        {
          ;
        }
        else
        {
          p1++;
          break;
        }
      }
      if (*q  == '\0')
      {
        return true;
      }
    }
    return false;
  }

            //字元串連接配接,相似與strcat
  String& operator+=(const String& s)
  {
    char *tmp = new char[strlen(s._pStr) + strlen(_pStr) + 1];
    char *pTmp = tmp;
    strcpy(tmp, _pStr);
    char *tmp1 = s._pStr;
    while (*tmp)
      *tmp++;
    while (*tmp1)
    {
      *tmp++ = *tmp1++;
    }
    *tmp = '\0';
    *this = pTmp;
    return *this;
  }

  void display()
  {
    cout << _pStr << endl;
  }
private:
  char* _pStr;
};

int main()
{
  String s1("hello ");
  String s2("world");
  String s3("world");

  s1 += s2;
  s1.display();
  s2.display();

  s1.Copy(s2);
  s1.display();
  s2.display();

  cout << s1[2] << endl;
  s1.display();

  cout << s1.Size() << endl;
  cout << s1.Lengh() << endl;

  if (s1 > s2)
  {
    cout << "s1>s2" << endl;
  }
  if (s1 < s2)
  {
    cout << "s1<s2" << endl;
  }
  if (s3 == s2)
  {
    cout << "s2=s3" << endl;
  }

  if (s1.strstr(s2))
    cout << "有" << endl;
  else
    cout << "沒有" << endl;

  system("pause");
  return 0;
}      

運作結果如下:

淺拷貝與深拷貝并實作String

4.什麼是引用計數,用引用計數能解決淺拷貝存在的問題嗎?

在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就将釋放占有的資源,就避免了淺拷貝中出現多次析構的問題。

5、對引用計數進行改進,完成string的引用計數版本。即寫時拷貝。(更新中)