1.前言
最近看了下《C++Primer》,覺得受益匪淺。不過紙上得來終覺淺,覺知此事須躬行。今天看了類類型,書中簡單實現了String類,自己以前也學過C++,不過說來慚愧,以前都是用C來寫程序,學的C++基本都忘記了,也說明自己以前對C++的理解不夠深入。基於這些,覺得有必要動手來寫寫C++的一些程序了,畢竟C++有很多的功能是C所不具備的。正好看了課本中String類的簡單實現,而且string類在C++中的使用頻率也很高,了解其內部的實現是很有必要的。所以今天打算寫個string類,就當做練手吧。
2.String類的設計
寫類首先是定義類的名字,我們實現的string就叫String吧,以防和標准庫中的string類沖突。其次是類中的內部數據,既然是字符串類,必須要把字符串類所代表的字符串保存起來,所以必須定義一個存放這些字符的字符數組或者一個指向字符數組的指針。我是用后者實現,因為這樣更為的靈活,不會因為提前定義數組的大小而限制了能存放到數組中的字符個數。為了方便,可以定義字符串的長度,當然也可以不定義,可以在字符數組的末尾存放0來表示字符串的結束,不過每次得到字符串的長度比較麻煩。最后是類對外的接口,根據我們平時使用string類的情況,我們一般會用到接口size(), c_str(),還有就是+,=,>>,<<,+=,[],==等操作符。
根據前面的分析,可以得到我們要設計的String類中的成員如下所示:
1 class String 2 { 3 public: 4 String(); 5 String(const char *); 6 String(const String &); 7 String(String &&); //新加的move構造函數 8 9 ~String(); 10 11 String& operator=(const char *); 12 String& operator=(const String &); 13 14 bool operator==(const char *); 15 bool operator==(const String &); 16 17 char &operator[](int); 18 19 String operator+(const char *); 20 String operator+(const char); 21 String operator+(const String &); 22 23 String &operator +=(const char *); 24 String &operator +=(const char); 25 String &operator +=(const String &); 26 27 int size(){return _size;} 28 char *c_str(){return _string;} 29 30 friend istream &operator>>(istream &cin, String &str); 31 private: 32 int _size; 33 char *_string; 34 };
3.String類的主要實現
我們先來說一下構造函數的實現。由於我想實現的String類中存放字符的數組大小是可以根據實際需要存放字符的個數來動態調整的(這樣對於存放字符的個數就沒有限制,除非內存不夠用了),所以必須根據實際存放的字符個數來動態的申請內存空間,代碼可以用如下的方式實現:
1 String::String(const char *str) 2 { 3 if (!str) 4 { 5 _size = 0; 6 _string = NULL; 7 } 8 else 9 { 10 _size = strlen(str); 11 _string = new char[_size + 1]; 12 strcpy(_string, str); 13 _string[_size] = 0; 14 } 15 }
由於構造函數動態申請了內存,所以必須定義析構函數來釋放我們申請的內存空間。
1 String::~String() 2 { 3 if (_string) 4 delete _string; 5 6 cout << "~String() call" << endl; 7 }
同樣,由於我們每個String類都會動態的申請內存空間,所以必須定義拷貝構造函數,否則默認的拷貝構造函數會導致多個String對象共享相同內存空間的問題(深拷貝與淺拷貝的的問題)。代碼和前面的構造函數差不多。
1 String::String(const String &str) 2 { 3 if (!str._size) 4 { 5 _size = 0; 6 _string = NULL; 7 } 8 else 9 { 10 _size = str._size; 11 _string = new char[_size + 1]; 12 strcpy(_string, str._string); 13 _string[_size] = 0; 14 } 15 16 }
其它的函數就是對+,=,+=操作符的重載,原理都是一樣的,需要重新分配內存空間來適應新的字符串個數的需求,不同的是+的返回類型不需要使用引用。具體代碼如下(用+=實現的):
String String::operator+(const String &str) { assert(_string && str._string); String str_temp(*this); str_temp += str; return std::move(str_temp); }
由於函數+會返回對象,編譯器會默認產生一個臨時的對象,這個對象完全是返回對象的拷貝,而返回對象馬上就會析構掉,所以如果我們能把返回對象中的字符數組地址拷貝到臨時對象中,那么臨時對象就可以不用再申請內存了,這樣可以提供效率。所以這里我實現了一個move函數( tangzhnju的提醒):
String::String(String && str) { _size = str._size; _string = str._string; str._string = NULL; }
還有我們經常用的是直接對string類進行輸出,所以我們必須重載<<,>>操作符,由於輸入需要用到String類中的私有成員變量_string,所以應該把輸入函數設置為String類的友元函數。而輸出可以通過調用c_str函數獲得字符數組地址來輸出。
istream &operator>>(istream &cin, String &str) { const int limit_string_size = 4096; str._string = new char[limit_string_size]; cin >> setw(limit_string_size) >> str._string; str._size = strlen(str._string); return cin; } ostream &operator<<(ostream &cout, String &str) { return cout << str.c_str(); }
編寫這個String類遇到了兩個小的問題,一個就是在寫+=操作符重載函數的時候,忘記在函數的前面寫String::,導致編譯器總是包各種莫名的錯誤,什么參數不對,不能訪問內部成員,這個問題完全是自己粗心導致的。另一個問題就是寫+操作符重載函數,開始寫+函數的時候直接修改了當前的String對象,后來測試發現這是有問題的,因為+應該重新返回一個新的String對象,這個新String對象是當前String對象與傳入參數的字符串的和,這個問題完全是自己沒有想清楚+與+=的區別,+=才是要修改當前String對象的。
