原文:http://noalgo.info/382.htmlhtml
String是C++中的重要類型,程序員在C++面試中常常會遇到關於String的細節問題,甚至要求當場實現這個類。只是因爲時間關係,可能只要求實現構造函數、析構函數、拷貝構造函數等關鍵部分。
String的實現涉及不少C++的基礎知識、內存控制及異常處理等問題,仔細研究起來很是複雜,本文主要作一個簡單的總結和概括。c++
面試時因爲時間關係,面試官通常不會要求很詳盡的String的功能,通常是要求實現構造函數、拷貝構造函數、賦值函數、析構函數這幾個很是重要的部分。由於String裏涉及動態內存的管理,默認的拷貝構造函數在運行時只會進行淺複製,即只複製內存區域的指針,會形成兩個對象指向同一塊內存區域的現象。若是一個對象銷燬或改變了該內存區域,會形成另外一個對象運行或者邏輯上出錯。這時就要求程序員本身實現這些函數進行深複製,即不止複製指針,須要連同內存的內容一塊兒複製。程序員
除了以上四個必須的函數,這裏還實現了一些附加的內容。面試
總體的類的框架以下所示。數組
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會引起內存錯誤。this
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 . . . */ }