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)普通建構函式
引數為 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; }
4、詳細說明
以“重構賦值運算子”例,詳細解說注意事項
(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的記憶體,這就相當於自動呼叫解構函式釋放例項的記憶體。即利用臨時例項的生命週期自動釋放原來例項內容。
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》