1. 程式人生 > >寫時拷貝的兩種方案

寫時拷貝的兩種方案

1. 什麼是寫時拷貝

寫時拷貝故名思意:是在寫的時候(即改變字串的時候)才會真正的開闢空間拷貝(深拷貝),如果只是對資料的讀時,只會對資料進行淺拷貝。
寫時拷貝:引用計數器的淺拷貝,又稱延時拷貝
:寫時拷貝技術是通過"引用計數"實現的,在分配空間的時候多分配4個位元組,用來記錄有多少個指標指向塊空間,當有新的指標指向這塊空間時,引用計數加一,當要釋放這塊空間時,引用計數減一(假裝釋放),直到引用計數減為0時才真的釋放掉這塊空間。當有的指標要改變這塊空間的值時,再為這個指標分配自己的空間(注意這時引用計數的變化,舊的空間的引用計數減一,新分配的空間引用計數加一)。

2. string中的兩種寫時拷貝

一:

  1. 動態開闢兩個空間一個用來存放字串:_str,一個用來存放計數器_refCountPtr
    這裡寫圖片描述
  2. 每次拷貝構造時(賦值運算子的過載後半段),直接把字串的指標付給新的String,然後給計數器加加(*_refCountPtr)++
    這裡寫圖片描述
  3. 在釋放String的時候,先對計數器減減,再判斷計數器是否為零(即看是否還有指標共享此記憶體),若計數器為零則直接釋放_str 和 _refCountPtr
  4. 在對字串進行更改的時候(寫時),就要進行深拷貝:先進行步驟3,在進行深拷貝和字串的更改
class String//寫時拷貝
    {
    public:
        String(char
* str = "\0") :_refCountPtr(new int(1))//開闢計數器動態記憶體 ,_size(strlen(str)) { _capacity = _size; _str = new char[_capacity+1];//開闢字串動態記憶體 strcpy(_str,str); } String(String& s)//拷貝構造 :_str(s._str)//直接淺拷貝 ,_
refCountPtr(s._refCountPtr) ,_size(s._size) ,_capacity(s._capacity) { (*_refCountPtr)++;//但對計數器 ++ } ~String()//析構 { Release(); } inline void Release() { if(--(*_refCountPtr) == 0)//計數器--,並判斷是否為0 { cout<<"~String"<<_str<<endl; delete[] _str; delete _refCountPtr;//釋放 } } String &operator=(String &s)//過載 { if(_str != s._str)//判斷是否為自己給自己賦值 { Release();//判斷並處理this _str = s._str; _refCountPtr = s._refCountPtr; _size = s._size; _capacity = s._capacity; (*_refCountPtr)++; } return *this; } char *c_str() const { return _str; } //寫時拷貝 String &push_back(char ch) { char* str = new char[_capacity*2]; strcpy(str,_str); //先將原來的資料儲存,用於給後面重新開闢的空間賦值 Release();//處理原來的空間 _str = str; _str[_size++] = ch; _str[_size] = '\0'; _capacity *= 2; _refCountPtr = new int(1);// return *this; } private: char *_str; int *_refCountPtr; size_t _size; size_t _capacity; };

二:

    開闢一個空間,前面4個位元組為計數器count,剩下的為字串_str的空間,用法與分開相同。

這裡寫圖片描述

class String
    {
    public:
        String(char *str = "\0")
            :_capacity(strlen(str))
        {
            _size = _capacity;
            _str = new char[_capacity + 5];//多開闢了4個位元組
            *(int *)_str = 1//前4個自己存放計數器
            _str += 4;          //使_str指向開闢的4個位元組後的空間
            strcpy(_str,str);//拷貝字串
        }
        String(String &s)//拷貝構造
            :_str(s._str)//直接淺拷貝
            ,_capacity(s._capacity)
            ,_size(s._size)
        {
            ++(*(int *)(_str-4));//(str前的4個位元組為計數器) ++
        }
        ~String()//析構
        {
            Release();
        }   
        void Release()
        {
            if(--(*(int *)(_str-4)) == 0)//計數器-1判斷是否為零
            {
                cout<<"~String "<<_str<<endl;
                delete[] (_str-4);
            }
        }
        String& operator=(String &s)//賦值過載
        {
            if(_str != s._str)//判斷是否為自己為自己賦值
            {
                Release();//處理原指標(釋放空間,或者計數器-1)
                _str = s._str;
                _size = s._size;
                _capacity = s._capacity;//賦值
                ++(*(int *)(_str-4));//改變先指標的計數器
            }
            return *this;
        }
        String &operator+=(char ch)//過載,會改變,會深拷貝
        {
            char* str = new char[_capacity*2 + 6];
            strcpy(str+4, _str);//先儲存原字串
            Release();//因為要重新開闢空間,所以需要處理以前的空間
            *(int *)str = 1;//新開闢的計數器置1
            _str = str + 4;
            _str[_size++] = ch;
            _str[_size] = '\0';
            _capacity *= 2;
            return *this;
        }
        String &append(const char *str)//追加字串
        {
            size_t len = strlen(str);//方法同+=的過載
            _capacity = len + _size;
            char *tmp = new char[_capacity + 6];
            strcpy(tmp+4 ,_str);
            Release();
            *(int *)tmp = 1;
            _str = tmp + 4;
            strcpy(_str+_size, str);
            _size = _capacity;
            return *this;
        }
    private:
        char* _str;//開闢多4個,指向4個位元組後的地址
        size_t _size;
        size_t _capacity;
    };