一. 環境
Linux x86_64,g++ 8.5.0
二. 實現
自實現 string 之前一直想寫來著,一直拖著,現在把它完稿。這個版本是比較簡單的版本,有一些可能有不同的或者更好的實現方式,後面有機會會加到裡面。
打算實現的介面如下
class MyString
{
friend std::ostream & operator<<(std::ostream & co, const MyString &ms);
friend std::istream & operator>>(std::istream & ci, MyString &ms);
public:
MyString(const char * s = nullptr);
~MyString();
MyString(const MyString & another);
MyString & operator=(const MyString & another);
MyString operator+(const MyString & another);
MyString & operator+=(const MyString & another);
bool operator>(const MyString & another);
bool operator<(const MyString & another);
bool operator==(const MyString & another);
char & operator[](int n);
char & at(int n);
private:
char * m_str;
};
- 建構函式,引數使用預設引數,預設引數作標記位。不論是否傳遞實參,申請資源時均以陣列形式申請。不傳遞實參時,申請一個 char 的陣列,有傳遞實參時,以實際的為準。這樣,在釋放資源時,均可以
delete []m_str
形式釋放。
MyString::MyString(const char *str)
{
if (nullptr == str)
{
m_str = new char[1];
*m_str = '\0';
}
else
{
m_str = new char[strlen(str)+1];
strcpy(m_str, str);
}
}
- 複製賦值,使用了兩種方式。
第一種是基礎的寫法,先 delete 堆上的空間,再申請新的空間,然後複製內容。需要注意的是,需判斷是否是自賦值的情況。
第二種採用了 copy && swap 技術,相對完善一點,前一種方式,在 delete []m_str
後如果程式出現異常,此時其它地方有使用到 m_str
的話就尷尬了,而後一種方式就沒有這個問題。
// version1
/*
MyString & MyString::operator=(const MyString &another)
{
if (this == &another)
{
return *this;
}
delete []m_str;
int len = strlen(another.m_str);
m_str = new char[len+1];
strcpy(m_str, another.m_str);
return *this;
}
*/
// version2,採用 copy and swap 技術
MyString & MyString::operator=(const MyString &another)
{
if (this == &another)
{
return *this;
}
MyString ms(another);
std::swap(this->m_str, ms.m_str);
return *this;
}
- 過載
+
運算子,成員函式返回一個臨時物件。在申請新的空間後,在使用strcat()
之前需要初始化,否則可能會出現問題。strcat()
是從末尾為 '\0' 的地方開始拼接的。
MyString MyString::operator+(const MyString &another)
{
MyString ms;
int len = strlen(this->m_str) + strlen(another.m_str);
delete []ms.m_str;
ms.m_str = new char[len +1]{0}; // 注意初始化
strcat(strcat(ms.m_str, this->m_str), another.m_str);
return ms;
}
- 過載
+=
運算子,返回值型別是引用型別,這樣可以連續使用+=
。
使用 realloc()
後,在使用 strcat()
連線兩個字串之前,需要將 m_str
後面一部分新擴充的空間進行初始化。
MyString & MyString::operator+=(const MyString &another)
{
int lenOfSource = strlen(this->m_str);
int lenOfAnother = strlen(another.m_str);
this->m_str = (char *)realloc(this->m_str, lenOfSource+lenOfAnother+1);
memset(this->m_str+lenOfSource, 0, lenOfAnother+1);
strcat(this->m_str, another.m_str);
return *this;
}
- 過載
>
運算子
bool MyString::operator>(const MyString &another)
{
return strcmp(this->m_str, another.m_str) > 0;
}
過載 <
和 ==
與上面類似,就不重複列舉了。
- 過載 [] 運算子,這個沒啥好說的了。
char & MyString::operator[](int n)
{
return m_str[n];
}
- 成員函式 at()
char & MyString::at(int n)
{
return m_str[n];
}
- 過載輸出 << 和 輸入 >> 運算子。
在測試成員函式前,可以早點寫這兩個函式,測試時就方便列印了,不然還需要單獨新增一個成員函式返回 m_str
了。
過載運算子,目標形式是:
Mystring ms;
cout << ms;
cin >> ms;
對於過載,一般會考慮到成員函式過載和全域性過載,但是 ostream
類和 istream
類都是系統提供的類,我們不可能在 ostream
類和 istream
類中進行修改,因此只能放棄成員函式過載。此時,只能是全域性過載,即全域性函式過載了。
考慮到會連續輸出(cout << a << b;
),因此返回型別是 ostream &
型別,它是經入參而來,入參型別也是 ostream &
。
std::ostream & operator<<(std::ostream & co, const MyString &ms)
{
co << ms.m_str;
return co;
}
輸入運算子與輸出運算子類似,第二個入參不能是 const
型別,因為需要修改入參 ms
。這裡處理的相對簡單了,棧上申請了1024位元組的字元陣列用以儲存輸入的資料,實際上會有不夠用的情況。
std::istream & operator>>(std::istream & ci, MyString &ms)
{
// 簡單處理,申請一塊固定大小的記憶體
char ch[1024];
ci >> ch;
delete []ms.m_str;
ms.m_str = new char[strlen(ch)+1];
strcpy(ms.m_str, ch);
return ci;
}
三. 完整程式碼,可點選連結 mystring ,如有有問題或不到之處,請指出並交流,看到後我會修改。
四. 參考
C++基礎與提高 王桂林