C++?STL之string的模擬實(shí)現(xiàn)實(shí)例代碼
前言
上一章我們對(duì)string的常見接口及使用進(jìn)行了講解,接下來我們將對(duì)一些常見的接口,包括構(gòu)造函數(shù),析構(gòu)函數(shù),運(yùn)算符重載等等進(jìn)行模擬實(shí)現(xiàn).方便我們理解string接口實(shí)現(xiàn)的原理.
在講解之前先說一下string的成員變量.
首先是字符串內(nèi)容_str,再是字符串的大小_size,最后是字符串的總?cè)萘看笮?strong>_capacity.
class string { private: char* _str; size_t _size; size_t _capacity; };
構(gòu)造函數(shù)
缺省值是一個(gè)空串,再給_str開辟空間時(shí)要多開辟一個(gè)空間存儲(chǔ)'\0'
開好了空間最后需要把內(nèi)容拷貝到_str.
string(const char* str = "") { //這里有個(gè)細(xì)節(jié),就是先計(jì)算出_size大小,然后再直接把_size賦值給_capacity,省了一次strlen()的調(diào)用. _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); }
析構(gòu)函數(shù)
完成對(duì)string類成員的資源清理,空間釋放等一些操作.
~string() { //釋放_(tái)str的空間,并將其指向的空間置為空 delete[] _str; _str = nullptr; _size = _capacity = 0; }
拷貝構(gòu)造函數(shù)
說到string的拷貝構(gòu)造函數(shù),這里一定會(huì)涉及到深淺拷貝問題.
所以在講解它的拷貝構(gòu)造函數(shù)之前必須先了解它
淺拷貝:也稱位拷貝,編譯器只是將對(duì)象中的值拷貝過來。如果對(duì)象中管理資源,可能就會(huì)導(dǎo)致多個(gè)對(duì)象共享同一份資源,當(dāng)一個(gè)對(duì)象銷毀時(shí)就會(huì)將該資源釋放掉,但是其他的對(duì)象不知道該資源已經(jīng)被釋放了,以為資源還有效,所以他們會(huì)繼續(xù)對(duì)這個(gè)資源進(jìn)行訪問。這時(shí)就出現(xiàn)了違法訪問。深拷貝就是為了解決淺拷貝的問題。
深拷貝:就是給自己重新開辟一塊空間,并將數(shù)據(jù)拷貝到新開辟的空間中,如果一個(gè)類中涉及到資源的管理,其拷貝的構(gòu)造函數(shù),賦值運(yùn)算符重載以及析構(gòu)函數(shù)必須要顯式給出。(就是要手動(dòng)寫,不能用編譯器自動(dòng)生成的)。一般這種情況都是按照深拷貝方式提供。
所以拷貝的時(shí)候,需要重新給_str開辟一塊空間.
string(const string& s) :_str(new char[s._capacity + 1]) , _size(s._size) , _capacity(s._capacity) { strcpy(_str, s._str); }
這里也用圖淺淺的介紹一下淺拷貝和深拷貝的區(qū)別.
operator=賦值運(yùn)算符重載
正如上一個(gè)所說,=賦值運(yùn)算符也同樣存在深淺拷貝的問題,所以也必須進(jìn)行深拷貝.
它和拷貝構(gòu)造的主要區(qū)別就是:拷貝構(gòu)造是對(duì)象還沒有初始化時(shí)進(jìn)行拷貝,而賦值運(yùn)算符重載是對(duì)一個(gè)已經(jīng)存在的變量進(jìn)行賦值.
當(dāng)然同樣這里也需要深拷貝
也有一些需要注意的問題:例如s1=s2.我們把s2賦值給s1后,那么原本的s1空間該怎么辦呢?
我們的解決方案是:
把原本的s1空間釋放掉,然后再開辟一塊和s2大小相同的空間,再把內(nèi)容從s2拷貝到s1
//= 運(yùn)算符重載 string& operator=(const string& s) { //不能自己賦值給自己 if (this != &s) {//先釋放掉原本的空間 delete[] _str; _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } return *this; }
c_str
c_str就是返回c語(yǔ)言風(fēng)格的字符串,既返回char*類型字符串,返回字符串首地址即可.
const char* c_str() const { return _str; }
為什么加const呢?
第一個(gè)const是為了使普通對(duì)象和const對(duì)象都可以調(diào)用這個(gè)函數(shù),因?yàn)闄?quán)限只可以縮小,不可以放大.
第二個(gè)const是保證函數(shù)體內(nèi)的內(nèi)容不會(huì)被改變,既this指針指向的內(nèi)容無法被改變.
operator[]
實(shí)現(xiàn)[]重載,是指?jìng)鬟^來一個(gè)下標(biāo)index,返回它index下標(biāo)所對(duì)應(yīng)的值
目的是讓字符串可以像數(shù)組一樣訪問每一個(gè)元素.
char& operator[](size_t index) { //下標(biāo)必須小于字符串總大小 assert(index < _size); return _str[index]; }
當(dāng)然為了const對(duì)象也可以調(diào)用,我們可以再寫一個(gè)const修飾的operator[].
const char& operator[](size_t index) const { assert(index < _size); return _str[index]; }
size()
寫一個(gè)函數(shù),直接返回_size即可
size_t size() const { return _size; }
那可能會(huì)有人想問了:既然返回_size,那我們直接調(diào)用它這個(gè)成員不就行了,為什么還有套一層函數(shù)呢?
這是因?yàn)開size是被private修飾的,我們是不能直接訪問私有成員的.
所以需要實(shí)現(xiàn)一個(gè)公有的函數(shù)間接訪問_size.
capacity()
這個(gè)所注意的和size完全一致.
size_t capacity() const { return _capacity; }
empty()
只需要判斷當(dāng)前的size是否等于0即可.
bool empty() const { return _size == 0; }
operator+=
這個(gè)重載運(yùn)算符我們上一章講過是可以插入字符或者插入字符串的,這里也分別復(fù)用了push_back和append(),這兩個(gè)函數(shù)后面將模擬實(shí)現(xiàn).
string& operator+= (const char ch) { push_back(ch); return *this; } string& operator+= (const char* str) { append(str); return *this; }
擴(kuò)容函數(shù)(reserve)
調(diào)整容量大小到n
先new一個(gè)n+1的新空間,再把原來的數(shù)據(jù)拷貝到新空間中去,然后釋放掉原來的空間,然后將capacity設(shè)置為n.
void reserve(size_t n) { //n應(yīng)該大于之前的容量 if (n > _capacity) { //先開辟大小為n+1的空間 char* tmp = new char[n + 1]; //將原來的數(shù)據(jù)拷貝到tmp strcpy(tmp, _str); //釋放掉原來的數(shù)據(jù) delete[] _str; //將擴(kuò)容后的數(shù)據(jù)重新賦給_str _str = tmp; _capacity = n; } }
畫圖來理解一下它
resize()
resize會(huì)有以下兩種情況:
1.若n < _size,既重新調(diào)整后的大小小于原來的大小,會(huì)發(fā)生數(shù)據(jù)截?cái)?,只保留前n個(gè)字符.
2.若n > _size,這里直接復(fù)用reserve即可
既如果n<_capacity,此時(shí)_capacity不發(fā)生變化,多出的空間用ch替代.
如果n>_capacity,此時(shí)_capacity需要擴(kuò)容(1.5倍速度,不一定是n),直到最接近為止.
void resize(size_t n, char ch = '\0') { if (n > _size) { //插入數(shù)據(jù) //reserve會(huì)和容量進(jìn)行比較以及是否需要闊人 reserve(n); //多余的字符用ch替代 for (size_t i = _size; i < n; i++) { _str[i] = ch; } //字符串結(jié)束 _str[n] = '\0'; _size = n; } else { //刪除數(shù)據(jù) //直接將第n個(gè)數(shù)據(jù)改為'\0',這樣相當(dāng)于將后面的數(shù)據(jù)全部刪除了. _str[n] = '\0'; _size = n; } }
push_back()
push_back的作用是在原字符串后上拼接一個(gè)字符,首先我們現(xiàn)需要判斷空間是否足夠,如不夠,則需要擴(kuò)容,復(fù)用之前的reserve函數(shù),再進(jìn)行插入數(shù)據(jù),最后加上'\0'.
當(dāng)然還可以利用復(fù)用insert()函數(shù)進(jìn)行插入,這個(gè)后面再實(shí)現(xiàn).
void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); _str[_size] = ch; ++_size; _str[_size] = '/0'; } }
還以復(fù)用insert這樣插入,會(huì)使代碼健壯性更強(qiáng),更加簡(jiǎn)潔.
這個(gè)inser()函數(shù)后面會(huì)實(shí)現(xiàn).
insert(_size, ch);
append()
這個(gè)與push_back不同的是:push_back()只能插入一個(gè)字符,append()只可以插入一個(gè)字符串.
這里的問題就出現(xiàn)了,我們不知道追加的字符串長(zhǎng)度,自然擴(kuò)容的時(shí)候也不知道擴(kuò)大到多少,是2倍還是3倍,所以這里要看插入的字符串的長(zhǎng)度len,只要要讓空間開到_size+len.
讓空間滿足最低的情況,能把所有的字符容納下,最后利用strcpy將其數(shù)據(jù)拷貝過來即可.
void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size = _size + len; }
當(dāng)然同樣可以復(fù)用insert函數(shù).
insert(_size, str);
下面就該說insert函數(shù)了.
insert()
insert也分為兩種情況:插入一個(gè)字符或插入多個(gè)字符(字符串)
插入一個(gè)字符:方法類似于順序表的插入
string& insert(size_t pos, char ch) { //插入的位置必須要與字符串大小 assert(pos <= _size); //如果空間滿了,則需要擴(kuò)容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //插入操作 size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; return *this; }
插入多個(gè)字符:
string& insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } //先把空間騰出來 size_t end = _size + len; while (end >= pos + len) { _str[end] = _str[end - len]; --end; } //再把利用strncpy把指定長(zhǎng)度的字符串插入 strncpy(_str + pos, str, len); _size = _size + len; }
說了插入就該說刪除了.
erase()
這個(gè)函數(shù)也比較巧妙,首先輸入兩個(gè)參數(shù):第一個(gè)參數(shù)是要開始刪除的下標(biāo),第二個(gè)參數(shù)是要?jiǎng)h除的長(zhǎng)度.
首先第二個(gè)參數(shù)默認(rèn)缺省值是npos,npos是一個(gè)非常大的數(shù).
首先判斷l(xiāng)en是否等于npos或者當(dāng)前位置+len是否大于總長(zhǎng)度,若是,則直接將pos位置置為'\0',后面的元素也就相當(dāng)于刪除了
如果不是,則把pos+len之后的元素拷貝到pos位置之后,這樣就相當(dāng)于刪除了pos~pos+len之間的這一段字符.再把_size-len,相當(dāng)于是一個(gè)覆蓋的過程.
void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } }
find()
也是實(shí)現(xiàn)兩個(gè),利用strstr()函數(shù)來查找字符串.
1.如果查找一個(gè)字符
如果找到,則直接返回字符所對(duì)應(yīng)的下標(biāo)pos,否則返回npos.
2.如果查找一個(gè)字符串
對(duì)于這種情況,找到字符串后,我們需要返回第一個(gè)字符的下標(biāo),通過指針差值確定目標(biāo)字符串的位置。
1.查找一個(gè)字符
思路很簡(jiǎn)單,就是利用循環(huán)
size_t find(char ch, size_t pos = 0) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (ch == _str[i]) { return i; } } return npos; }
2.查找一個(gè)字符串
利用strstr函數(shù),從第pos個(gè)位置開始查找,如果找到則返回目標(biāo)字符串的首元素地址,若沒有找到則返回空指針
size_t find(const char* sub, size_t pos = 0) { assert(sub); assert(pos < _size); const char* ptr = strstr(_str + pos, sub); if (ptr == nullptr) { return npos; } else { return ptr - _str; } }
substr()
這個(gè)函數(shù)實(shí)現(xiàn)比較簡(jiǎn)單,復(fù)用之前實(shí)現(xiàn)的+=即可
首先計(jì)算出實(shí)際要切割的長(zhǎng)度realLen = len
如果pos+len>_size或者len == npos,則需要重新計(jì)算realLen = _size - pos
然后循環(huán)realLen次,創(chuàng)建一個(gè)string類型的sub變量,每次利用sub+=這個(gè)字符即可.
string substr(size_t pos, size_t len = npos) { assert(pos < _size); size_t realLen = len; if (len == npos || pos + len > _size) { realLen = _size - pos; } string sub; for (size_t i = 0; i < realLen; i++) { sub += _str[pos + i]; } return sub; }
比較大小函數(shù)
實(shí)現(xiàn)比較大小,只需要實(shí)現(xiàn)兩個(gè)運(yùn)算符重載即可:
1. > 或 <其中任意一個(gè)
2.==
剩下的>=、<=、!=等等復(fù)用即可.
實(shí)現(xiàn)> 或 < 時(shí),利用strcmp比較函數(shù)即可.
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) { return !(*this == s); }
這樣string的模擬實(shí)現(xiàn)基本就完成了,下面是總代碼:
namespace hmylq { class string { public: typedef char* iterator; typedef const char* const_iterator; string(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } //拷貝構(gòu)造 - - - 1 string(const string& s) :_str(new char[s._capacity + 1]) , _size(s._size) , _capacity(s._capacity) { strcpy(_str, s._str); } //拷貝構(gòu)造 - - - 2 /* string(const string& s) :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(_str, tmp._str); swap(_size, tmp._size); swap(_capacity, tmp._capacity); }*/ //析構(gòu)函數(shù) ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } iterator begin() { return _str; } iterator end() { return _str + _size; } const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } / void push_back(char ch) { /*if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); _str[_size] = ch; ++_size; _str[_size] = '/0'; }*/ insert(_size, ch); } string& operator += (char ch) { push_back(ch); return *this; } void append(const char* str) { /*size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size = _size + len;*/ insert(_size, str); } string& operator += (const char* str) { append(str); return *this; } //= 運(yùn)算符重載 string& operator=(const string& s) { if (this != &s) { delete[] _str; _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; return *this; } } void clear() { _str[0] = '\0'; _size = 0; } void swap(string& tmp) { ::swap(_str, tmp._str); ::swap(_size, tmp._size); ::swap(_capacity, tmp._capacity); } const char* c_str() const { return _str; } // size_t size() const { return _size; } size_t capacity() const { return _capacity; } bool empty() const { return _size == 0; } void resize(size_t n, char ch = '\0') { if (n > _size) { reserve(n); for (size_t i = _size; i < n; i++) { _str[i] = ch; } _str[n] = '\0'; _size = n; } else { _str[n] = '\0'; _size = n; } } void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } / char& operator[](size_t index) { assert(index < _size); return _str[index]; } const char& operator[](size_t index) const { assert(index < _size); return _str[index]; } // 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) { return !(*this == s); } size_t find(char ch, size_t pos = 0) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (ch == _str[i]) { return i; } } return npos; } size_t find(const char* sub, size_t pos = 0) { assert(sub); assert(pos < _size); const char* ptr = strstr(_str + pos, sub); if (ptr == nullptr) { return npos; } else { return ptr - _str; } } string& insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } 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 (_size + len > _capacity) { reserve(_size + len); } size_t end = _size + len; while (end >= pos + len) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len); _size = _size + len; } void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } } string substr(size_t pos, size_t len = npos) { assert(pos < _size); size_t realLen = len; if (len == npos || pos + len > _size) { realLen = _size - pos; } string sub; for (size_t i = 0; i < realLen; i++) { sub += _str[pos + i]; } return sub; } private: char* _str; int _size; int _capacity; const static size_t npos = -1; }; }
總結(jié)
到此這篇關(guān)于C++ STL之string的模擬實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++ STL string模擬實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vector與map的erase()函數(shù)詳細(xì)解析
vector和map都不能將it++寫在for循環(huán)中,而在循環(huán)體內(nèi)erase(it)2013-09-09關(guān)于C++中push_back()函數(shù)的用法及代碼實(shí)例
push_back是vector的一個(gè)方法,表示將一個(gè)元素存儲(chǔ)到容器的末尾,下面這篇文章主要給大家介紹了關(guān)于C++中push_back()函數(shù)用法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11vs運(yùn)行時(shí)報(bào)C4996代碼錯(cuò)誤的問題解決
C4996錯(cuò)誤的意思:是VS覺得strcpy這函數(shù)不安全,建議你使更安全的函數(shù),那么如何解決呢,本文主要介紹了vs運(yùn)行時(shí)報(bào)C4996代碼錯(cuò)誤的問題解決,感興趣的可以了解一下2024-01-01C++ 中約瑟夫環(huán)替換計(jì)數(shù)器m(數(shù)組解決)
這篇文章主要介紹了C++ 中約瑟夫環(huán)替換計(jì)數(shù)器m(數(shù)組解決)的相關(guān)資料,需要的朋友可以參考下2017-05-05總結(jié)了24個(gè)C++的大坑,你能躲過幾個(gè)
這篇文章主要介紹了總結(jié)了24個(gè)C++的大坑,你能躲過幾個(gè),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-05-05Qt自定義控件實(shí)現(xiàn)簡(jiǎn)易儀表盤
這篇文章主要為大家詳細(xì)介紹了Qt自定義控件實(shí)現(xiàn)簡(jiǎn)易儀表盤,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12