您好,登錄后才能下訂單哦!
這篇文章主要介紹了C++怎么實現String類的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇C++怎么實現String類文章都會有所收獲,下面我們一起來看看吧。
首先我們不考慮string類的增刪查改,只是先給string類搭建一個最簡單的框架出來。
和C語言中相同,為了存儲一個字符串,我們的string類需要一個char*的指針來指向字符像這個對象。作為一個對象,string還需要有構造函數,析構函數和拷貝構造。
class string { private: char *_str; public: string(const char *str) : _str(new char[strlen(str) + 1]) // +1 是給'\0'留出位置 { strcpy(_str, str); } string(const string &str) : _str(new char[strlen(str._str) + 1]) { strcpy(_str, str._str); } ~string() { if (_str) { delete[] _str; _str = nullptr; } } };
有的朋友可能會疑惑,這里的構造函數和拷貝構造函數為什么不用編譯器自動生成的,直接將_str指向原本的字符串就可以了,為什么還要開辟空間呢?
這是因為我們在日常使用中,假如有兩個string類 a 和 b,b是由a拷貝構造而來,一般情況下我們在修改b的同時不希望a也被改。此外,如果直接將_str指向原本的字符串會導致的問題是當 a 和 b用完被銷毀時,會對同一片空間調用兩次析構函數,對同一片空間釋放兩次。所以在這里,我們需要重新開辟一片空間來給這個string。這也就是所謂的深拷貝。
然后,為了訪問string類中的元素,我們需要對運算符[]進行重載。
char& operator[](size_t pos) { assert(pos < strlen()) return _str[pos]; }
這樣我們就實現了一個簡單的string類。
構造函數,析構函數,拷貝構造
之前我們實現的一個string類是一個最簡單的string類,它沒有辦法進行增刪查改,接下來我們就來一點一點完善它。
要實現增刪查改,我們還需要兩個變量,一個記錄string類當前長度,一個記錄string類的容量大小。加入這兩個變量后,我們原本的構造函數,拷貝構造和析構函數需要發生一點點變化。
class string { private: char *_str; size_t _size; size_t _capacity; public: string(const char *str = "") : _size(strlen(str)), _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); } string(const string &str) : _size(str._size), _capacity(str._capacity) { _str = new char[_size + 1]; strcpy(_str, str._str); } ~string() { if (_str) { delete[] _str; _str = nullptr; _size = _capacity = 0; } } };
運算符重載
接下來我們來實現一下,string類的運算符。在實現運算符重載時,我們需要做的只是實現少數幾個運算符即可,其他的運算符可復用前面實現的運算符來達到我們想要的效果。
//關系運算符的重載 bool operator>(const string &s) { return strcmp(_str, s.c_str()); } bool operator==(const string &s) { return strcmp(_str, s.c_str()) == 0; } bool operator!=(const string &s) { return !(*this == s); } bool operator>=(const string &s) { return *this > s || *this == s; } bool operator<(const string &s) { return !(*this >= s); } bool operator<=(const string &s) { return !(*this > s); } //操作運算符的重載 string &operator=(string& str) { if(*this != str) { char *tmp = new char[str._capacity + 1]; strcpy(tmp,str._str); delete[] _str; _str = tmp; _size = str._size; _capacity = str._capacity; } return *this; } char &operator[](size_t pos) { assert(pos < _size); return *(_str + pos); } const char &operator[](size_t pos) const { assert(pos < _size); return *(_str + pos); }
string接口實現
首先是比較簡單的size(),empty(),capacity(),clear()。這些接口大部分直接訪問string類的成員變量就可以得到結果。
size_t size() const { return _size; } size_t capacity() const { return _capacity; } bool empty() const { return 0 == _size; } //后面添加const的目的是為了讓所有對象都可以進行訪問 void clear() { _str[0] = '\0'; _size = 0; _capacity = 0; }
因為后面的接口大部分都需要進行空間的調整,所以首先我們將調整空間的接口,reserve和resize實現。
void reserve(size_t n) { if (n > _capacity) //判斷是否需要擴容 { char *tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } //resize和reserve的區別在于,reserve只是開空間,而resize還要進行初始化 void resize(size_t n, char c = '\0') { if (n > _capacity) { reserve(n); //開空間復用reserve } for (size_t i = _size; i < n; ++i) { _str[i] = c; } _size = n; _str[_size] = '\0'; }
接下來是插入的實現,首先是push_back,這個比較簡單,找到尾部進行插入即可。
void push_back(char n) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); //開空間復用reserve } _str[_size++] = n; _str[_size] = '\0'; }
接下來是insert,這個較push_back而言要麻煩一些,因為除了尾插,其他地方去插入數據你都需要挪動后面數據的位置。
string &insert(size_t pos, const char *str) { //檢查空間是否足夠 assert(pos <= _size); size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } //挪動后面的數據 size_t end = _size + len; while (end != pos + len - 1) { _str[end] = _str[end - len]; --end; } //數據插入 strncpy(_str + pos, str, len); _size += len; return *this; }
寫完了插入,接下來當然就是刪除接口:eraser
string &eraser(size_t pos, size_t len = npos) //npos為靜態變量,值為-1 { assert(pos < _size); if (len == npos || pos + len >= _size) //將位置后的元素全部刪除 { _str[pos] = '\0'; _size = pos; } else //刪除位置后的部分元素 { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size = _size - len; } return *this; }
迭代器的實現
C++中的迭代器和指針類似。為什么要有迭代器呢?因為C++中有各種各樣的容器,每個容器它背后的存儲方式不同,訪問方式也不同,為了讓使用者的使用成本降低,使大部分容器可以以相同的方式去訪問,就有了迭代器的產生。
接下來我們來實現string的迭代器,其實string的迭代器就是一個指針。并不用去封裝特別的東西。
typedef char *iterator; typedef const char *const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } iterator begin() { return _str; } iterator end() { return _str + _size; }
部分函數優化和完善
前面在寫運算符重載時,還有部分運算符未重載在此加上
string &operator+=(const char *str) { append(str); } string &operator+=(char n) { push_back(n); return *this; }
同時增加拷貝構造和operator=的現代寫法,之前我們寫拷貝構造和operator=時都需要自己去重新開空間,那么這個活可不可以讓其他人幫我做呢?
我們來看看下面這一段代碼
void swap(string& str) { std::swap(_str, str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); } string(const string &s) : _str(nullptr), _size(0), _capacity(0) { string tmp(s._str); swap(tmp); } string &operator=(string s) { swap(s); return *this; }
上述代碼同樣可以幫我們完成拷貝構造和operator= ,原理如下:
1.首先是拷貝構造,我們在拷貝構造中使用構造函數去創建一個臨時對象,這個臨時對象在創建時,就幫我們開辟了空間。然后我們將臨時對象和此對象的所有成員進行一個交換,這樣此對象就可以接管臨時對象創建的那塊空間,我們的拷貝構造也就成功了
2.在operator=這,我們使用的是傳值傳參。好處在于由于我們的string類是自定義對象,所以在傳參時會去調用拷貝構造,這樣傳過來的str參數也擁有了自己的空間,此時我們和拷貝構造一樣,將str所開辟的那塊空間接管,同時由于str是函數參數,當函數結束時,str會去調用析構函數進行一個空間釋放。
class string { public: typedef char *iterator; typedef const char *const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } iterator begin() { return _str; } iterator end() { return _str + _size; } string(const char *s = "") : _size(strlen(s)), _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, s); } string(const string &s) : _str(nullptr), _size(0), _capacity(0) { string tmp(s._str); swap(tmp); } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } string &operator=(string s) { swap(s); return *this; } char &operator[](size_t pos) { assert(pos < _size); return *(_str + pos); } const char &operator[](size_t pos) const { assert(pos < _size); return *(_str + pos); } const char *c_str() const { return _str; } void reserve(size_t n) { if (n > _capacity) { char *tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void push_back(char n) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = n; _str[_size] = '\0'; } string &operator+=(char n) { push_back(n); return *this; } void append(const char *str) { size_t len = _size + strlen(str); if (len > _capacity) { reserve(len); } strcpy(_str + _size, str); _size = len; } string &operator+=(const char *str) { append(str); } void resize(size_t n, char c = '\0') { if (n > _capacity) { reserve(n); } for (size_t i = _size; i < n; ++i) { _str[i] = c; } _size = n; _str[_size] = '\0'; } size_t size() const { return _size; } size_t capacity() const { return _capacity; } bool empty() { return 0 == _size; } bool operator>(const string &s) { return strcmp(_str, s.c_str()); } bool operator==(const string &s) { return strcmp(_str, s.c_str()) == 0; } bool operator!=(const string &s) { return !(*this == s); } bool operator>=(const string &s) { return *this > s || *this == s; } bool operator<(const string &s) { return !(*this >= s); } bool operator<=(const string &s) { return !(*this > s); } string &insert(size_t pos, const char *str) { assert(pos <= _size); size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } size_t end = _size + len; while (end != pos + len - 1) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len); _size += len; return *this; } string &eraser(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size = _size - len; } return *this; } void clear() { _size = 0; _str[0] = '\0'; _capacity = 0; } void swap(string &s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } size_t find(char c, size_t pos = 0) const { while (pos < _size) { if (_str[pos] == c) { return pos; } ++pos; } return npos; } size_t find(char *s, size_t pos = 0) const { const char *p = strstr(_str + pos, s); if (p == nullptr) { return npos; } else { return p - _str; } } private: char *_str; size_t _size; size_t _capacity; const static size_t npos; }; const size_t string::npos = -1;
關于“C++怎么實現String類”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“C++怎么實現String類”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。