目錄
string類(lèi)的模擬實(shí)現(xiàn)
經(jīng)典的string類(lèi)問(wèn)題
淺拷貝
深拷貝
寫(xiě)時(shí)拷貝(了解)
構(gòu)造函數(shù)
string的全缺省的構(gòu)造函數(shù):
string的拷貝構(gòu)造函數(shù)
傳統(tǒng)寫(xiě)法
現(xiàn)代寫(xiě)法
string的賦值重載函數(shù)
傳統(tǒng)寫(xiě)法
現(xiàn)代寫(xiě)法
string的無(wú)參構(gòu)造函數(shù):
遍歷函數(shù)
operator[ ]
迭代器
迭代器的底層實(shí)現(xiàn)begin和end:
范圍for的使用
修改字符串相關(guān)函數(shù):
reserve?
push_back
append
operator+=
insert(字符的版本)
insert(字符串常量)
erase
resize
find(查找單個(gè)字符)
find(查找子串)
substr
比較運(yùn)算符的重載:
流插入cout
流提取cin
優(yōu)化寫(xiě)法
clear
前言:
??個(gè)人博客:Dream_Chaser
??博客專(zhuān)欄:C++
??本篇內(nèi)容:string類(lèi)通用函數(shù)的模擬實(shí)現(xiàn)
string類(lèi)的模擬實(shí)現(xiàn)
經(jīng)典的string類(lèi)問(wèn)題
上面已經(jīng)對(duì)string類(lèi)進(jìn)行了簡(jiǎn)單的介紹,大家只要能夠正常使用即可。在面試中,面試官總喜歡讓學(xué)生自己來(lái)模擬實(shí)現(xiàn)string類(lèi),最主要是實(shí)現(xiàn)string類(lèi)的構(gòu)造、拷貝構(gòu)造、賦值運(yùn)算符重載以及析構(gòu)函數(shù)。大家看下以下string類(lèi)的實(shí)現(xiàn)是否有問(wèn)題?
淺拷貝
說(shuō)明:上述String類(lèi)沒(méi)有顯式定義其拷貝構(gòu)造函數(shù)與賦值運(yùn)算符重載,此時(shí)編譯器會(huì)合成默認(rèn)的,當(dāng)用s1構(gòu) 造s2時(shí),編譯器會(huì)調(diào)用默認(rèn)的拷貝構(gòu)造。最終導(dǎo)致的問(wèn)題是,s1、s2共用同一塊內(nèi)存空間,在釋放時(shí)同一塊 空間被釋放多次而引起程序崩潰,這種拷貝方式,稱(chēng)為淺拷貝。
淺拷貝
淺拷貝:也稱(chēng)位拷貝,編譯器只是將對(duì)象中的值拷貝過(guò)來(lái)。如果對(duì)象中管理資源,最后就會(huì)導(dǎo)致多個(gè)對(duì)象共 享同一份資源,當(dāng)一個(gè)對(duì)象銷(xiāo)毀時(shí)就會(huì)將該資源釋放掉,而此時(shí)另一些對(duì)象不知道該資源已經(jīng)被釋放,以為 還有效,所以當(dāng)繼續(xù)對(duì)資源進(jìn)項(xiàng)操作時(shí),就會(huì)發(fā)生發(fā)生了訪問(wèn)違規(guī)。
深拷貝
如果一個(gè)類(lèi)中涉及到資源的管理,其拷貝構(gòu)造函數(shù)、賦值運(yùn)算符重載以及析構(gòu)函數(shù)必須要顯式給出。一般情況都是按照深拷貝方式提供。
寫(xiě)時(shí)拷貝(了解)
寫(xiě)時(shí)拷貝就是一種拖延癥,是在淺拷貝的基礎(chǔ)之上增加了引用計(jì)數(shù)的方式來(lái)實(shí)現(xiàn)的。
引用計(jì)數(shù):用來(lái)記錄資源使用者的個(gè)數(shù)。在構(gòu)造時(shí),將資源的計(jì)數(shù)給成1,每增加一個(gè)對(duì)象使用該資源,就給計(jì)數(shù)增加1,當(dāng)某個(gè)對(duì)象被銷(xiāo)毀時(shí),先給該計(jì)數(shù)減1,然后再檢查是否需要釋放資源,如果計(jì)數(shù)為1,說(shuō)明該對(duì)象時(shí)資源的最后一個(gè)使用者,將該資源釋放;否則就不能釋放,因?yàn)檫€有其他對(duì)象在使用該資源。
構(gòu)造函數(shù)
string的全缺省的構(gòu)造函數(shù):
我們對(duì)string成員函數(shù)進(jìn)行模擬實(shí)現(xiàn)的時(shí)候,首先要定義一個(gè)自定義的命名空間,避免與庫(kù)里面的沖突:
讓我們來(lái)看看錯(cuò)誤的案例:
①注意常量字符串的用法,權(quán)限不能放大:
②嘗試給_str聲明加上const,這樣就會(huì)導(dǎo)致初始化的時(shí)候出現(xiàn)隨機(jī)值:
③需要注意的是"string.h"要定義在std的下方:
那么帶參的構(gòu)造可以怎么寫(xiě)呢?初始化列表的順序應(yīng)該對(duì)應(yīng)著成員變量聲明的順序
string帶參的構(gòu)造是否可以再優(yōu)化一下呢?
注意new的時(shí)候的+1是為了給'\0'預(yù)留的空間:
代碼實(shí)現(xiàn):
此構(gòu)造函數(shù)接受一個(gè)可選參數(shù)(默認(rèn)為空字符串),用于初始化一個(gè)字符串對(duì)象,根據(jù)傳入的C風(fēng)格字符串(const char* 類(lèi)型)計(jì)算其長(zhǎng)度,并據(jù)此分配內(nèi)存空間,最后,將傳入的字符串內(nèi)容復(fù)制到新分配的內(nèi)存區(qū)域,以便在C++字符串類(lèi)中進(jìn)行管理。
//構(gòu)造函數(shù),用于初始化一個(gè)字符串類(lèi)對(duì)象
string(const char* str = "")
:_size(strlen(str))// 初始化_size成員變量,存儲(chǔ)傳入C風(fēng)格字符串(char數(shù)組)str的長(zhǎng)度
, _capacity(_size)// 初始化_capacity成員變量,初始容量與傳入字符串長(zhǎng)度相同
{
_str = new char[_capacity + 1];// 動(dòng)態(tài)分配內(nèi)存,為字符串對(duì)象分配一個(gè)新的字符數(shù)組
strcpy(_str, str); // 使用strcpy將傳入的字符串str復(fù)制到新分配的字符數(shù)組中
}
string的拷貝構(gòu)造函數(shù)
傳統(tǒng)寫(xiě)法
//傳統(tǒng)寫(xiě)法 -- 拷貝構(gòu)造
//s2(s1)
string(const string& s)
{
_str = new char[s._capacity + 1];//僅僅開(kāi)空間
strcpy(_str, s._str);//
_size = s._size;
_capacity = s._capacity;
}
現(xiàn)代寫(xiě)法
// 定義交換函數(shù),將當(dāng)前字符串對(duì)象與輸入?yún)?shù)s的內(nèi)部數(shù)據(jù)(字符數(shù)組、長(zhǎng)度及容量)進(jìn)行交換
void swap(string& s) {
// 使用STL中的std::swap交換三個(gè)關(guān)鍵成員變量
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_size, s._capacity);
}
// 構(gòu)造函數(shù),復(fù)制輸入?yún)?shù)s的字符串內(nèi)容
// 使用臨時(shí)字符串對(duì)象tmp存儲(chǔ)s的內(nèi)容,然后通過(guò)調(diào)用swap函數(shù)交換數(shù)據(jù)
string(const string& s) :
_str(nullptr),
_size(0),
_capacity(0)
{
string tmp(s._str); // 創(chuàng)建臨時(shí)字符串tmp,拷貝s的內(nèi)容
swap(tmp); // 交換當(dāng)前對(duì)象與tmp的數(shù)據(jù),實(shí)現(xiàn)深拷貝
}
string的賦值重載函數(shù)
傳統(tǒng)寫(xiě)法
//傳統(tǒng)寫(xiě)法--賦值重載
// 使得可以使用 "s2 = s3" 的形式,將 s3 字符串對(duì)象的值賦給 s2
string& operator=(const string& s)
{
// 檢查是否為自我賦值,即檢查是否同一個(gè)對(duì)象
if (this != &s)
{
// 創(chuàng)建一個(gè)臨時(shí)字符數(shù)組 tmp,大小為 s 的容量加一(包含結(jié)束符 '\0')
char* tmp = new char[s._capacity + 1];
// 使用 strcpy 函數(shù)將 s 的內(nèi)部字符串復(fù)制到臨時(shí)數(shù)組 tmp 中
strcpy(tmp, s._str);
// 刪除當(dāng)前對(duì)象已有的內(nèi)部字符串?dāng)?shù)組
delete[] _str;
// 將臨時(shí)數(shù)組 tmp 的地址賦給當(dāng)前對(duì)象的內(nèi)部字符串指針 _str
_str = tmp;
// 將源字符串對(duì)象 s 的大小賦給當(dāng)前對(duì)象的大小屬性 _size
_size = s._size;
// 同樣將源字符串對(duì)象 s 的容量賦給當(dāng)前對(duì)象的容量屬性 _capacity
_capacity = s._capacity;
}
// 返回當(dāng)前對(duì)象的引用,以支持連續(xù)賦值如 "s1 = s2 = s3"
return *this;
}
現(xiàn)代寫(xiě)法
// 重載賦值運(yùn)算符,實(shí)現(xiàn)字符串對(duì)象之間的深拷貝賦值操作
string& operator=(const string& s)
{
// 檢查是否自我賦值(即源對(duì)象與目標(biāo)對(duì)象相同),避免不必要的資源釋放與重新分配
if (this != &s)
{
// 創(chuàng)建臨時(shí)字符串對(duì)象tmp,并使用s的內(nèi)容初始化
string tmp(s);
// 調(diào)用自定義的swap函數(shù),將當(dāng)前對(duì)象的數(shù)據(jù)與臨時(shí)對(duì)象tmp的數(shù)據(jù)進(jìn)行交換
// 這樣可以確保原對(duì)象的資源被正確釋放,并且新內(nèi)容被賦值給當(dāng)前對(duì)象
swap(tmp);
// 注:這里原本可能是 "this->swap(tmp);",但因?yàn)?"swap" 是成員函數(shù),
// 在成員函數(shù)內(nèi)部可以直接調(diào)用,無(wú)需 "this->"。
}
// 返回當(dāng)前對(duì)象的引用,以便支持連續(xù)賦值操作
return *this;
}
現(xiàn)代寫(xiě)法優(yōu)化
//現(xiàn)代寫(xiě)法優(yōu)化
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
string的無(wú)參構(gòu)造函數(shù):
經(jīng)過(guò)嘗試之后,可以發(fā)現(xiàn)以下問(wèn)題:
無(wú)參的構(gòu)造應(yīng)該這樣寫(xiě):
代碼實(shí)現(xiàn): 建議只寫(xiě)一個(gè)全缺省的構(gòu)造函數(shù)即可,因?yàn)槟J(rèn)構(gòu)造函數(shù)只能存在一個(gè)。
String()
:_str(new char[1]{'\0'})//數(shù)組的初始化方式
,_size(0)
,_capacity(0)
{}
遍歷函數(shù)
c_str函數(shù)
簡(jiǎn)要說(shuō)明
const char* c_str()const
{
return _str;
}
operator[ ]
這里要演示的話就得遍歷這個(gè)字符串,需要重載一下operator[ ]:
void test_string1()
{
string s1("hello world");
cout << s1.c_str() << endl;
string s2;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
}
需要區(qū)分只能讀和可讀可寫(xiě)的版本:
//可讀可寫(xiě)
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只能讀
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
代碼執(zhí)行:?
既然說(shuō)到遍歷數(shù)組的話,那就少不了迭代器。
迭代器
迭代器聲明:?
//聲明
typedef char* iterator;//可讀可寫(xiě)
typedef const char* const_iterator;//只可讀
迭代器的底層實(shí)現(xiàn)begin和end:
begin:函數(shù)的作用就是返回字符串中第一個(gè)字符的地址
//實(shí)現(xiàn):
iterator begin()
{
return _str;
}
const_iterator begin()const
{
return _str;
}
end函數(shù)的作用就是返回字符串中最后一個(gè)字符的后一個(gè)字符的地址(即’\0’的地址)
//實(shí)現(xiàn):
iterator end()
{
return _str + _size;//指向'\0'
}
const_iterator end()const
{
return _str + _size;//指向'\0'
}
迭代器外層調(diào)用:
范圍for的使用
同時(shí)為了方便理解,把范圍for的實(shí)現(xiàn)列舉出來(lái):
當(dāng)咱們將begin改為Begin時(shí),發(fā)現(xiàn)會(huì)出錯(cuò),還有一個(gè)點(diǎn):范圍for的本質(zhì)是迭代器
迭代器與范圍for的應(yīng)用:?
怎么能夠讓改變后的字符串,不再變回原來(lái)的字符串呢?使用引用即可:
實(shí)例代碼:
void test_string1()
{
string s1("hello world");
cout << s1.c_str() << endl;
string s2;
cout << s2.c_str() << endl;
/* for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;*/
string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)++;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& ch : s1)//使用引用
{
ch++;//讓字符串里每個(gè)字符的ascll碼值+1
cout << ch << " ";
}
cout << endl;
cout << s1.c_str() << endl;
}
修改字符串相關(guān)函數(shù):
reserve?
????????其主要作用是預(yù)先為容器分配足夠的未使用容量,以應(yīng)對(duì)未來(lái)可能的元素添加,而不立即改變?nèi)萜鞯拇笮。ㄔ財(cái)?shù)量)。使用?reserve()
?可以有策略地控制容器的內(nèi)存分配,提高連續(xù)添加元素時(shí)的效率
void reserve(size_t n)
{
// 檢查請(qǐng)求的容量n是否大于當(dāng)前內(nèi)部緩沖區(qū)的實(shí)際容量(_capacity)
if (n > _capacity)
{
// 如果是,則需要重新分配更大的內(nèi)存空間。新分配的內(nèi)存大小為n+1,+1用于存儲(chǔ)結(jié)尾的空字符'\0
char* tmp = new char[n + 1];//+1存“\0'
strcpy(tmp,_str);//將當(dāng)前字符串內(nèi)容(包括結(jié)尾的'\0')復(fù)制到新分配的tmp緩沖區(qū)中
delete[] _str;// 釋放原有的內(nèi)部緩沖區(qū)(_str),以避免內(nèi)存泄漏
_str = tmp;// 更新內(nèi)部緩沖區(qū)指針,指向新分配的tmp緩沖區(qū)
_capacity = n;// 更新內(nèi)部記錄的緩沖區(qū)容量為請(qǐng)求的值n
}
}
push_back
? push_back()
?是序列容器(如?std::vector
,?std::list
,?std::deque
?等)的成員函數(shù),用于在容器末尾添加新元素。它自動(dòng)處理內(nèi)存管理,確保新元素順利加入,且保持原有元素順序不變。對(duì)于動(dòng)態(tài)擴(kuò)容容器(如?std::vector
),在必要時(shí)會(huì)自動(dòng)增大容量。?
嘗試:可以寫(xiě)成這樣嗎?不行,當(dāng)_capacity = 0 時(shí),空間還沒(méi)分配到,此時(shí)如果訪問(wèn)就屬于越界訪問(wèn)。
代碼實(shí)現(xiàn):
void push_back(char ch)
{
// 檢查當(dāng)前內(nèi)部字符串長(zhǎng)度(_size)是否已達(dá)到內(nèi)部緩沖區(qū)容量(_capacity)
if (_size == _capacity)
{
// 若已滿(mǎn),調(diào)用reserve函數(shù)預(yù)分配新的內(nèi)存,擴(kuò)大容量。
// 首次擴(kuò)容時(shí)容量設(shè)為4,后續(xù)擴(kuò)容按當(dāng)前容量翻倍。
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
// 將待添加字符ch 存儲(chǔ)在 內(nèi)部字符串緩沖區(qū)的當(dāng)前長(zhǎng)度索引處(_size)
_str[_size] = ch;
++_size;// 更新內(nèi)部字符串長(zhǎng)度(_size),增1以包含新添加的字符
_str[_size] = '\0';// 在新添加字符之后添加空字符('\0'),確保字符串正確終止
}
append
對(duì)于std::string:
append()用于在字符串末尾追加其他字符串、字符數(shù)組或單個(gè)字符,可以指定追加的起始位置和長(zhǎng)度。
void append(const char* str)
{
int len = strlen(str);// 獲取輸入字符串str長(zhǎng)度
// 檢查是否需擴(kuò)大內(nèi)部緩沖區(qū)容量以容納追加的str
if (_size + len > _capacity)
{
reserve(_size + len);
}
//這個(gè)地方要是忘記 +_size,從頭到尾覆蓋新字符串
strcpy(_str+_size,str);// 將str追加到內(nèi)部字符串緩沖區(qū)末尾
_size += len;// 更新內(nèi)部字符串長(zhǎng)度
}
push_back 和 append 的區(qū)別在于一個(gè)追加字符,另一個(gè)追加字符串
示例:
operator+=
+=運(yùn)算符的重載:分別復(fù)用了 push_back? 和? append,?實(shí)現(xiàn)字符串與字符,字符串與字符串之間能夠直接使用+=運(yùn)算符進(jìn)行尾插。
字符串追加字符:
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
字符串追加字符串:
string& operator+=(const char* str)
{
append(str);
return *this;
}
示例運(yùn)行:
void test_string2 ()
{
string s1("hello world");
s1 += '#';
s1 += "css";
cout << s1.c_str() << endl;
string s2;
s2 += '#';
s2 += "csgo";
cout << s2.c_str() << endl;
}
insert(字符的版本)
嘗試寫(xiě)一下insert:
測(cè)試一下代碼的可行性:
?可以發(fā)現(xiàn)頭插會(huì)出錯(cuò):
頭插:
那就換成有符號(hào),此時(shí)pos為0,end為-1,此時(shí)end<pos,但是依然進(jìn)入了循環(huán),明顯的不對(duì)吧,這里是發(fā)生了類(lèi)型轉(zhuǎn)換,end變成無(wú)符號(hào)整型了。
怎么解決呢:以下兩種寫(xiě)法都是正確的:
此時(shí)函數(shù)的功能:
在動(dòng)態(tài)字符數(shù)組?
_str?
的指定位置?pos?
插入一個(gè)字符?ch
。首先,它驗(yàn)證插入位置的有效性(必須小于等于當(dāng)前數(shù)組大小_size
)。若當(dāng)前數(shù)組容量已滿(mǎn),函數(shù)會(huì)自動(dòng)擴(kuò)容,初始容量為4或者當(dāng)前容量的兩倍。
然后,函數(shù)通過(guò)循環(huán)將?
pos
位置之后的所有元素向右移動(dòng)一位,為新字符騰出空間。最后,函數(shù)在指定位置
pos
插入字符ch
,并將數(shù)組大小_size
加一,表示數(shù)組元素?cái)?shù)量增加。?
//上圖的版本二
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
insert(字符串常量)
該函數(shù)的作用:
????????用于在動(dòng)態(tài)字符數(shù)組(或類(lèi)似字符串結(jié)構(gòu))的指定位置
pos
插入一個(gè)C風(fēng)格字符串str
。首先,它通過(guò)斷言確保插入位置的有效性。????????接著計(jì)算待插入字符串的長(zhǎng)度,并檢查現(xiàn)有容量是否足夠容納插入后的完整字符串,不足則調(diào)用
reserve
進(jìn)行擴(kuò)容。????????然后,函數(shù)將插入位置之后的所有字符向右移動(dòng)適當(dāng)距離,為新字符串騰出空間。最后,使用
strncpy
函數(shù)將待插入字符串復(fù)制到目標(biāo)位置,并更新整個(gè)字符串的大小,反映出新增字符的影響。
首先要注意:
所以需要強(qiáng)轉(zhuǎn):
// pos:插入位置,從0開(kāi)始計(jì)數(shù)
// str:要插入的C風(fēng)格字符串
void insert(size_t pos, const char* str)
{
// 斷言檢查,確保插入位置pos不大于當(dāng)前字符串的大小
assert(pos <= _size);
// 獲取要插入字符串str的長(zhǎng)度,即需要移動(dòng)的字符數(shù)量
size_t len = strlen(str);
// 檢查插入后字符串總長(zhǎng)度是否超過(guò)當(dāng)前容量,如果超過(guò)則進(jìn)行擴(kuò)容
if (_size + len > _capacity)
{
reserve(_size + len); // 調(diào)用reserve函數(shù)來(lái)增加內(nèi)部緩沖區(qū)的容量
}
// 數(shù)據(jù)挪動(dòng)部分:
// 將插入位置pos之后的所有字符向右移動(dòng)len個(gè)位置
int end = static_cast<int>(_size); // 使用int類(lèi)型方便進(jìn)行減操作,注意這里假設(shè)_size不會(huì)超出int表示范圍
while (end >= static_cast<int>(pos))
{
_str[end + len] = _str[end]; // 將原字符串中下標(biāo)為end的字符移動(dòng)到end + len的位置
--end; // 移動(dòng)指針至下一個(gè)待移動(dòng)的字符
}
// 在指定位置pos處插入字符串str
strncpy(_str + pos, str, len); // 使用 strncpy 函數(shù)將str復(fù)制到目標(biāo)字符串相應(yīng)位置
// 更新字符串的大小
_size += len; // 插入操作完成后,字符串的新長(zhǎng)度應(yīng)增加len
}
erase
? ? ? ? 作用是從動(dòng)態(tài)字符數(shù)組(或類(lèi)字符串結(jié)構(gòu))的特定位置
pos
開(kāi)始刪除指定長(zhǎng)度len
的子串。????????首先,它通過(guò)斷言確保刪除起始位置的有效性(小于當(dāng)前字符串長(zhǎng)度
_size
)。根據(jù)傳入的參數(shù),函數(shù)會(huì)判斷是否需要?jiǎng)h除從pos
到結(jié)尾的所有字符,????????如果是,則將
pos
位置之后的字符置為結(jié)束符\0
,并將字符串大小更新為pos
;否則,將從pos+len
開(kāi)始到結(jié)尾的字符依次向前移動(dòng)len
個(gè)位置以覆蓋待刪除區(qū)域,并相應(yīng)減少字符串大小_size
。????????簡(jiǎn)而言之,該函數(shù)實(shí)現(xiàn)了對(duì)字符串的指定范圍擦除操作。
// 函數(shù):刪除指定位置起始的子串,若未指定長(zhǎng)度,默認(rèn)刪除到字符串末尾
void erase(size_t pos, size_t len = npos)
{
// 檢查刪除起始位置是否合法(必須在當(dāng)前字符串內(nèi))
assert(pos < _size);
// 如果未指定長(zhǎng)度或指定長(zhǎng)度使刪除范圍超出字符串末尾,則刪除從pos到字符串末尾的部分
if (len == npos || pos+len >= _size)
{
// 截?cái)嘧址瑢os位置置空字符,字符串長(zhǎng)度變?yōu)閜os
_str[pos] = '\0';
_size = pos;
}
// 否則,按指定長(zhǎng)度刪除子串
else
{
// 計(jì)算刪除范圍結(jié)束后的新起始位置
size_t begin = pos + len;
// 將刪除范圍后的字符逐個(gè)向前移動(dòng)len位,覆蓋待刪除部分
while (start <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
// 更新字符串長(zhǎng)度,減少len
_size -= len;
}
}
resize
作用: 改變動(dòng)態(tài)字符數(shù)組(或類(lèi)字符串結(jié)構(gòu))的大小,并可選地用指定字符
ch
填充新添加的空間。????????若新指定長(zhǎng)度
n
小于等于當(dāng)前長(zhǎng)度,函數(shù)將截?cái)嘧址?,使其長(zhǎng)度變?yōu)?code>n;????????若新長(zhǎng)度大于當(dāng)前長(zhǎng)度且可能超過(guò)當(dāng)前容量時(shí),函數(shù)先調(diào)用
reserve
進(jìn)行擴(kuò)容,然后使用給定字符ch
填充新增的字符位置,直到字符串長(zhǎng)度達(dá)到指定的n
,并在字符串末尾添加結(jié)束符\0
,以確保字符串的完整性和正確性。????????總之,此函數(shù)靈活地調(diào)整了字符串的長(zhǎng)度,并保持其內(nèi)容的有效性。
void resize(size_t n, char ch = '\0')
{
// 當(dāng)新長(zhǎng)度n小于等于當(dāng)前字符串長(zhǎng)度時(shí),執(zhí)行刪除操作
if (n <= _size)
{
// 將第n個(gè)字符設(shè)置為空字符,相當(dāng)于截?cái)嘧址⒏伦址膶?shí)際長(zhǎng)度為n
_str[n] = '\0';
_size = n;
}
// 當(dāng)新長(zhǎng)度n大于當(dāng)前字符串長(zhǎng)度但小于等于當(dāng)前容量時(shí),或新長(zhǎng)度n大于當(dāng)前容量時(shí)
else
{
// 先調(diào)用reserve函數(shù)確保容量足夠容納新長(zhǎng)度的字符串
reserve(n);
// 循環(huán)填充字符,直至字符串長(zhǎng)度達(dá)到n
while (_size < n)
{
_str[_size] = ch; // 使用指定字符ch填充
++_size;
}
// 最后,在新字符串末尾添加結(jié)束符'\0'
_str[_size] = '\0';
}
}
find(查找單個(gè)字符)
// 定義一個(gè)成員方法,用于在當(dāng)前字符串對(duì)象中查找指定字符 ch 的首次出現(xiàn)位置
//ch -- 要查找的字符
// 查找的起始位置,默認(rèn)從字符串開(kāi)頭(索引0)開(kāi)始
size_t find(char ch, size_t pos = 0)
{
// 使用一個(gè)循環(huán)遍歷從 pos 位置開(kāi)始到字符串結(jié)尾的所有字符
for (size_t i = pos; i < _size; i++)
{
// 檢查當(dāng)前遍歷到的字符是否與要查找的字符 ch 相同
if (_str[i] == ch)
{
// 如果相同,則返回該字符在字符串中的索引位置
return i;
}
}
// 若遍歷完整個(gè)指定區(qū)間后仍未找到字符 ch,則返回 npos
// npos 是一個(gè)特殊的值,通常表示“未找到”或“無(wú)效位置”
return npos;
}
find(查找子串)
size_t find(const char* sub, size_t pos = 0)
{
// 使用C語(yǔ)言庫(kù)函數(shù)strstr查找子串sub在當(dāng)前字符串(從pos開(kāi)始的部分)中首次出現(xiàn)的位置
const char* p = strstr(_str + pos, sub);
// 如果找到了子串sub,則返回子串首字符在當(dāng)前字符串中的相對(duì)索引(即下標(biāo))
if (p)
{
return p - _str;
}
// 若找不到子串,則返回特殊值npos,表示子串不在當(dāng)前字符串中
else
{
return npos;
}
}
substr
// 定義一個(gè)方法,用于從原始字符串中提取子串
string substr(size_t pos, size_t len /* 默認(rèn)為最大長(zhǎng)度 */) {
// 創(chuàng)建一個(gè)新字符串 s 來(lái)保存子串
string s;
// 計(jì)算子串的結(jié)束位置(默認(rèn)取到原始字符串結(jié)尾)
size_t end = pos + len;
if (len == npos || pos + len >= _size) {
// 若指定長(zhǎng)度超過(guò)原始字符串剩余長(zhǎng)度,則取剩余全部字符
len = _size - pos;
end = _size;
}
// 為新字符串預(yù)分配足夠內(nèi)存以存放子串
s.reserve(len);
// 循環(huán)遍歷原始字符串,從 pos 位置開(kāi)始,復(fù)制到新字符串 s 中,直到結(jié)束位置 end
for (size_t i = pos; i < end; i++) {
s += _str[i];
}
// 返回包含子串的新字符串 s
return s;
}
比較運(yùn)算符的重載:
bool operator<(const string& s)const
{
return strcmp(_str,s._str);
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str)==0;
}
//<= 復(fù)用<
bool operator<=(const string& s)const
{
return *this < s || *this == s;
}
//>復(fù)用 !(<=)
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);
}
流插入cout
這里的范圍for可以訪問(wèn)嗎?不可以:這里要用const迭代器,因?yàn)槭且粋€(gè)被const修飾的對(duì)象,const迭代器指針本身是可以++(修改)的,指針指向的內(nèi)容不可以被修改:
所以要這樣做:
代碼:
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;//const迭代器
}
ostream& operator<<(ostream& out, const string& s)
{
/* for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}*/
for (auto ch : s)
out << ch;
return out;
}
流提取cin
需要注意的地方:
那么應(yīng)該這樣去寫(xiě):
代碼:?
istream& operator>>(istream& in, string& s)
{
s.clear();//清理原來(lái)的字符數(shù)據(jù),不然就變成尾插了
char ch;
//in >> ch;//拿不到 空格 或者 換行
ch = in.get();//一個(gè)字符一個(gè)字符的拿
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
優(yōu)化寫(xiě)法
// 重載輸入流提取運(yùn)算符 >>,使得可以從輸入流(如cin)中讀取一串連續(xù)的非空格和非換行符字符到string對(duì)象s中
istream& operator>>(istream& in, string& s)
{
// 清空目標(biāo)string對(duì)象s,準(zhǔn)備接收新的輸入
s.clear();
// 定義一個(gè)固定大小的緩沖區(qū)buff,用于暫存從輸入流中讀取的字符
char buff[129];
size_t i = 0; // 初始化緩沖區(qū)索引為0
// 從輸入流in中獲取第一個(gè)字符
char ch;
ch = in.get();
// 當(dāng)讀取到的字符不是空格也不是換行符時(shí),繼續(xù)循環(huán)
while (ch != ' ' && ch != '\n')
{
// 將字符放入緩沖區(qū)
buff[i++] = ch;
// 如果緩沖區(qū)已滿(mǎn)(達(dá)到128個(gè)字符)
if (i == 128)
{
// 在緩沖區(qū)末尾添加結(jié)束符'\0',將其視為一個(gè)C風(fēng)格字符串添加到目標(biāo)string對(duì)象s中
buff[i] = '\0';
s += buff; // 更新s的內(nèi)容
// 重置緩沖區(qū)索引為0,以便繼續(xù)填充下一個(gè)子字符串
i = 0;
}
// 繼續(xù)從輸入流中獲取下一個(gè)字符
ch = in.get();
}
// 若緩沖區(qū)中仍有剩余字符(循環(huán)結(jié)束后)
if (i != 0)
{
// 添加結(jié)束符'\0',將剩余的子字符串添加到目標(biāo)string對(duì)象s中
buff[i] = '\0';
s += buff;
}
// 返回輸入流in的引用,以支持鏈?zhǔn)捷斎氩僮? return in;
}
clear
不清理之前的數(shù)據(jù)就變成尾插了:
void clear()
{
_str[0] = '\0';
_size = 0;
}
本篇結(jié)束。
??本文修改次數(shù):0文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-860934.html
??更新時(shí)間:2024年4 月 24 日?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-860934.html
到了這里,關(guān)于【C++初階】第八站:string類(lèi)的模擬實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!