C++——string的深拷貝與淺拷貝
阿新 • • 發佈:2019-02-02
在c++中,基本所有的類都要考慮深拷貝,淺拷貝與寫時拷貝,根據不同的定義,選擇適合自己的拷貝方式。時間類就可以用淺拷貝,而二叉樹,string類就需要深拷貝。
string類在vs編譯器下使用的深拷貝,在Linux下使用的淺拷貝。
為什麼會存在深淺拷貝的問題呢?
string的淺拷貝是讓兩個不同的指標指向同一塊空間,而這在析構的時候會出現將一塊空間釋放兩次,程式會崩潰,因此我們才需要進行深拷貝,即第二個指標開闢和第一個指標一樣大小空間,然後將內容複製過去。
深拷貝:
class string
{
public:
string(char* pstr) //建構函式
:str (new char[strlen(pstr) + 1]) //開闢新的空間
{
if (pstr == NULL)//
{
str = new char[1]; //開闢一個 存放‘\0’
*str = '\0';
}
else
{
str = new char[strlen(pstr) + 1];
for (size_t i = 0; i < strlen(pstr); i++) //深複製
{
str [i] = pstr[i];
}
//strcpy(str,pstr);
//memcpy(str,pstr,strlen(pstr)+1);淺複製
}
}
//拷貝建構函式
string(const string& pstr)
:str(new char[strlen(pstr.str)+1])
{
for (size_t i = 0; i < strlen(pstr.str); i++)
{
str[i] = pstr.str [i];
}
//strcpy(str,pstr.str);
//memcpy(str,pstr.str,strlen(pstr.str)+1);
}
//賦值運算子過載
string& operator = (const string & pstr)
{
if (&str == &pstr.str) //檢查是否是自己給自己賦值
return *this;
delete[] str; //釋放臨時空間
str = new char[strlen(pstr.str) + 1];
for (size_t i = 0; i < strlen(pstr.str); i++)
{
str[i] = pstr.str[i];
}
//strcpy(str,pstr.str);
//memcpy(str,pstr.str,strlen(pstr.str)+1);
return *this;
}
/*現代寫法: 根據拷貝建構函式讓系統自己開闢空間
拷貝建構函式
string(const string& pstr)
:str=null; 必須置為空,要不然訪問地址非法化
{
string tmp (pstr);
swap(tmp.str,str);
return *this;
}
賦值運算子過載
string& operator = (const string& pstr )
{
string tmp(pstr);
swap(tmp.str, str);
return *this;
}*/
//解構函式
~string()
{
delete[] str;
str = NULL;
}
private:
char* str;
};
int main()
{
string a ("12345");
string b(a);
cout << b << endl;
}
淺拷貝,當我們需要改變新的空間的內容的時候,才會重新開闢空間呢?
1)判斷空間使用的次數來選擇析構,增加一個類成員 count,但是這樣造成的後果是每一個成員都有一個不同的count 在析構的時候就很混亂還是會出錯
2)然後呢我們會想到使用靜態成員的辦法,因為此時 static int count 是放在靜態區,它是所有物件共享的,不會為每個物件單獨生成一個count,可是當我們有多個不同的成員共同管理一塊空間,而此時我們又用建構函式建立一個物件時候,count又會變為1,所以這種方法還是不行 。
3)使用引用計數,把引用計數放在字串的前四個位元組裡,每個創建出來的物件都有不同的引用計數
class string
{
public:
string(char* pstr) //建構函式
:str(new char[strlen(pstr) + 1+4])//多開闢的4個位元組存放引用計數
{
if (pstr == NULL)
{
str = new char[1];
*str = '\0';
}
else
{
str = new char[strlen(pstr) + 1+4];
int* count = (int* )str;
*count = 1;
count ++; //後移拷貝資料
str = (char*)count;
for (size_t i = 0; i < strlen(pstr); i++) //深複製
{
str[i] = pstr[i];
}
//strcpy(str,pstr);
//memcpy(str,pstr,strlen(pstr)+1);淺複製
}
}
//拷貝建構函式
string(const string& pstr)
{
str = pstr.str;
int * count = (int *)(str-4) ;
++*count;
}
//賦值運算子過載
string& operator = (const string & pstr)
{
if (&str == &pstr.str)
return *this;
destroy();//判斷是否釋放空間
str = pstr.str;
int * count = (int *)(str-4) ;
++*count;
}
void destroy()
{
int * count = (int *)(str-4); //獲取引用計數
if(*count == 1)
{
delete[](str-4);
str = NULL;
}
else
--*count;
}
//解構函式
~string()
{
destroy();
}
private:
char* str;
int * count;
};
寫時拷貝:
在淺拷貝中,假設有多個名稱引用了同一個字串,當其中一個需要修改字串時,這就會導致引用的值發生改變,這明顯不是我們所期望的,所以出現了寫時拷貝,(可能會修改字串的時候使用深拷貝將其與大部隊分離,這樣就保護了其他引用成員)
則我們只需要過載[] 就可以實現
const string& operator[]const
{
string tmp(pstr);
swap(tmp.str, str);
return *this;
}