string類的實現

Christal_R 發表於 2021-04-07

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)淺拷貝,也叫位拷貝

string類的實現

CMyString::CMyString( const CMyString& other ) //淺拷貝
{
	//沒有重新申請新空間,共用同一塊記憶體空間
	//隱患:非法訪問,重複釋放記憶體
	m_pDate = other.m_pDate;
}

淺拷貝引發的錯誤

string類的實現

(2.2)深拷貝

string類的實現

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;
}

輸出樣子:

string類的實現

 

6、參考

《後臺開發》核心技術與應用實踐

《劍指Offer》