深入解析淺拷貝和深拷貝
淺拷貝
淺拷貝,也稱位拷貝,編譯器只是將物件中的值拷貝過來,如果物件中管理資源,最後就會導致多個物件共享同一份資源,當一個物件銷燬時就會將該資源釋放掉,而此時另一些物件不知道該資源已經被釋放,以為還有效,所以當繼續對資源進行操作時就會出現訪問違規
先看一段程式碼
class String
{
public :
String(const char *ptr="")//建構函式。預設放\0
:_ptr(new char[strlen(ptr)+1])//strlen計算長度不加\0
{
strcpy(_ptr,ptr);
}
~String()
{
if(_ptr)
{
cout<<this<<endl;
delete[] _ptr;
}
}
private:
char *_ptr;
};
int main()
{
String s1("hello");
String s2(s1);
return 0;
}
執行結果如下:
可以發現這裡程式會崩潰,什麼原因導致崩潰
這就是淺拷貝,多個物件共享同一份資源,造成的問題也顯而易見,一份資源被釋放了多次。
深拷貝
深拷貝就是給所構造的物件重新申請了一段空間
class String
{
public:
String(const char *str="")
{
if(str==NULL)
str="";
_pStr=new char[strlen(str)+1];
strcpy(_pStr,str);
}
String(const String& s)//深拷貝
:_pStr(new char[strlen(s._pStr)+1])//重新申請了一段空間
{
strcpy(_pStr,s._pStr);
}
//賦值運算子過載
//方法一
//String& operator=(const String s)
//{
// if(this != &s)
// {
// delete[] _pStr;
// _pStr=new char[strlen(s._pStr )+1];
// strcpy(_pStr,s._pStr);
// }
// return *this;//為了支援鏈式訪問
//}
//方法二(優)
String& operator=(const String& s)
{
if(this!=&s)//自己不能拷貝自己
{
char *tmp=new char[strlen(s._pStr )+1];
strcpy(tmp,s._pStr );
_pStr=tmp;
}
return *this;
}
~String()
{
if(_pStr)
{
delete[] _pStr;
}
}
private:
char *_pStr;
};
int main()
{
String s1("hello");
String s2(s1);
return 0;
}
一般情況下,上面對賦值運算子過載的兩種寫法都可以,但是相對而言,第二種更優一點,對於第一種,先釋放了舊空間,但是如果下面用new開闢新空間時有可能失敗,丟擲異常而這時將s2賦值給s3,不僅沒有賦值成功,而且也破壞了原有的s3物件,對於第二種,先開闢新空間,將新空間賦給一個臨時變數,就算這時空間開闢失敗,也不會影響原本s3物件
引用計數
當多個物件共享一塊資源時,要保證該資源只釋放一次,只需記錄有多少個物件在使用該資源即可,沒減少(增加)一個物件使用,給該計數減一(加一),當最後一個物件不使用時,該物件負責將資源釋放掉即可
class String
{
public:
String()
{}
String(const char *str)
{
if(str==NULL)
{
_str=new char[4+1];//多申請一個int來儲存計數器
_str+=4;
_str='\0';
}
else
{
_str=new char[strlen(str)+1+4];
_str+=4;
strcpy(_str,str);
}
GetCount(_str)=1;//將計數器初始值設為1
}
String(const String& s)
:_str(s._str)
{
GetCount(_str)++;
}
//s1=s2;
String operator=(const String& s)
{
if(_str!=s._str )
{
//1.當s1的引用計數為1時
//a.釋放空間
//b.改變s1指標的指向
//c.s2的引用計數加1
Release();
//當s1的引用計數大於1時
//a.s1引用計數減1
//b.同上b,c
_str=s._str ;
++GetCount(_str);//引用計數加1
}
return *this;
}
~String()
{
Release();
}
private:
int& GetCount(char * str)
{
return *(int *)(str-4);//因為這塊記憶體型別為char
}
void Release()
{
if(_str!=NULL && (--GetCount(_str))==0)
delete[] (_str-4);//一定要釋放儲存計數器的空間
}
private:
char *_str;
};
int main()
{
String s1("hello");
String s2(s1);
String s3("world");
String s4(s3);
String s5(s3);
return 0;
}
上面的程式碼是在構造的時候多申請4位元組空間來儲存計數器,從而實現計數
但是這樣還是有問題,看下面的程式碼
class String
{
public:
String()
{}
String(const char *str)
{
if(str==NULL)
{
_str=new char[4+1];//多申請一個int來儲存計數器
_str+=4;
_str='\0';
}
else
{
_str=new char[strlen(str)+1+4];
_str+=4;
strcpy(_str,str);
}
GetCount(_str)=1;//將計數器初始值設為1
}
String(const String& s)
:_str(s._str)
{
GetCount(_str)++;
}
//s1=s2;
String operator=(const String& s)
{
if(_str!=s._str )
{
//1.當s1的引用計數為1時
//a.釋放空間
//b.改變s1指標的指向
//c.s2的引用計數加1
Release();
//當s1的引用計數大於1時
//a.s1引用計數減1
//b.同上b,c
_str=s._str ;
++GetCount(_str);//引用計數加1
}
return *this;
}
char& operator[](size_t index)//可以用下標的方式來訪問String類
{
return _str[index];
}
~String()
{
Release();
}
private:
int& GetCount(char * str)
{
return *(int *)(str-4);//因為這塊記憶體型別為char
}
void Release()
{
if(_str!=NULL && (--GetCount(_str))==0)
delete[] (_str-4);//一定要釋放儲存計數器的空間
}
private:
char *_str;
};
int main()
{
String s1("hello");
String s2(s1);
String s3(s1);
s3[1]='a';
return 0;
}
當共用同一塊空間的物件的任一物件修改字串中的值,則會導致所有共用這塊空間中的內容全被改變,我們只想改變s3的值,但是和它共用的兩個物件的值也全改變,這就引出了寫時拷貝
寫時拷貝
有多個物件共享同一空間時,當對其中一個物件只讀時,不會有什麼影響,但是想要改變某個物件的值時,這時就要為這個物件重新分配空間,程式碼實現如下
class String
{
public:
String()
{}
String(const char *str)
{
if(str==NULL)
{
_str=new char[4+1];//多申請一個int來儲存計數器
_str+=4;
_str='\0';
}
else
{
_str=new char[strlen(str)+1+4];
_str+=4;
strcpy(_str,str);
}
GetCount()=1;//將計數器初始值設為1
}
String(const String& s)
:_str(s._str)
{
GetCount()++;
}
//s1=s2;
String operator=(const String& s)
{
if(_str!=s._str )
{
Release();
_str=s._str ;
++GetCount();//引用計數加1
}
return *this;
}
char& operator[](size_t index)//可以用下標的方式來訪問String類
{
if(GetCount()>1)
{
--GetCount();
char* pTemp=new char[strlen(_str)+1+4];
pTemp+=4;
strcpy(pTemp,_str);
_str=pTemp;
GetCount()=1;//將新空間置1
}
return _str[index];
}
~String()
{
Release();
}
private:
int& GetCount()
{
return *(int *)(_str-4);//因為這塊記憶體型別為char
}
void Release()
{
if(_str!=NULL && (--GetCount())==0)
delete[] (_str-4);//一定要釋放儲存計數器的空間
}
private:
char *_str;
};
int main()
{
String s1("hello");
String s2(s1);
String s3(s1);
s3[1]='a';
return 0;
}