原文:http://noalgo.info/382.html
String是C++中的重要類型,程序員在C++面試中經常會遇到關於String的細節問題,甚至要求當場實現這個類。只是由於時間關系,可能只要求實現構造函數、析構函數、拷貝構造函數等關鍵部分。
String的實現涉及很多C++的基礎知識、內存控制及異常處理等問題,仔細研究起來非常復雜,本文主要做一個簡單的總結和歸納。
一 整體框架
面試時由於時間關系,面試官一般不會要求很詳盡的String的功能,一般是要求實現構造函數、拷貝構造函數、賦值函數、析構函數這幾個非常重要的部分。因為String里涉及動態內存的管理,默認的拷貝構造函數在運行時只會進行淺復制,即只復制內存區域的指針,會造成兩個對象指向同一塊內存區域的現象。如果一個對象銷毀或改變了該內存區域,會造成另一個對象運行或者邏輯上出錯。這時就要求程序員自己實現這些函數進行深復制,即不止復制指針,需要連同內存的內容一起復制。
除了以上四個必須的函數,這里還實現了一些附加的內容。
- 若干個運算符重載,這里的幾個是常見的運算符,可以加深對String的認識和運算符重載的理解。
- 兩個常用的函數,包括取字符串長度和取C類型的字符串。
- 兩個處理輸入輸出的運算符重載,為了使用的方便,這里把這兩個運算符定義為友元函數。
整體的類的框架如下所示。
class String { public: String(const char *str = NULL); //通用構造函數 String(const String &str); //拷貝構造函數 ~String(); //析構函數 String operator+(const String &str) const; //重載+ String& operator=(const String &str); //重載= String& operator+=(const String &str); //重載+= bool operator==(const String &str) const; //重載== char& operator[](int n) const; //重載[] size_t size() const; //獲取長度 const char* c_str() const; //獲取C字符串 friend istream& operator>>(istream &is, String &str);//輸入 friend ostream& operator<<(ostream &os, String &str);//輸出 private: char *data; //字符串 size_t length; //長度 };
注意,類的成員函數中,有一些是加了const修飾的,表示這個函數不會對類的成員進行任何修改。一些函數的輸入參數也加了const修飾,表示該函數不會對改變這個參數的值。
二 具體實現
下面逐個進行成員函數的實現。
同樣構造函數適用一個字符串數組進行String的初始化,默認的字符串數組為空。這里的函數定義中不需要再定義參數的默認值,因為在類中已經聲明過了。
另外,適用C函數strlen的時候需要注意字符串參數是否為空,對空指針調用strlen會引發內存錯誤。
String::String(const char *str)//通用構造函數 { if (!str) { length = 0; data = new char[1]; *data = '\0'; } else { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } }
拷貝構造函數需要進行深復制。
String::String(const String &str)//拷貝構造函數 { length = str.size(); data = new char[length + 1]; strcpy(data, str.c_str()); }
析構函數需要進行內存的釋放及長度的歸零。
String::~String()//析構函數 { delete []data; length = 0; }
重載字符串連接運算,這個運算會返回一個新的字符串。
String String::operator+(const String &str) const//重載+ { String newString; newString.length = length + str.size(); newString.data = new char[newString.length + 1]; strcpy(newString.data, data); strcat(newString.data, str.data); return newString; }
重載字符串賦值運算,這個運算會改變原有字符串的值,為了避免內存泄露,這里釋放了原先申請的內存再重新申請一塊適當大小的內存存放新的字符串。
String& String::operator=(const String &str)//重載= { if (this == &str) return *this; delete []data; length = str.length; data = new char[length + 1]; strcpy(data, str.c_str()); return *this; }
重載字符串+=操作,總體上是以上兩個操作的結合。
String& String::operator+=(const String &str)//重載+= { length += str.length; char *newData = new char[length + 1]; strcpy(newData, data); strcat(newData, str.data); delete []data; data = newData; return *this; }
重載相等關系運算,這里定義為內聯函數加快運行速度。
inline bool String::operator==(const String &str) const//重載== { if (length != str.length) return false; return strcmp(data, str.data) ? false : true; }
重載字符串索引運算符,進行了一個簡單的錯誤處理,當長度太大時自動讀取最后一個字符。
inline char& String::operator[](int n) const//重載[] { if (n >= length) return data[length-1]; //錯誤處理 else return data[n]; }
重載兩個讀取私有成員的函數,分別讀取長度和C字符串。
inline size_t String::size() const//獲取長度 { return length; }
重載輸入運算符,先申請一塊足夠大的內存用來存放輸入字符串,再進行新字符串的生成。這是一個比較簡單朴素的實現,網上很多直接is>>str.data的方法是錯誤的,因為不能確定str.data的大小和即將輸入的字符串的大小關系。
istream& operator>>(istream &is, String &str)//輸入 { char tem[1000]; //簡單的申請一塊內存 is >> tem; str.length = strlen(tem); str.data = new char[str.length + 1]; strcpy(str.data, tem); return is; }
重載輸出運算符,只需簡單地輸出字符串的內容即可。注意為了實現形如cout<<a<<b的連續輸出,這里需要返回輸出流。上面的輸入也是類似。
ostream& operator<<(ostream &os, String &str)//輸出 { os << str.data; return os; } inline const char* String::c_str() const//獲取C字符串 { return data; }
三 功能測試
編碼完成后需要對代碼進行測試,以下是一個簡單但不夠嚴謹的測試。
int main() { String s; cin >> s; cout << s << ": " << s.size() << endl; char a[] = "Hello", b[] = "World!"; String s1(a), s2(b); cout << s1 << " + " << s2 << " = " << s1 + s2 << endl; String s3 = s1 + s2; if (s1 == s3) cout << "First: s1 == s3" << endl; s1 += s2; if (s1 == s3) cout << "Second: s1 == s3" << endl; /*程序輸入輸出為: 123456789 123456789: 9 Hello + World! = HelloWorld! Second: s1 == s3 Press any key to continue . . . */ }