如何實現乙個string類 1

2021-09-11 21:02:03 字數 3958 閱讀 5359

string類是c++當中用的非常頻繁的乙個類,它提供了很多處理字串的函式,讓字串的使用變得像int、float等built-in型別一樣簡單。string類的實現包含了大量c++語言的知識,其中有很多值得討論的問題。自己動手實現乙個string類是學習c++語言的好方法,可以檢驗自己一下c++基礎知識掌握的如何。下面我們來嘗試動手實現乙個字串類。

我們的字串類需要處理變長的字串,需要根據字串的長度動態的調整記憶體空間。因此我們需要乙個指標,指向儲存字串的記憶體位置。我們還需要乙個變數來記錄字串的長度,同時我們還要乙個變數記錄所申請的記憶體空間大小。另外,我們的字串也不能無限長,我們需要定義乙個最大的長度。因此,我們的string類中需要四個資料成員。

class mystring
由於最大長度對於每個mystring類是固定的,因此我們把它宣告為const,初始化為65536,也就是說我們的string類能儲存的字串最大長度為65536。

成員變數定義好後,我們需要確定如何構造我們的mystring類。c++98為string類提供了7種建構函式,c++11又新增了兩種,實在是太多了!我們不管那麼多,先實現兩個簡單的吧!

首先來實現乙個預設建構函式。既然是預設建構函式,那麼就設定一下各個成員變數的值,然後存乙個空字串吧。 在預設建構函式,雖然存的是乙個空字串,但是我們也給它分配一點空間,主要是為了方便以後拓展。預設建構函式很簡單,實現如下。

mystring::mystring()

通常我們希望在構造string類的時候就給它乙個初始值。那麼我們需要實現乙個帶引數的建構函式,傳入乙個字串指標,通過乙個c風格的字串來構造乙個mystring物件。

mystring::mystring(const char *s)

while (size < len)

p = new char[size + 1];

strncpy(p, s, size);

p[size] = '\0';

}

因為在建構函式中,不需要修改傳入的字串,因此我們把輸入的引數設定為const。如果傳入的字串長度超過了max_size,那麼我們只把字串中前65536個字元寫入mystring。然後要做的就是申請空間了,你可能會想申請空間當然是根據字串的長度來了,只要不超過最大長度,字串有多長就申請多少個位元組的記憶體。

但是想一想這樣做有沒有什麼壞處呢?如果以後我們想拓展字串怎麼辦?是不是又要重新申請空間呢?要知道申請空間可是相當耗時間的,這個辦法不好。那麼我們是不是可以直接申請max_size個位元組的記憶體呢?當然可以的,但是對於每乙個mystring物件我們都申請max_size個位元組的記憶體,實在是太浪費了!要知道我們平常處理的大多數字串都是很短的。

兩種辦法都不行,那怎麼辦?在這裡我們採取跟vector申請記憶體一樣的策略,如果當前記憶體不夠用,那麼就申請一塊大小為當前空間兩倍的記憶體塊來儲存。上面**中的while迴圈就是為了確定要申請的記憶體空間的大小。假設我們傳入建構函式的字串是"hello,world",一共11個字元(結束符另算),那麼我們申請16個位元組來儲存它(結束符也另算)。申請好記憶體後(實際上這裡應該檢查申請記憶體是否成功,p==null?,但是這裡忽略這個問題),我們把傳入的字串拷貝到申請好的記憶體。注意,這裡用strncpy,不要用strcpy,同時記得把最後乙個位元組的值置為0(如果沒有置為0會造成什麼後果,自己試驗一下)。

現在我迫不及待的想知道mystring的建構函式是否可以正常工作。怎麼驗證呢?我們當然可以寫乙個print函式,在函式中把mystring的值列印出來。但是這種辦法實在是太不cpp了,我們來採用乙個更專業的寫法,那就是通過過載《來實現。通過過載《輸出mystring可以像輸出int、float等型別一樣方便。

// mystring.h

friend ostream& operator<< (ostream& out, const mystring& s);

// mystring.cpp

ostream& operator<< (ostream& out, const mystring& s)

我們在標頭檔案中宣告乙個過載《操作符函式,為了直接在函式中操作mystring物件的成員變數,我們把這個函式宣告為友元函式。這個函式的目的是為了輸出字串的值,當然不會對mystring有什麼更改,因此把傳入的mystring引用設定為const。函式的實現我們放在cpp檔案中,**就兩行,so easy!鑑於這個函式如此簡單,我們也可以把它宣告為inline函式,自己試一下。現在我們可以用cout來列印mystring物件了,就像操作int、float等基本型別一樣簡單,試一下看看。

#includeusing namespace std;

int main()

這段**的的執行結果是

$ ./a.exe

hello,world

nice work! 看來我們的建構函式可以正常工作,我們過載的《函式貌似也沒啥問題,哈哈!

嗯…………不要高興的太早!完成建構函式後,我們就要考慮如何析構 。向作業系統老大申請記憶體,大部分時候老大總是慷慨的借給我們,但是我們也要記得及時歸還,否則以後老大不肯借了怎麼辦?現在是時候考慮一下析構函式的問題了。

大家都知道,如果沒有顯式地定義析構函式,編譯器會自動為我們提供乙個析構函式。真是個貼心的大暖男!可惜的是這個大暖男有點笨啊,他只會傻傻的把成員變數挨個釋放掉,對於成員變數指向的動態申請的記憶體塊,他就裝作沒看見一樣,任由這個記憶體塊變成乙個幽靈遊蕩在記憶體之中。這就會帶來乙個另作業系統暴怒,另所有程式設計師心驚膽戰的嚴重問題–記憶體洩漏!所以我們必須為mystring提供乙個析構函式,在析構函式中釋放在建構函式中申請的記憶體。在mystring類的析構函式應該這樣寫:

mystring::~mystring()

看看這個函式,就這麼短短一行,比重載的《函式還短。但是你不寫試試,記憶體洩漏給你看哦~真是人狠話不多的典型!

在我們使用string的時候,經常需要用乙個構造好的string來構造乙個新的string,這個時候就需要用到拷貝建構函式。跟析構函式一樣,如果沒有顯式定義拷貝建構函式,編譯器會為我們提供乙個預設的拷貝建構函式。大暖男再次登場,可惜的是同樣不靠譜。預設的拷貝建構函式只會傻傻的把傳入物件的成員變數挨個賦值給新構造物件的成員變數。對於我們的mystring類,預設的拷貝建構函式做的事情就相當於下面這個函式

mystring::mystring(const mystring& s) 

預設拷貝建構函式會造成什麼問題呢?對於不含指標的類來說,預設建構函式可以正常工作,沒啥問題。暖男可以幫我們少寫幾行**。但是對於含有指標的類來說,預設拷貝建構函式造成的麻煩大了去了。

假設存在mystring物件a,用a來構造物件b,此時會呼叫預設拷貝建構函式。b構造完成後,b的p指標和a的p指標值相等,也就是說兩個指標指向同一塊記憶體。那麼問題來了,當我們通過a的p指標修改記憶體內容時,b的p指標指向的記憶體塊也會跟著改變!這顯然是不符合設計需求的。

如果你以為這就完了,那就大錯特錯,更麻煩的問題還在後面呢!b跟著a修改雖然不符合我們的需求,但是好歹不會造成程式崩潰。那如果我們構造b之後把a物件給析構掉了會怎麼樣呢?

a物件析構之後,a的p指標指向的記憶體塊(同時也是b的p指標指向的記憶體塊)也隨之被釋放掉,此時b的p指標就變成了乙個野指標。當b的生命週期結束後,b的析構函式會嘗試釋放b的p指標指向的記憶體塊,然而這塊記憶體實際上已經在a被析構的時候被釋放掉了,因此造成記憶體二次析構的問題,產生意想不到的後果。

既然預設的拷貝建構函式不靠譜,那我們只好自己實現乙個。實現拷貝建構函式的關鍵是要避免複製構造前後兩個物件的指標指向同一片記憶體。原有物件的內容當然不能亂改,只能重新申請一塊記憶體,用新物件的指標指向這塊記憶體了。因為不能改動原有的物件,因此把傳入的引數設定為const。拷貝建構函式實現如下:

mystring::mystring(const mystring& s) 

如何實現乙個string類 2

首先來看一下賦值運算子過載。在實際應用中,我們經常遇到需要將乙個物件賦值給另外乙個物件的情況,那麼就需要使用賦值運算子 跟預設的拷貝建構函式一樣,如果我們沒有顯式地定義乙個賦值運算子過載函式,那麼編譯器會提供乙個預設的函式實現賦值功能。大暖男再次出場,不出意外地再次不靠譜。編譯器提供的賦值運算函式只...

實現乙個string類

需要實現的基本功能 建構函式 拷貝建構函式 賦值函式 析構函式.以前合稱big three,現在叫做copy control 1 class string 1213 不簡潔版本 14string string const char str else 23 24 2526 string string ...

乙個string類的簡單實現

string類中使用到了賦值建構函式 複製建構函式 建構函式 預設建構函式 析構函式 過載操作符等一些類操作 class string string const char str string const char str,int n string const string src 拷貝建構函式 也...