string類底層是一個字符串指針
1、類結構定義
#include <iostream>
#include <cstring>
using namespace std;
class CMyString
{
private:
char* m_pDate;
public:
CMyString(const char* pDate = NULL); //普通構造函數,const:防止修改
CMyString(const CMyString& other); //拷貝構造函數,const:防止修改,&:省去調用復制構造函數提高效率,涉及深拷貝、淺拷貝
~CMyString(); //析構函數
CMyString& operator = (const CMyString& other); //重構賦值運算符,返回引用:為了連續賦值,const:防止修改,&:省去調用復制構造函數提高效率,涉及安全性
//CMyString& operator + (const CMyString& other);
//bool operator == (const CMyString& other);
int getLength();
void printString(){ cout<<m_pDate<<endl; } //用於測試
};
2、簡單說明
正如代碼中注釋部分說明,
const 是為了防止函數內部修改;
& 是為了省去隱式調用拷貝構造函數,從而提高效率;
3、詳細解說
以“重構賦值運算符”例,詳細解說注意事項
(1)是否把返回值的類型聲明為該類型的引用,並在函數結束前返回實例自身的引用(即*this)。
只有返回一個引用,才可以允許連續賦值。否則如果函數的返回值是void,應用該賦值運算符將不能做連續賦值。假設有3個CMyString的對象:str1、str2和str3,在程序中語句str1=str2=str3將不能通過編譯 。若只是兩個對象之間的賦值,返回值為void也可以達到效果。
(2)是否把傳入的參數的類型聲明為常量引用。
如果傳入的參數不是引用而是實例,那么從形參到實參會調用一次復制構造函數。把參數聲明為引用可以避免這樣的無謂消耗,能提高代碼的效率。同時,我們在賦值運算符函數內不會改變傳入的實例的狀態,因此應該為傳入的引用參數加上const關鍵字。即省去調用復制構造函數,提高效率。
(3)是否釋放實例自身已有的內存。
如果我們忘記在分配新內存之前釋放自身已有的空間,程序將出現內存泄露。
(4)是否判斷傳入的參數和當前的實例(*this)是不是同一個實例。
避免自賦值,如果是同一個,則不進行賦值操作,直接返回。如果事先不判斷就進行賦值,那么在釋放實例自身的內存的時候就會導致嚴重的問題:當*this和傳入的參數是同一個實例時,那么一旦釋放了自身的內存,傳入的參數的內存也同時被釋放了,因此再也找不到需要賦值的內容了。即存在非法訪問或者多次釋放同一內存單元的風險。
(5)是否有申請內存失敗的安全處理。
如果此時內存不足導致new char拋出異常,m_pData將是一個空指針,這樣非常容易導致程序崩潰。先創建一個臨時實例,再交換臨時實例和原來的實例。把strTemp.m_pData和實例自身的m_pData做交換。由於strTemp是一個局部變量,但程序運行到 if 的外面時也就出了該變量的作用域,就會自動調用strTemp 的析構函數,把 strTemp.m_pData 所指向的內存釋放掉。由於strTemp.m_pData指向的內存就是實例之前m_pData的內存,這就相當於自動調用析構函數釋放實例的內存。即利用臨時實例的生命周期自動釋放原來實例內容。
4、類成員函數實現
(1)普通構造函數
參數為 const 防止修改
strlen計算字符串長度沒有把'\0'算進去,所以要+1
CMyString::CMyString(const char* pDate)
{
if( pDate == NULL )
{
m_pDate = new char[1];
*m_pDate = '\0';
}
else
{
//strlen計算字符串長度沒有吧'\0'算進去
m_pDate = new char[strlen(pDate)+1];
strcpy(m_pDate, pDate);
}
}
(2)拷貝構造函數
參數為 const 防止修改
參數加 & 省去調用賦值構造函數提高效率
(2.1)淺拷貝,也叫位拷貝

CMyString::CMyString( const CMyString& other ) //淺拷貝
{
//沒有重新申請新空間,共用同一塊內存空間
//隱患:非法訪問,重復釋放內存
m_pDate = other.m_pDate;
}
淺拷貝引發的錯誤

(2.2)深拷貝

CMyString::CMyString( const CMyString& other ) //深拷貝
{
//delete m_pDate;//既然也是屬於構造函數的一類,初始為空,不必delete
if( other.m_pDate == NULL )
{
m_pDate = NULL;
}
else
{
m_pDate = new char[strlen(other.m_pDate)+1];
strcpy(m_pDate, other.m_pDate);
}
}
(3)析構函數
釋放前判斷,避免重復釋放
CMyString::~CMyString()
{
if(m_pDate) //釋放前判斷,避免重復釋放
{
delete m_pDate;
m_pDate = NULL;
}
}
(4)重載賦值運算符
返回引用 實現連續賦值
參數為 const 防止修改
參數加 & 省去調用賦值構造函數提高效率
(4.1)不安全實現
CMyString& CMyString::operator = ( const CMyString& other )
{
if( &other != this ) //避免自賦值
{
if( m_pDate ) //先判斷再刪除,避免重復操作
delete m_pDate;
m_pDate = new char[strlen(other.m_pDate)+1]; //如果申請失敗,后面strcpy會不安全
strcpy(m_pDate, other.m_pDate);
}
return *this;
}
(4.2)安全實現
利用臨時實例巧妙實現安全轉移
CMyString& CMyString::operator = ( const CMyString& other )
{
if( &other != this ) //避免自賦值
{
CMyString tmpOther(other);
//讓tmpOther跟this交換date
char *tmpDate = tmpOther.m_pDate;
tmpOther.m_pDate = m_pDate;
m_pDate = tmpDate;
//臨時實例tmpOther退出if會自動調用析構函數,清除了原本m_pDate的內容
}
return *this;
}
5、輸出實例
int main()
{
CMyString str("hello"); //等同於 const char* p = "hello"; CMyString str(p);
str.printString();
cout<<"拷貝構造函數"<<endl;
CMyString str1(str);
str1.printString();
cout<<"重載賦值操作符"<<endl;
CMyString str2("world");
str2.printString();
CMyString str3("Birthday");
str3.printString();
str1 = str2 = str3;
str1.printString();
str2.printString();
str3.printString();
cout<<str1.getLength()<<endl;
return 0;
}
輸出樣子:

6、參考
《后台開發》核心技術與應用實踐
《劍指Offer》
