C++中String的語法及常用接口的底層實現(xiàn)詳解
一、string類
在學習 string 前,我們不妨先來了解一下 string 類到底是什么,有什么用呢?下圖是C++標準庫中的對 string 內容:
what???沒錯,C++標準庫都是英語解釋。我們也應該試著去適應,不懂的可以查閱。當然,在這里我就直接給出翻譯,主要是以下內容:
字符串是表示字符序列的類;
- 標準的字符串類提供了對此類對象的支持,其接口類似于標準字符容器的接口,但添加了專門用于操作單字節(jié)字符字符串的設計特性。
- string類是使用char(即作為它的字符類型,使用它的默認char_traits和分配器類型(關于模板的更多信息,請參閱basic_string)。
- string類是basic_string模板類的一個實例,它使用char來實例化basic_string模板類,并用char_traits和allocator作為basic_string的默認參數(shù)(根于更多的模板信息請參考basic_string)。
- 注意,這個類獨立于所使用的編碼來處理字節(jié):如果用來處理多字節(jié)或變長字符(如UTF-8)的序列,這個類的所有成員(如長度或大小)以及它的迭代器,將仍然按照字節(jié)(而不是實際編碼的字符)來操作。
了解到上面的內容后,我們要開始真正的學習 string 的用法了。
二、string的常用見用法
2、1 string對象的構造
2、1、1 string對象的構造的使用方法
最為常用的無非就是我們用串string來構造一個對象,也就是存儲一個字符,常用的方法有如下幾點:
- string()——構造空的 string 類對象,即空字符串;
- string(const char* s)——用 char* 來構造 string 類對象;
- string(size_t n, char c)——string類對象中包含n個字符c;
- string(const string&s)——拷貝構造函數(shù)。
下面是使用方法所對應的實例,幫助更好的理解其用法。
根據(jù)上面的實例和對應的輸出結果,我們可以更好的理解。
2、1、2 string()的底層實現(xiàn)
構造空串,其有效字符長度為0,但是實際上是開辟了一個字節(jié)的空間存儲 ‘\0’ 的。具體如下:
string() :_str(new char[1]) , _size(0) ,_capacity(0) { }
2、1、3 string(const char* s)的底層實現(xiàn)
我們這里就給出以上兩個底層的構造實現(xiàn),其余兩個類似,就不再給出。具體實現(xiàn)如下:
string(const char* str = "") //默認空串。注意:空串是以 \0 結尾 { _size = strlen(str); _capacity = _size; _str = new char[_size + 1]; strcpy(_str, str); }
2、2 string對象的修改操作
當我們構建好對象后,我們接下來就要往對應的字符串對象進行修改操作了。常用的修改操作無非就是插入和查找,具體有如下幾種常見用法:
- push_back——在字符串后尾插字符c;
- insert——在pos位置插入n個字符或者插入一個字符串;
- append ——在字符串后追加一個字符串 ;
- operator+=——在字符串后追加字符或者字符串str; c_str——返回C格式字符串;
- find——從字符串pos位置開始往后找字符c,返回該字符在字符串中的位置。
我們再看其具體的使用方法實例,如下圖:
上圖為常用的修改用法。當然,其他的用法還有很多。如果想了解的過可去C++官網(wǎng)(cppreference)或者 cplusplus 的標準庫中查詢。具體也可看下圖:
insert
operator+=
find
2、3 string對象的容量操作
在平常對字符串的操作中,我們也經(jīng)常需要去了解到字符串的實際長度為多少,或者改數(shù)組到底能夠存下多長的字符串,又或是修改字符串的長度和空間大小。C++的string類中,這些操作都提供了相應的接口,具體如下:
- size——返回字符串有效字符長度;
- length——返回字符串有效字符長度 ;
- empty——檢測字符串釋放為空串,是返回true,否則返回false;
- reserve——為字符串預留空間;
- resize——將有效字符的個數(shù)該成n個,多出的空間用字符c填充;
- capacity——返回空間總大小;
- clear——清空有效字符;
我們發(fā)現(xiàn)。size和length的功能一樣的。確實都是求字符串的有效長度。那我們接著看其具體的實例:
注意:capacity返回的是空間的總大小,size和length返回的是字符串的實際有效長度。 兩者是有所區(qū)別的。一個字符串的capacity是有底層的具體實現(xiàn)決定的,不同的編譯器可能實現(xiàn)的是不同的。
2、4 string對象的訪問和遍歷操作
string對象的訪問,支持像數(shù)組一樣使用 [] 進行訪問,也可通過迭代器進行訪問,具體有如下用法:
- operator[]——返回pos位置的字符,const string類對象調用;
- begin+ end——begin獲取一個字符的迭代器 + end獲取最后一個字符下一個位置的迭 代器;
- 范圍for——C++11支持更簡潔的范圍for的新遍歷方式。
其具體的用法實例如下:
范圍for的底層實現(xiàn)就是用迭代器來實現(xiàn)的。寫起來更加的便捷和方便。
以上為string中較為常用的接口。以上完全足夠我們平常的使用了,如果想要了解的更多,可參考C++的標準庫。不過string容器一共實現(xiàn)了106個接口?。。∑渲写蟛糠侄际侨哂嗟?。這也是很多人吐槽string類實現(xiàn)的過于復雜和冗余的一個重要原因。所以在查看時,我們只需要看自己想要了解的接口即可。
三、string常用結構的底層實現(xiàn)
3、1 初建結構
我們通過上述的構造,不難發(fā)現(xiàn)也不難理解string的底層其實就是一個字符指針,該指針指向一個數(shù)組。當然,我們還需要兩個變量來維護其有效長度(_size)和數(shù)組容量(_capacity).
其次,我們自己實現(xiàn)的string類為了區(qū)分std命名空間,我們可自己設置一個命名空間。處型的模擬實現(xiàn)如下:
namespace gtm { class string { public: //string() // :_str(new char[1]) // , _size(0) // ,_capacity(0) //{ //} //string(const char* str) // :_str(new char[strlen(str) + 1]) //三次strlen函數(shù),效率低。 // ,_size(strlen(str)) // ,_capacity(strlen(str)) //{ // strcpy(_str, str); //} // 不再使用strlen函數(shù),初始化列表與變量聲明順序固定 string(const char* str = "") //默認空串。注意:空串是以 \0 結尾 { _size = strlen(str); _capacity = _size; _str = new char[_size + 1]; strcpy(_str, str); } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } private: char* _str; size_t _size; size_t _capacity; };
注意,我們上述省略了無參的構造。原因是我們在字符串的構造中有缺省參數(shù),即為空串。
3、2 返回大小和容量
這兩個部分,是比較容易實現(xiàn)的兩部分。同時也是較為常用的兩部分。具體如下:
size_t size() const { return _size; } size_t capacity() const { return _capacity; }
3、3 拷貝構造和賦值重載
這兩部分較為復雜的兩部分。其中均需要深拷貝去實現(xiàn)完成,而淺拷貝是不可以的。注意:拷貝構造使用一個已定義變量去初始化另一個變量,賦值重載是兩個已定義變量進行賦值。
具體實現(xiàn)如下:
//深拷貝 //string(const string& s) // :_str(new char[s._capacity+1]) // ,_size(s._size) // ,_capacity(s._capacity) //{ // strcpy(_str, s._str); //} void swap(string& tmp) { //調用全局的swap ::swap(_str, tmp._str); ::swap(_size, tmp._size); ::swap(_capacity, tmp._capacity); } //借助變量tmp string(const string& s) :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp); } //賦值 //string& operator=(const string& s) //{ // if(this == &s) // { // return *this; // } // //先開空間拷貝數(shù)據(jù),以防new失敗銷毀原來的空間 // char* tmp = new char[s._capacity + 1]; // strcpy(tmp, s._str); // delete[] _str; // _str = tmp; // _size = s._size; // _capacity = s._capacity; // return *this; // //delete[] _str; // //_str = new char[s._capacity + 1]; // //strcpy(_str, s._str); // //_size = s._size; // //_capacity = s._capacity; // return *this; //} //string& operator=(const string& s) //{ // if(this == &s) // { // return *this; // } // string tmp(s._str); // swap(tmp); // return *this; //} string& operator=(string s) { if (this == &s) { return *this; } swap(s); return *this; }
上述的輔助重載我們巧妙地借助了臨時變量s。當賦值完成后,出了作用域s會自動調用戲后進行銷毀,這里是需要反復理解的。
3、4 擴容(reserve)
我們可簡單的理解reserve為擴容(擴容的前提為要求的容量比原來的大),但是我們要記得把字符數(shù)組中原有的內容拷貝過來,并且釋放之前所動態(tài)開辟的空間。 具體實現(xiàn)如下:
void reserve(size_t capacity) { if (capacity > _capacity) { char* tmp = new char[capacity + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = capacity; } }
3、5 插入(push_back、append、operator+=、insert)
插入的實現(xiàn),主要的點就是是否要進行擴容。其次,當我們實現(xiàn)push_back和append后,其他的均可復用這兩個結構進行實現(xiàn)。具體實現(xiàn)如下:
void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; _size++; _str[_size] = '\0'; } void append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size >= _capacity * 2 ? len + _size : _capacity * 2); } strcpy(_str + _size, str); _size += len; } void append(const string& s) { append(s._str); } void append(int n, char ch) { reserve(_size + n); for (int i = 0; i < n; i++) { push_back(ch); } } string& operator+= (char ch) { push_back(ch); return *this; } string& operator+= (const char* str) { append(str); return *this; } string& insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //注意,當運算數(shù)一個是有符號,另一個是無符號時,有符號的運算數(shù)會強制類型轉換為無符號數(shù)。pos等于0的位置插入,end--后為超大數(shù)據(jù),會出錯。 //int end = _size; //while (end >= (int)pos) //{ // _str[end + 1] = _str[end]; // end--; //} size_t end = _size+1; while (end > pos) { _str[end] = _str[end - 1]; end--; } _str[pos] = ch; _size++; return *this; } string& insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size >= _capacity * 2 ? len + _size : _capacity * 2); } size_t end = _size + len; while (end >= pos+len) { _str[end] = _str[end - len]; end--; } for (int i = pos,j=0; j < len;j++, i++) { _str[i] = str[j]; } _size += len; return *this; }
3、6 刪除(erase)
我們這里實現(xiàn)的從某個位置開始刪除,刪除長度為 len 的字符。len有一個缺省參數(shù),為npos(npos是一個很大的數(shù),也就是不傳參給 len 的話,默認刪除到最后)。如果 len 本就很大,刪除的長度超過從pos開始所剩余的長度,那么默認也是pos后的刪除完。那么我們看其具體的實現(xiàn)。
void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || _size - pos <= len) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } }
3、7 查找(find)
查找的話,主要常用的就兩個:從pos位置開始查找,查找的內容可能是一個字符,也可能是一個子串。如果找到,則返回其下標。沒找到就返回npos。具體實現(xiàn)如下:
size_t find(char ch, size_t pos = 0)const { for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) return i; } return npos; } size_t find(const char* sub, size_t pos = 0)const { const char* ret=strstr(_str + pos, sub); if (ret == nullptr) { return npos; } else { return ret - _str; } }
3、8 返回子串(substr)
返子串也是我們經(jīng)常需要的一個接口。返回子串就一個接口,從某個位置開始查找,查找長度為 len 的字符。len有一個缺省參數(shù),為npos(npos是一個很大的數(shù),也就是不傳參給 len 的話,默認返回到最后)。如果 len 本就很大,返回子串的長度超過從pos開始所剩余的長度,那么默認也是pos后的子串全部返回。我們看其具體實現(xiàn):
string substr(size_t pos, size_t len = npos)const { assert(pos < _size); size_t realLen = len; if (len == npos || pos + len > _size) { realLen = _size - pos; } string s; for (size_t i = 0; i < realLen; i++) { s += _str[pos + i]; } return s; }
3、9 迭代器(iterator)
在string中的迭代器底層就是指針,但是并不是所有的迭代器底層實現(xiàn)都是指針!我們直接看起底層實現(xiàn):
typedef char* iterator; typedef const char* const_iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; }
這里的begin()就是返回的字符串的首元素地址,end()返回的是字符串最后一個元素的后一個地址!
3、10 比較(>、<、>=、<=、==、!=)
字符串的比較并非比較其長度,而是與其相同位置字符的大小有關,也就是我們所說的字典序。我們這里只需要實現(xiàn)其中的兩個,其他均可復用。具體如下:
bool operator> (const string& s) const { return strcmp(_str, s._str) > 0; } bool operator== (const string& s) const { return strcmp(_str, s._str) == 0; } bool operator>= (const string& s) const { return *this > s || *this == s; } bool operator< (const string& s) const { return !(*this >= s); } bool operator<= (const string& s) const { return !(*this > s); } bool operator!= (const string& s) const { return !(*this == s); }
四、總結
string 在C++中算是比較重要的了,也是入門時必須所學的容器。在平常中使用的頻率較高,所以我們不僅要掌握其簡單的用法,更應該去了解其底層的實現(xiàn)。這有助于我們后續(xù)的使用和理解。本篇文章列舉出了string中常用的語法和接口底層的底層實現(xiàn),這些都是我們應該熟練掌握的內容。
本篇文章講解就到這里,感謝觀看ovo~
以上就是C++中String的語法及常用接口的底層實現(xiàn)詳解的詳細內容,更多關于C++ String的語法及接口底層實現(xiàn)的資料請關注腳本之家其它相關文章!
相關文章
利用Matlab實現(xiàn)繪制中秋山間秋月和皓月當空效果
中秋節(jié)還有三天就到了,中秋節(jié)啊,闔家團圓的日子。本文將利用Matlab繪制中秋山間秋月和皓月當空的動態(tài)效果,感興趣的可以了解一下2022-09-09C++示例分析內聯(lián)函數(shù)與引用變量及函數(shù)重載的使用
為了消除函數(shù)調用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數(shù)調用處用函數(shù)體替換,類似于C語言中的宏展開。這種在函數(shù)調用處直接嵌入函數(shù)體的函數(shù)稱為內聯(lián)函數(shù)(Inline Function),又稱內嵌函數(shù)或者內置函數(shù)2022-08-08