String 的一種簡單實現
阿新 • • 發佈:2018-12-27
C++ 的一個常見面試題是讓你實現一個 String 類,限於時間,不可能要求具備 std::string 的功能,但至少要求能正確管理資源。具體來說:
- 能像 int 型別那樣定義變數,並且支援賦值、複製。
- 能用作函式的引數型別及返回型別。
- 能用作標準庫容器的元素型別,即 vector/list/deque 的 value_type。(用作 std::map 的 key_type 是更進一步的要求,本文從略)。
換言之,你的 String 能讓以下程式碼編譯執行通過,並且沒有記憶體方面的錯誤。
void foo(String x) { } void bar(const String& x) { } String baz() { String ret("world"); return ret; } int main() { String s0; String s1("hello"); String s2(s0); String s3 = s1; s2 = s1; foo(s1); bar(s1); foo("temporary"); bar("temporary"); String s4 = baz(); std::vector<String> svec; svec.push_back(s0); svec.push_back(s1); svec.push_back(baz()); svec.push_back("good job"); }
首先選擇資料成員,最簡單的 String 只有一個 char* 成員變數。好處是容易實現,壞處是某些操作的複雜度較高(例如 size() 會是線性時間)。為了面試時寫程式碼不出錯,本文設計的 String 只有一個 char* data_成員。而且規定 invariant 如下:一個 valid 的 string 物件的 data_ 保證不為 NULL,data_ 以 '\0'
結尾,以方便配合 C 語言的 str*() 系列函式。
其次決定支援哪些操作,構造、析構、拷貝構造、賦值這幾樣是肯定要有的(以前合稱 big three,現在叫 copy control)。如果鑽得深一點,C++11的移動構造和移動賦值也可以有。為了突出重點,本文就不考慮 operator[] 之類的過載了。
#include <utility> #include <string.h> class String { public: String() : data_(new char[1]) { *data_ = '\0'; } String(const char* str) : data_(new char[strlen(str) + 1]) { strcpy(data_, str); } String(const String& rhs) : data_(new char[rhs.size() + 1]) { strcpy(data_, rhs.c_str()); } /* Delegate constructor in C++11 String(const String& rhs) : String(rhs.data_) { } */ ~String() { delete[] data_; } /* Traditional: String& operator=(const String& rhs) { String tmp(rhs); swap(tmp); return *this; } */ String& operator=(String rhs) // yes, pass-by-value { swap(rhs); return *this; } // C++ 11 String(String&& rhs) : data_(rhs.data_) { rhs.data_ = nullptr; } String& operator=(String&& rhs) { swap(rhs); return *this; } // Accessors size_t size() const { return strlen(data_); } const char* c_str() const { return data_; } void swap(String& rhs) { std::swap(data_, rhs.data_); } private: char* data_; };
注意程式碼的幾個要點:
- 只在建構函式裡呼叫 new char[],只在解構函式裡呼叫 delete[]。
- 賦值操作符采用了《C++程式設計規範》推薦的現代寫法。
- 每個函式都只有一兩行程式碼,沒有條件判斷。
- 解構函式不必檢查 data_ 是否為 NULL。
- 建構函式
String(const char* str)
沒有檢查 str 的合法性,這是一個永無止境的爭論話題。這裡在初始化列表裡就用到了 str,因此在函式體內用 assert() 是無意義的。