寫時拷貝的兩種方案

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;
    };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章