前言:
在之前的學(xué)習(xí)中,我們已經(jīng)對string類進行了簡單的介紹,大家只要能夠正常使用即可。但是在面試中,面試官總喜歡讓學(xué)生自己 來模擬實現(xiàn)string類,最主要是實現(xiàn)string類的構(gòu)造、拷貝構(gòu)造、賦值運算符重載以及析構(gòu)函數(shù)。因此,接下來我將帶領(lǐng)大家手動模擬實現(xiàn)一下。
目錄
(一)成員函數(shù)
1、構(gòu)造函數(shù)
2、拷貝構(gòu)造
3、賦值重載
4、析構(gòu)函數(shù)
(二)容量
1、size()
2、capacity()
3、reserve()
4、resize()
5、clear()
(三)元素訪問
1、?operator[]
(四)修改?
1、?operator+=
2、append()
3、push_back()
4、insert()
5、erase()
6、swap()
(五)字符串操作?
?1、c_str()
2、find()
(六)非成員函數(shù)重載
1、relational operators()
2、operator<<
3、operator>>
(七)代碼匯總
(八)總結(jié)
(一)成員函數(shù)
1、構(gòu)造函數(shù)
剛開始時,如果我們要實現(xiàn)構(gòu)造函數(shù),可能就需要分別實現(xiàn)帶參的構(gòu)造函數(shù)和無參的構(gòu)造函數(shù),但是有沒有簡單方法可以做到一步到位呢?
?????因此,為了更加的靈活方便,我們直接把帶參的構(gòu)造函數(shù)和無參構(gòu)造函數(shù)集合,形成全缺省的構(gòu)造函數(shù),這樣就省得再去寫兩個構(gòu)造函數(shù)。
代碼如下:
//全缺省的構(gòu)造函數(shù)
//string(const char* str = nullptr) //不可以,對其解引用如果遇到空指針就報錯
//string(const char* str = '\0') //類型不匹配,char 不能匹配為指針
//string(const char* str = "\0") //可以
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 5 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
2、拷貝構(gòu)造
編譯器默認的實現(xiàn)的是淺拷貝,但是淺拷貝存在問題:
- 如果對象中管理資源,最后就會導(dǎo)致多個對象共享同一份資源,當一個對象銷毀時就會將該資源釋放掉,而此時另一些對象不知道該資源已經(jīng)被釋放,以為還有效,所以當繼續(xù)對資源進項操作時,就會發(fā)生發(fā)生了訪問違規(guī)。
因此為了解決上述的問題,可以采用深拷貝解決淺拷貝問題:
- 每個對象都有一份獨立的資源,不要和其他對象共享。父母給每個孩子都買一份玩具,各自玩各自的就不會有問題了。
代碼如下:
//深拷貝
// str3(str2)
string(const string& STR)
:_size(STR._size)
, _capacity(STR._capacity)
{
_str = new char[STR._capacity + 1];
strcpy(_str, STR._str);
}
3、賦值重載
注意:
- 當以拷貝的方式初始化一個對象時,會調(diào)用拷貝構(gòu)造函數(shù);
- 當給一個對象賦值時,會調(diào)用重載過的賦值運算符。
即使我們沒有顯式的重載賦值運算符,編譯器也會以默認地方式重載它。默認重載的賦值運算符功能很簡單,就是將原有對象的所有成員變量一一賦值給新對象,這和默認拷貝構(gòu)造函數(shù)的功能類似。
代碼如下:
string& operator=(const 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;
}
4、析構(gòu)函數(shù)
析構(gòu)函數(shù)的實現(xiàn)就比較簡單,只需將指針所指的空間進行釋放并把置空即可(防止野指針)??,最后把剩余的兩個成員置為0即可。
代碼如下:
//析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
(二)容量
1、size()
顧名思義返回字符串的長度(以字符數(shù)為單位)
代碼如下:
size_t size() const
{
return _size;
}
2、capacity()
返回當前為basic_string分配的存儲空間的大小,以字符表示。
代碼如下:
size_t capacity() const
{
return _capacity;
}
3、reserve()
表示請求更改容量,使字符串容量適應(yīng)計劃的大小更改為最多?n?個字符。
注意是有效字符,不包含標識字符,而在具體實現(xiàn)的時候,我們在底層多開一個空間給\0。
代碼如下:
//擴容操作
void reserve(size_t N)
{
if (N > _capacity)
{
char* tmp = new char[N + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = N;
}
}
4、resize()
其實它的情況大體上可以分為插入數(shù)據(jù)和刪除數(shù)據(jù)兩種情況。
- 1.對于插入數(shù)據(jù)來說直接調(diào)用【reserve】提前預(yù)留好空間,然后搞一個for循環(huán)將字符ch尾插到數(shù)組里面去,最后再在數(shù)組末尾插入一個\0標識字符;
- 2.對于刪除數(shù)據(jù)就比較簡單了,如果 n 小于當前字符串長度,則當前值將縮短為其第一個 n 個字符,刪除第?n?個字符以外的字符,然后重置一下_size的大小為n即可。
代碼如下:
//擴容+初始化
void resize(size_t n, char STR = '\0')
{
if (n < _size)
{
// 刪除數(shù)據(jù)--保留前n個
_size = n;
_str[_size] = '\0';
}
else if (n > _size)
{
if (n > _capacity)
{
reserve(n);
}
size_t end = _size;
while (end < n)
{
_str[end] = STR;
end++;
}
_size = n;
_str[_size] = '\0';
}
}
5、clear()
顧名思義就是清除字符串,擦除basic_string的內(nèi)容,該內(nèi)容變?yōu)榭兆址ㄩL度為?0?個字符)。
代碼如下:
void clear()
{
_str[0] = '\0';
_size = 0;
}
(三)元素訪問
1、?operator[]
元素訪問操作相對來說用的最多的就是operator[] ;
- 對它進行調(diào)用時可能進行的是寫操作,也可能進行讀操作,所以為了適應(yīng)const和非const對象,operator[]應(yīng)該實現(xiàn)兩個版本的函數(shù);
- 并且這個函數(shù)處理越界訪問的態(tài)度就是assert直接斷言,而at對于越界訪問的態(tài)度是拋異常。
代碼如下:
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
?文章來源地址http://www.zghlxwxcb.cn/news/detail-448111.html
(四)修改?
1、?operator+=
追加到字符串,通過在當前值的末尾附加其他字符來擴展
在這里我們只實現(xiàn)添加字符和字符串的操作;
我們可以直接復(fù)用【push_back】的操作來實現(xiàn)。
代碼如下:
//+=
string& operator+=(char STR_1)
{
push_back(STR_1);
return *this;
}
string& operator+=(const char* STR_2)
{
append(STR_2);
return *this;
}
?
2、append()
追加到字符串, 通過在當前值的末尾附加其他字符來擴展。
- 我們可以直接調(diào)用strcpy接口來進行字符串的尾插,但是需要注意一點,那就是【string】類的字符串函數(shù)是不會進行自動擴容的,所以我們需要判斷一下是否需要進行擴容,在空間預(yù)留好的情況下進行字符串的尾插即可實現(xiàn);
- 其次,如果已經(jīng)實現(xiàn)了【insert】函數(shù)的情況下。我們可以直接復(fù)用【insert】函數(shù)也可實現(xiàn)對應(yīng)的操作。
代碼如下:
//追加字符串
void append(const char* STR)
{
size_t len = strlen(STR);
if (len + _size > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, STR);
_size += len;
//insert(_size, STR);
}
3、push_back()
注意:
- 首先對于【push_back】有一個特別需要注意的地方就是當容量不夠時的擴容操作。如果是一個空對象進行push_back的話,這時如果我們采取的二倍擴容就有問題,因為0*2還是0,所以對于空對象的情況我們應(yīng)該給他一個初始的capacity值,所以上述構(gòu)造函數(shù)的時候我給成了【5】,其他情況下進行二倍擴容即可;
- 其次,就是在尾插字符之后,要記得進行補【\0】操作,,否則在打印的時候就會有麻煩了。
- 最后跟【append】一樣,如果已經(jīng)實現(xiàn)了【insert】函數(shù)的情況下。我們可以直接復(fù)用【insert】函數(shù)也可實現(xiàn)對應(yīng)的操作。
代碼如下:
//尾插操作
void push_back(char STR)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = STR;
++_size;
_str[_size] = '\0';
//insert(_size, STR);
}
4、insert()
插入到字符串中,在?pos(或?p)指示的字符之前將其他字符插入
注意:
- 對于【insert】函數(shù),有經(jīng)常會引出錯誤的地方,那就是對于while循環(huán)里面的操作;
可能很多的小伙伴在while循環(huán)里面都是這樣寫的:_str[end + 1] = _str[end] ,那么這樣寫有沒有問題呢?答案是會出問題的;
- 我們的end是size_t定義的,因為size_t是無符號數(shù),那么-1會被認為是無符號整數(shù),進行隱式類型轉(zhuǎn)換,由于-1的補碼是全1,此時就是恒大于0,程序會陷入死循環(huán)。所以我們可以不用size_t來定義end,防止發(fā)生隱式類型轉(zhuǎn)換;
- 那么是不是只要把【size_t end = _size + len;】中的【end】用 int 定義就可以解決了呢?答案當然不是的 (是不是覺得很坑了呀?。?!);
- 因為-1在和size_t定義的pos進行比較時,又會發(fā)生隱式類型轉(zhuǎn)換。這是因為比較運算符也是運算符,只要進行運算就有可能出現(xiàn)隱式類型轉(zhuǎn)換,因此此時又可能出現(xiàn)上述那樣的情況,-1就又會被轉(zhuǎn)為無符號整型,程序就又陷入死循環(huán);
- 那么有沒有解決方法呢?當然是有的,我們只需在比較時將【size_t】的pos強轉(zhuǎn)為【int】類型,此時再去比較就沒得問題了;
- 但當我們就想使用size_t類型,通過把【end-1】位置的元素挪到【end】位置上去,在while循環(huán)條件的判斷位置,我們用end來和pos位置進行比較,end應(yīng)該大于pos的位置,一旦end=pos我們就跳出循環(huán),這樣就可以了。
代碼如下:
//插入字符操作
string& insert(size_t pos, char STR_1)
{
assert(pos < _size);
if (_size + 1 > _capacity)
{
reserve(2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = STR_1;
++_size;
return *this;
}
//插入字符串
string& insert(size_t pos, const char* STR_2)
{
assert(pos < _size);
size_t len = strlen(STR_2);
if (_size + len > _capacity){
reserve(_size + len);
}
// 挪動數(shù)據(jù)
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
// 拷貝插入
strncpy(_str + pos, STR_2, len);
_size += len;
return *this;
}
5、erase()
意思很簡單,就是從字符串中刪除字符
對于刪除,思路很簡單,分為兩種情況下的刪除:
- 1.如果當前位置加上要刪除的長度大于字符串的長度,即【?pos + len >= _size】,此時的意思即為刪除pos之后的所有元素;
- 2.除了上述情況,就是在字符串內(nèi)正常刪除操作。我們只需利用strcpy來進行,將pos+len之后的字符串直接覆蓋到pos位置,這樣實際上就完成了刪除的工作。
注意:
- 對于【npos】這個參數(shù),首先我們知道對于靜態(tài)成員變量,它的規(guī)則是在類外定義,類里面聲明,定義時不加static關(guān)鍵字;
- 但如果靜態(tài)成員變量有const修飾,這時它可以在類內(nèi)直接進行定義,這樣的特性只針對于整型,對于其他類型則是不適用的;
- npos就是const static修飾的成員變量,可以直接在類內(nèi)進行定義。
代碼如下:
//刪除操作
string& 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;
}
return *this;
}
6、swap()
至于交換,這個就沒有必要再多說什么了很簡單,我相信大家肯定也會這個。
代碼如下:
//交換
void swap(string& STR)
{
std::swap(_str, STR._str);
std::swap(_capacity, STR._capacity);
std::swap(_size, STR._size);
}
?
(五)字符串操作?
?1、c_str()
獲取等效的 C 字符串,返回指向一個數(shù)組的指針,該數(shù)組包含以 null 結(jié)尾的字符序列(即 C 字符串),表示basic_string對象的當前值
const char* c_str()
{
return _str;
}
2、find()
查找字符串中的第一個匹配項, 在basic_string中搜索由其參數(shù)指定的序列的第一個匹配項。
代碼展示:
//查找
size_t find(char STR, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == STR)
{
return i;
}
}
return npos;
}
size_t find(const char* STR, size_t pos = 0)
{
assert(pos < _size);
char* p = strstr(_str + pos, STR);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
?
(六)非成員函數(shù)重載
1、relational operators()
basic_string的關(guān)系運算符,以ascll碼的方式比較大小
這個實現(xiàn)的過程,跟之前日期類的時間如出一轍,基本上都是一樣的。
代碼如下:
//比較大小
bool operator >(const string& STR) const
{
return strcmp(_str, STR._str) > 0;
}
bool operator == (const string & STR)const
{
return strcmp(_str, STR._str) == 0;
}
bool operator >= (const string & STR)const
{
return *this > STR || *this == STR;
}
bool operator < (const string & STR)const
{
return !(*this >= STR);
}
bool operator <= (const string& STR)const
{
return !(*this > STR);
}
bool operator!=(const string& STR) const
{
return !(*this == STR);
}
2、operator<<
將字符串插入流, 將符合?str?值的字符序列插入到 os?中。
代碼如下:
//operator<<
ostream& operator<<(ostream& out, const string& STR)
{
for (auto e : STR)
{
out << e;
}
return out;
}
3、operator>>
從流中提取字符串, 從輸入流中提取字符串,將序列存儲在 str 中,該序列被覆蓋(替換 str?的先前值)
注意:
- 流提取是以空格和\n作為間隔標志的 ,而【getline】則是以【\0】就停止。
?代碼如下:
//operator>>
istream& operator>>(istream& in, string& STR)
{
STR.clear();
char ch = in.get();
//如果輸入到緩沖區(qū)里的字符串非常非常的長,那么+=就需要頻繁的擴容,則效率就會降低
//因此,在這里可以使用開辟一個數(shù)組,先將有效數(shù)據(jù)放入數(shù)組中,在進行操作,可有效提高效率
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\0')
{
buff[i++] = ch;
if (i == 127) //最后得留一個位置給\0
{
buff[127] = '\0';
STR += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
STR += buff;
}
return in;
}
?
(七)代碼匯總
代碼匯總?cè)缦拢?/strong>
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;
}
//全缺省的構(gòu)造函數(shù)
//string(const char* str = nullptr) //不可以,對其解引用如果遇到空指針就報錯
//string(const char* str = '\0') //類型不匹配,char 不能匹配為指針
//string(const char* str = "\0") //可以
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 5 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//深拷貝
// str3(str2)
string(const string& STR)
:_size(STR._size)
, _capacity(STR._capacity)
{
_str = new char[STR._capacity + 1];
strcpy(_str, STR._str);
}
//賦值操作
string& operator=(const string& STR)
{
if (this != &STR)
{
// str1 = str1 的情況不滿足
/*delete[] _str;
_str = new char[s._capaicty + 1];
strcpy(_str, s._str);
_size = s._size;
_capaicty = s._capaicty;*/
char* tmp = new char[STR._capacity + 1];
strcpy(tmp, STR._str);
delete[] _str;
_str = tmp;
_size = STR._size;
_capacity = STR._capacity;
}
return *this;
}
//析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str()
{
return _str;
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
//比較大小
bool operator >(const string& STR) const
{
return strcmp(_str, STR._str) > 0;
}
bool operator == (const string & STR)const
{
return strcmp(_str, STR._str) == 0;
}
bool operator >= (const string & STR)const
{
return *this > STR || *this == STR;
}
bool operator < (const string & STR)const
{
return !(*this >= STR);
}
bool operator <= (const string& STR)const
{
return !(*this > STR);
}
bool operator!=(const string& STR) const
{
return !(*this == STR);
}
//擴容+初始化
void resize(size_t n, char STR = '\0')
{
if (n < _size)
{
// 刪除數(shù)據(jù)--保留前n個
_size = n;
_str[_size] = '\0';
}
else if (n > _size)
{
if (n > _capacity)
{
reserve(n);
}
size_t end = _size;
while (end < n)
{
_str[end] = STR;
end++;
}
_size = n;
_str[_size] = '\0';
}
}
//擴容操作
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 STR)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = STR;
++_size;
_str[_size] = '\0';
//insert(_size, STR);
}
//追加字符串
void append(const char* STR)
{
size_t len = strlen(STR);
if (len + _size > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, STR);
_size += len;
//insert(_size, STR);
}
//+=
string& operator+=(char STR_1)
{
push_back(STR_1);
return *this;
}
string& operator+=(const char* STR_2)
{
append(STR_2);
return *this;
}
//插入字符操作
string& insert(size_t pos, char STR_1)
{
assert(pos < _size);
if (_size + 1 > _capacity)
{
reserve(2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = STR_1;
++_size;
return *this;
}
//插入字符串
string& insert(size_t pos, const char* STR_2)
{
assert(pos < _size);
size_t len = strlen(STR_2);
if (_size + len > _capacity){
reserve(_size + len);
}
// 挪動數(shù)據(jù)
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
// 拷貝插入
strncpy(_str + pos, STR_2, len);
_size += len;
return *this;
}
//刪除操作
string& 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;
}
return *this;
}
//交換
void swap(string& STR)
{
std::swap(_str, STR._str);
std::swap(_capacity, STR._capacity);
std::swap(_size, STR._size);
}
//查找
size_t find(char STR, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == STR)
{
return i;
}
}
return npos;
}
size_t find(const char* STR, size_t pos = 0)
{
assert(pos < _size);
char* p = strstr(_str + pos, STR);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
//operator<<
ostream& operator<<(ostream& out, const string& STR)
{
for (auto e : STR)
{
out << e;
}
return out;
}
//operator>>
istream& operator>>(istream& in, string& STR)
{
STR.clear();
char ch = in.get();
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\0')
{
buff[i++] = ch;
if (i == 127)
{
buff[127] = '\0';
STR += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
STR += buff;
}
return in;
}
(八)總結(jié)
到此,關(guān)于string的模擬實現(xiàn),在這里我們主要實現(xiàn)的是經(jīng)常用得到的,對于其他的,我們并沒有一一列舉。如果后面有機會再給大家展示。
接下來,我們簡單總結(jié)一下本文:
- 我們從文檔的先后順序入手,依次對各個板塊的常用接口進行了模擬實現(xiàn);
- 大家在上手操作的時候,一定要想明白為什么,做到真正的掌握string類它是非常重要的。在面試中,面試官總喜歡讓學(xué)生自己來模擬實現(xiàn)string類,最主要是實現(xiàn)string類的構(gòu)造、拷貝構(gòu)造、賦值運算符重載以及析構(gòu)函數(shù)。
到此,便于string類的模擬實現(xiàn)便講解完畢了。希望本文對大家有所幫助,感謝各位的觀看!?。?/strong>
文章來源:http://www.zghlxwxcb.cn/news/detail-448111.html
?
到了這里,關(guān)于【C++】——string的模擬實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!