string類的實現


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》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM