C++?STL-string類底層實(shí)現(xiàn)過(guò)程
實(shí)現(xiàn)框架
#include<iostream> #include<assert.h> using namespace std; namespace lzg { class string { public: //typedef char* iterator; using iterator = char*; using const_iterator = const char*; //一、默認(rèn)成員函數(shù) string(const char* str = ""); //默認(rèn)構(gòu)造 string(const string& s); //拷貝構(gòu)造 string& operator=(const string& s); //賦值重載 ~string(); //析構(gòu)函數(shù) //二、迭代器相關(guān)函數(shù) iterator begin() { return _str; } iterator end() { return _str + _size; } const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } //三、容量和大小相關(guān)函數(shù) void reserve(size_t n); size_t size() const { return _size; } size_t capacity() const { return _capacity; } void resize(size_t n, char ch = '\0'); //四、與修改字符串相關(guān)的函數(shù) void push_back(char ch); void append(const char* str); string& operator+=(char ch); string& operator+=(const char* str); void insert(size_t pos, char ch); void insert(size_t pos, const char* str); void erase(size_t pos, size_t len = npos); void clear() { _str[0] = '\0'; _size = 0; } //五、訪問(wèn)字符串相關(guān)函數(shù) char& operator[](size_t i) { assert(i < _size); return _str[i]; } const char& operator[](size_t i) const { assert(i < _size); return _str[i]; } const char* c_str() const { return _str; } size_t find(char ch, size_t pos = 0); size_t find(const char* str, size_t pos = 0); string substr(size_t pos, size_t len = npos); private: char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; public: static const size_t npos; }; //六、關(guān)系運(yùn)算符重載函數(shù) bool operator== (const string& lhs, const string& rhs); bool operator!= (const string& lhs, const string& rhs); bool operator> (const string& lhs, const string& rhs); bool operator< (const string& lhs, const string& rhs); bool operator>= (const string& lhs, const string& rhs); bool operator<= (const string& lhs, const string& rhs); ostream& operator<<(ostream& os, const string& str); istream& operator>>(istream& is, string& str); }
一、默認(rèn)成員函數(shù)
1.默認(rèn)構(gòu)造函數(shù)
string() :_str(new char[1] {'\0'}), _size(0), _capacity(0) {}
為_str開辟1字節(jié)空間來(lái)存放'\0'_capacity不記錄\0的大小,這樣調(diào)用c_str()就不會(huì)報(bào)錯(cuò)
2.構(gòu)造函數(shù)
string(const char* str) :_size(strlen(str)) { _capacity = _size; _str = new char[_size + 1]; strcpy(_str, str); }
注意在private中的聲明順序,這里初始化列表只走_(dá)size(大小為str的長(zhǎng)度),讓_str和_capacity在函數(shù)體內(nèi)初始化(這樣保證了不會(huì)出錯(cuò),比如三者都在初始化列表的話,會(huì)先初始化_str這樣有風(fēng)險(xiǎn)),同樣的,_str開辟空間時(shí),為'\0'多開辟1字節(jié)
構(gòu)造函數(shù)有一個(gè)默認(rèn)構(gòu)造就行了,寫1個(gè)就行,把1注釋掉,并把2改為全缺省
string(const char* str="")//不要寫成" "這是非空 :_size(strlen(str)) { _capacity = _size; _str = new char[_size + 1]; strcpy(_str, str); }
3.拷貝構(gòu)造函數(shù)(重點(diǎn))
在模擬實(shí)現(xiàn)拷貝構(gòu)造函數(shù)前,我們應(yīng)該首先了解深淺拷貝:
我們不寫拷貝構(gòu)造,編譯器默認(rèn)生成的拷貝構(gòu)造是值拷貝也叫淺拷貝
- 淺拷貝:拷貝出來(lái)的目標(biāo)對(duì)象的指針和源對(duì)象的指針指向的內(nèi)存空間是同一塊空間。其中一個(gè)對(duì)象的改動(dòng)會(huì)對(duì)另一個(gè)對(duì)象造成影響。
- 深拷貝:深拷貝是指源對(duì)象與拷貝對(duì)象互相獨(dú)立。其中任何一個(gè)對(duì)象的改動(dòng)不會(huì)對(duì)另外一個(gè)對(duì)象造成影響。
很明顯,我們并不希望拷貝出來(lái)的兩個(gè)對(duì)象之間存在相互影響,因此,我們這里需要用到深拷貝。
//s2(s1) s1 string(const string& s) { _str = new char[s._capacity + 1]; strcpy(_str, s._str); _capacity = s._capacity; _size = s._size; }
這里還有一個(gè)現(xiàn)代(偷懶)寫法
string(const string& s) { string tmp(s._str); swap(tmp); }
現(xiàn)代寫法與傳統(tǒng)寫法的思想不同:先構(gòu)造一個(gè)tmp對(duì)象,然后再將tmp對(duì)象與拷貝(s)對(duì)象的數(shù)據(jù)交換即可。
4.賦值運(yùn)算符重載函數(shù)
與拷貝構(gòu)造函數(shù)類似,賦值運(yùn)算符重載函數(shù)的模擬實(shí)現(xiàn)也涉及深淺拷貝問(wèn)題,我們同樣需要采用深拷貝
再?gòu)?qiáng)調(diào)一下賦值和拷貝的區(qū)別,賦值是兩個(gè)已創(chuàng)建的對(duì)象之間完成賦值操作,拷貝是指一個(gè)已創(chuàng)建的對(duì)象調(diào)用拷貝構(gòu)造生成一個(gè)新的對(duì)象
// s1 = s3 s3 string& operator=(const string& s) { if (this != &s)//避免自己給自己賦值 { delete[] _str; //釋放掉s1的舊空間,開辟一個(gè)和s3容量的新空間 _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } return *this; }
同樣的賦值也有現(xiàn)代寫法
// 現(xiàn)代寫法 // s1 = s3 string& operator=(string s) { swap(s); return *this; }
賦值運(yùn)算符重載函數(shù)的現(xiàn)代寫法是通過(guò)采用“值傳遞”接收右值的方法,讓編譯器自動(dòng)調(diào)用拷貝構(gòu)造函數(shù),然后我們?cè)賹⒖截惓鰜?lái)的對(duì)象與左值進(jìn)行交換即可
5.析構(gòu)函數(shù)
~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
二、迭代器相關(guān)函數(shù)
string類中的迭代器實(shí)際上就是字符指針,只是給字符指針起了一個(gè)別名叫iterator而已。
typedef char* iterator; typedef const char* const_iterator;
begin和end
iterator begin() { return _str; //返回字符串中第一個(gè)字符的地址 } const_iterator begin()const { return _str; //返回字符串中第一個(gè)字符的const地址 } iterator end() { return _str + _size; //返回字符串中最后一個(gè)字符的后一個(gè)字符的地址 } const_iterator end()const { return _str + _size; //返回字符串中最后一個(gè)字符的后一個(gè)字符的const地址 }
其實(shí)范圍for的底層就是調(diào)用了begin()和end()迭代器,當(dāng)你模擬實(shí)現(xiàn)了迭代器,范圍for自然就能使用
string s("hello world!!!"); //編譯器將其替換為迭代器形式 for (auto e : s) { cout << e << " "; } cout << endl;
注:自己寫的begin()和end()函數(shù)名必須是這樣的,不能有任何大小寫否則范圍for就報(bào)錯(cuò)
三、容量和大小相關(guān)函數(shù)
size和capacity
因?yàn)閟tring類的成員變量是私有的,我們并不能直接對(duì)其進(jìn)行訪問(wèn),所以string類設(shè)置了size和capacity這兩個(gè)成員函數(shù),用于獲取string對(duì)象的大小和容量。
size函數(shù)用于獲取字符串當(dāng)前的有效長(zhǎng)度(不包括’\0’)。
size_t size() const { return _size; }
capacity函數(shù)用于獲取字符串當(dāng)前的容量。(不包括’\0’)。
size_t capacity() const { return _capacity; }
reserve和resize
reserve和resize這兩個(gè)函數(shù)的執(zhí)行規(guī)則一定要區(qū)分清楚。
reserve規(guī)則:
- 1、當(dāng)n大于對(duì)象當(dāng)前的capacity時(shí),將capacity擴(kuò)大到n或大于n。
- 2、當(dāng)n小于對(duì)象當(dāng)前的capacity時(shí),什么也不做。
void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1];//多開一個(gè)空間用于存放'\0' strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
resize規(guī)則:
- 1、當(dāng)n大于當(dāng)前的size時(shí),將size擴(kuò)大到n,擴(kuò)大的字符為ch,若ch未給出,則默認(rèn)為’\0’。
- 2、當(dāng)n小于當(dāng)前的size時(shí),將size縮小到n。
//改變大小 void resize(size_t n, char ch = '\0') { if (n <= _size) { _size = n; _str[_size] = '\0'; } else { if (n > _capacity) { reserve(n); } for (size_t i = _size; i < n; i++) { _str[i] = ch; } _size = n; _str[_size] = '\0'; } }
四、與修改字符串相關(guān)的函數(shù)
push_back
void push_back(char c) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } _str[_size] = c; // 在結(jié)尾位置寫入新字符(覆蓋原'\0') _size++; // 增加長(zhǎng)度 _str[_size] = '\0'; // 在新結(jié)尾補(bǔ)終止符 }
實(shí)現(xiàn)push_back還可以直接復(fù)用下面即將實(shí)現(xiàn)的insert函數(shù)。
//尾插字符 void push_back(char ch) { insert(_size, ch); //在字符串末尾插入字符ch }
append
//尾插字符串 void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { size_t newCapacity = 2 * _capacity; // 擴(kuò)2倍不夠,則需要多少擴(kuò)多少 if (newCapacity < _size + len) newCapacity = _size + len; reserve(newCapacity); }//避免尾插字符串時(shí)如果字符串大于二倍_capacity時(shí)的多次擴(kuò)容 strcpy(_str + _size, str); _size += len; }
operator+=
- +=運(yùn)算符的重載是為了實(shí)現(xiàn)字符串與字符、字符串與字符串之間能夠直接使用+=運(yùn)算符進(jìn)行尾插。
- +=運(yùn)算符實(shí)現(xiàn)字符串與字符之間的尾插直接調(diào)用push_back函數(shù)即可。
//+=運(yùn)算符重載 string& operator+=(char ch) { push_back(ch); //尾插字符串 return *this; //返回左值(支持連續(xù)+=) }
+=運(yùn)算符實(shí)現(xiàn)字符串與字符串之間的尾插直接調(diào)用append函數(shù)即可。
//+=運(yùn)算符重載 string& operator+=(const char* str) { append(str); //尾插字符串 return *this; //返回左值(支持連續(xù)+=) }
insert
insert函數(shù)的作用是在字符串的任意位置插入字符或是字符串。
void insert(size_t pos, char ch); void intsert(size_t pos,const char* str);
void intsert(size_t pos, char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } size_t end = _size; while (end >=(int)pos) { _str[end + 1] = _str[end]; end--; } _str[pos] = ch; _size++; }
insert函數(shù)插入字符串時(shí)也是類似思路
void intsert(size_t pos, const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { size_t newCapacity = 2 * _capacity; // 擴(kuò)2倍不夠,則需要多少擴(kuò)多少 if (newCapacity < _size + len) newCapacity = _size + len; reserve(newCapacity); } size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } for (size_t i = 0; i < len; i++) { _str[pos + i] = str[i]; } _size += len; }
erase
erase函數(shù)的作用是刪除字符串任意位置開始的n個(gè)字符。
void erase(size_t pos, size_t len) { assert(pos < _size); if (len >= _size - pos) { _str[pos] = '\0'; _size = pos; } else { //從后往前挪 size_t end = pos + len; while (end <= _size) { _str[end-len] = _str[end];//覆蓋pos及后len長(zhǎng)度的值,完成刪除 end++; } _size -= len; } }
clear
clear函數(shù)用于將對(duì)象中存儲(chǔ)的字符串置空,實(shí)現(xiàn)時(shí)直接將對(duì)象的_size置空,然后在字符串后面放上’\0’即可
//清空字符串 void clear() { _size = 0; //size置空 _str[_size] = '\0'; //字符串后面放上'\0' }
swap
有三個(gè)swap,有兩個(gè)是string里的swap函數(shù),另外一個(gè)是算法庫(kù)的swap函數(shù)
std::swap template <class T> void swap (T& a, T& b); std::string::swap void swap (string& str); void swap (string& x,sring& y);
算法庫(kù)中的swap也能交換自定義類型,但代價(jià)非常大
我們自己實(shí)現(xiàn)swap完全不用這么復(fù)雜,直接把s1、s2指針及數(shù)據(jù)交換一下就行了
//交換兩個(gè)對(duì)象的數(shù)據(jù) void swap(string& s) { //調(diào)用庫(kù)里的swap ::swap(_str, s._str); //交換兩個(gè)對(duì)象的C字符串 ::swap(_size, s._size); //交換兩個(gè)對(duì)象的大小 ::swap(_capacity, s._capacity); //交換兩個(gè)對(duì)象的容量 }
還有一個(gè)swap存在的意義是什么呢?
void swap(string& s1,string& s2) { s1.swap(s2); }
這樣在類外面調(diào)用swap函數(shù)時(shí)就不會(huì)調(diào)到算法庫(kù)的swap函數(shù)了(模板實(shí)例化會(huì)走上面的swap函數(shù))
五、訪問(wèn)字符串相關(guān)函數(shù)
operator[ ]
[ ]運(yùn)算符的重載是為了讓string對(duì)象能像C字符串一樣,通過(guò)[ ] +下標(biāo)的方式獲取字符串對(duì)應(yīng)位置的字符。
//[]運(yùn)算符重載(可讀可寫) char& operator[](size_t i) { assert(i < _size); //檢測(cè)下標(biāo)的合法性 return _str[i]; //返回對(duì)應(yīng)字符 }
在某些場(chǎng)景下,我們可能只能用[ ] +下標(biāo)的方式讀取字符而不能對(duì)其進(jìn)行修改。
例如,對(duì)一個(gè)const的string類對(duì)象進(jìn)行[ ] +下標(biāo)的操作,我們只能讀取所得到的字符,而不能對(duì)其進(jìn)行修改。所以我們需要再重載一個(gè)[ ] 運(yùn)算符,用于只讀操作。
//[]運(yùn)算符重載(可讀可寫) const char& operator[](size_t i)const { assert(i < _size); //檢測(cè)下標(biāo)的合法性 return _str[i]; //返回對(duì)應(yīng)字符 }
c_str
c_str函數(shù)用于獲取對(duì)象C類型的字符串
//返回C類型的字符串 const char* c_str()const { return _str; }
小知識(shí)點(diǎn)- npos
在 C++ 標(biāo)準(zhǔn)庫(kù)中,?npos
? 是一個(gè)特殊的靜態(tài)常量成員,主要用于表示“無(wú)效位置”或“未找到”的狀態(tài)
注:只有整形才能在聲明中定義
static const size_t npos=-1;
?核心用途
(1) ?查找函數(shù)失敗時(shí)的返回值
std::string str = "Hello"; size_t pos = str.find('x'); // 查找不存在字符 if (pos == std::string::npos) { // 必須用 npos 檢查 std::cout << "Not found!"; }
(2) ?表示“直到字符串末尾”?
std::string sub = str.substr(2, std::string::npos); // 從索引2到末尾
特性
- ?無(wú)符號(hào)值?:由于是
size_t
類型,避免直接與-1
比較,而應(yīng)使用npos
。• - ?足夠大?:其值(如 18446744073709551615)保證不會(huì)與任何有效索引沖突。
find函數(shù)
- find函數(shù)是用于在字符串中查找一個(gè)字符或是字符串
- find函數(shù):正向查找即從字符串開頭開始向后查找
1、正向查找第一個(gè)匹配的字符。
size_t find(char ch,size_t pos=0) { //默認(rèn)從首字符開始查找 assert(pos<_size); for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } //找不到返回npos return npos; }
2、正向查找第一個(gè)匹配的字符串。
這里用到了一個(gè)strstr函數(shù)
char * strstr (char * str1, const char * str2 );
返回指向 str1 中第一次出現(xiàn)的 str2 的指針,如果 str2 不是 str1 的一部分,則返回空指針。
size_t find(const char* str, size_t pos = 0) { assert(pos < _size); const char* ptr = strstr(_str, str); if (ptr)//如果找到了 { return ptr - _str; //下標(biāo) } else //為空 { return npos; } }
substr函數(shù)
substr函數(shù)用來(lái)取字符串中的子字符串,位置和長(zhǎng)度由自己決定
默認(rèn)從首字符取,默認(rèn)取的長(zhǎng)度是取到尾
string substr (size_t pos = 0, size_t len = npos) const;
調(diào)用拷貝構(gòu)造,返回pos位后len長(zhǎng)度的字符串
string substr(size_t pos, size_t len) { assert(pos < _size); //len長(zhǎng)度足夠大就直接取到尾 if (len > _size - pos) { len = _size - pos; } string sub; sub.reserve(len); for (size_t i = pos;i < len; i++) { sub += _str[pos + i]; } return sub; }
六、關(guān)系運(yùn)算符重載函數(shù)
>、>=、<、<=、==、!=
關(guān)系運(yùn)算符有 >、>=、<、<=、==、!= 這六個(gè),但是對(duì)于C++中任意一個(gè)類的關(guān)系運(yùn)算符重載,我們均只需重載其中的兩個(gè),剩下的四個(gè)關(guān)系運(yùn)算符可以通過(guò)復(fù)用已經(jīng)重載好了的兩個(gè)關(guān)系運(yùn)算符來(lái)實(shí)現(xiàn)。
例如,對(duì)于string類,我們可以選擇只重載 < 和 == 這兩個(gè)關(guān)系運(yùn)算符。
注:重載在類外面,命名域例:lzg里。這樣操作數(shù)就不用限定死是string類的
- ?
lhs
?:代表 ?Left-Hand ?Side (左側(cè)操作數(shù)/左側(cè)值) - ?
rhs
?:代表 ?Right-Hand ?Side (右側(cè)操作數(shù)/右側(cè)值)
//==運(yùn)算符重載 bool operator== (const string& lhs, const string& rhs) { return strcmp(lhs.c_str(), rhs.c_str()) == 0; } //<運(yùn)算符重載 bool operator< (const string& lhs, const string& rhs) { return strcmp(lhs.c_str(), rhs.c_str()) < 0; }
剩下的四個(gè)關(guān)系運(yùn)算符的重載,就可以通過(guò)復(fù)用這兩個(gè)已經(jīng)重載好了的關(guān)系運(yùn)算符來(lái)實(shí)現(xiàn)了。
//<=運(yùn)算符重載 bool operator<= (const string& lhs, const string& rhs) { return lhs < rhs || lhs == rhs; } //!=運(yùn)算符重載 bool operator!= (const string& lhs, const string& rhs) { return !(lhs == rhs); } //>運(yùn)算符重載 bool operator> (const string& lhs, const string& rhs) { return !(lhs <= rhs); } //>=運(yùn)算符重載 bool operator>= (const string& lhs, const string& rhs) { return !(lhs < rhs); }
>>和<<運(yùn)算符的重載
>>運(yùn)算符的重載
ostream& operator<<(ostream& os, const string& str) { for (size_t i = 0; i < str.size(); i++) { os << str[i]; } return os; }
<<運(yùn)算符的重載
istream& operator>>(istream& is, string& str) { str.clear(); char ch; //is >> ch; ch = is.get(); while (ch != ' ' && ch != '\n') { str += ch; ch = is.get(); } return is; }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)實(shí)戰(zhàn)教學(xué)
在本篇文章里小編給大家分享了關(guān)于C語(yǔ)言實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)實(shí)戰(zhàn)教學(xué)內(nèi)容,有興趣的朋友們可以跟著學(xué)習(xí)參考下。2019-01-01C語(yǔ)言實(shí)現(xiàn)旅游資訊管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)旅游資訊管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03C++中的動(dòng)態(tài)分派在HotSpot?VM中的應(yīng)用小結(jié)
多態(tài)是面向?qū)ο缶幊陶Z(yǔ)言的重要特性,它允許基類的指針或引用指向派生類的對(duì)象,而在具體訪問(wèn)時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定,這篇文章主要介紹了C++的動(dòng)態(tài)分派在HotSpot?VM中的重要應(yīng)用,需要的朋友可以參考下2023-09-09C++文件關(guān)鍵詞快速定位出現(xiàn)的行號(hào)實(shí)現(xiàn)高效搜索
這篇文章主要為大家介紹了C++文件關(guān)鍵詞快速定位出現(xiàn)的行號(hào)實(shí)現(xiàn)高效搜索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10MFC實(shí)現(xiàn)簡(jiǎn)單計(jì)算器
這篇文章主要為大家詳細(xì)介紹了MFC實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05C++中如何實(shí)現(xiàn)回調(diào)的方法示例
這篇文章主要給大家介紹了關(guān)于C++中如何實(shí)現(xiàn)回調(diào)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用c++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10QT使用共享內(nèi)存實(shí)現(xiàn)進(jìn)程間通訊
這篇文章主要為大家詳細(xì)介紹了QT如何使用共享內(nèi)存實(shí)現(xiàn)進(jìn)程間通訊,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12