寫時拷貝的兩種方案
阿新 • • 發佈:2019-01-08
1. 什麼是寫時拷貝
寫時拷貝故名思意:是在寫的時候(即改變字串的時候)才會真正的開闢空間拷貝(深拷貝),如果只是對資料的讀時,只會對資料進行淺拷貝。
寫時拷貝:引用計數器的淺拷貝,又稱延時拷貝
:寫時拷貝技術是通過"引用計數"實現的,在分配空間的時候多分配4個位元組,用來記錄有多少個指標指向塊空間,當有新的指標指向這塊空間時,引用計數加一,當要釋放這塊空間時,引用計數減一(假裝釋放),直到引用計數減為0時才真的釋放掉這塊空間。當有的指標要改變這塊空間的值時,再為這個指標分配自己的空間(注意這時引用計數的變化,舊的空間的引用計數減一,新分配的空間引用計數加一)。
2. string中的兩種寫時拷貝
一:
- 動態開闢兩個空間一個用來存放字串:_str,一個用來存放計數器_refCountPtr
- 每次拷貝構造時(賦值運算子的過載後半段),直接把字串的指標付給新的String,然後給計數器加加(*_refCountPtr)++
- 在釋放String的時候,先對計數器減減,再判斷計數器是否為零(即看是否還有指標共享此記憶體),若計數器為零則直接釋放_str 和 _refCountPtr
- 在對字串進行更改的時候(寫時),就要進行深拷貝:先進行步驟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;
};