1.為什么要模擬實(shí)現(xiàn)string
模擬實(shí)現(xiàn) std::string 是一個(gè)有挑戰(zhàn)性的練習(xí),它可以帶來多方面的收益,尤其對(duì)于學(xué)習(xí) C++ 和深入了解字符串操作以及動(dòng)態(tài)內(nèi)存管理的機(jī)制。以下是模擬實(shí)現(xiàn) std::string 的一些好處和重要意義:
- 學(xué)習(xí) C++ 內(nèi)存管理:std::string 是一個(gè)動(dòng)態(tài)分配內(nèi)存的容器,模擬實(shí)現(xiàn)需要手動(dòng)處理內(nèi)存的分配和釋放。這可以讓你更深入地理解動(dòng)態(tài)內(nèi)存管理的原理和機(jī)制,如何正確地使用 new 和 delete 運(yùn)算符,以及如何避免內(nèi)存泄漏和懸空指針。
- 字符串操作的練習(xí):在模擬實(shí)現(xiàn)過程中,您需要實(shí)現(xiàn)字符串的拼接、插入、刪除、查找等操作,以及其他與字符串處理相關(guān)的函數(shù)。這可以幫助您熟悉 C++ 中字符串的操作和處理方式。
- 深入理解類和對(duì)象:std::string 是一個(gè)類模板,模擬實(shí)現(xiàn)它需要深入理解類和對(duì)象的概念,包括構(gòu)造函數(shù)、析構(gòu)函數(shù)、成員函數(shù)、成員變量等。通過實(shí)現(xiàn)一個(gè)類似 std::string 的類,你可以更好地理解類的設(shè)計(jì)和使用。
- 提高編程技能:模擬實(shí)現(xiàn) std::string 是一項(xiàng)挑戰(zhàn)性的任務(wù),它可以鍛煉你的編程技能,讓你更加熟練地使用 C++ 的語法和特性。
- 深入學(xué)習(xí)模板編程:std::string 是一個(gè)類模板,模擬實(shí)現(xiàn)它可以幫助你深入了解模板編程的機(jī)制和技巧。
- 實(shí)現(xiàn)自定義容器:std::string 是 C++ 標(biāo)準(zhǔn)庫中的一個(gè)容器類,模擬實(shí)現(xiàn)它是實(shí)現(xiàn)自定義容器的練習(xí)。自定義容器可以幫助您更好地理解容器的設(shè)計(jì)和實(shí)現(xiàn)。
2.string的模擬實(shí)現(xiàn)需要注意哪些問題
模擬實(shí)現(xiàn) std::string 類是一個(gè)有挑戰(zhàn)性的任務(wù),因?yàn)?std::string 是 C++ 標(biāo)準(zhǔn)庫中的一個(gè)復(fù)雜數(shù)據(jù)類型,它有很多功能和特性,而其實(shí)現(xiàn)涉及到動(dòng)態(tài)內(nèi)存管理、字符串操作、復(fù)制語義等方面。在進(jìn)行模擬實(shí)現(xiàn)時(shí),需要注意以下一些關(guān)鍵問題:
- 內(nèi)存管理:std::string 類是一個(gè)動(dòng)態(tài)分配內(nèi)存的容器,模擬實(shí)現(xiàn)需要正確地處理內(nèi)存的分配和釋放。你可以使用動(dòng)態(tài)數(shù)組、指針或其他數(shù)據(jù)結(jié)構(gòu)來模擬動(dòng)態(tài)內(nèi)存的管理。
- 字符串操作:模擬實(shí)現(xiàn)需要支持字符串的拼接、插入、刪除、查找等操作,以及其他字符串處理的函數(shù)(如 size()、substr()、find() 等)。
- 異常處理:std::string 在一些情況下可能會(huì)引發(fā)異常,例如內(nèi)存分配失敗或訪問越界等。模擬實(shí)現(xiàn)需要考慮如何正確處理異常情況,以確保程序的穩(wěn)定性和安全性。
- 內(nèi)存拷貝:std::string 采用了深拷貝(deep copy)語義,即在復(fù)制時(shí)會(huì)復(fù)制整個(gè)字符串的內(nèi)容。模擬實(shí)現(xiàn)需要正確地處理內(nèi)存的拷貝,以避免懸空指針和資源泄漏等問題。
- 迭代器支持:std::string 支持迭代器用于訪問字符串的內(nèi)容,模擬實(shí)現(xiàn)需要提供相應(yīng)的迭代器支持。
- 性能優(yōu)化:std::string 的標(biāo)準(zhǔn)實(shí)現(xiàn)通常會(huì)對(duì)性能進(jìn)行優(yōu)化,例如采用了擴(kuò)容策略來減少頻繁的內(nèi)存分配。模擬實(shí)現(xiàn)可以考慮一些優(yōu)化策略,提高性能和效率。
- 邊界條件:在進(jìn)行模擬實(shí)現(xiàn)時(shí),需要特別注意邊界條件和特殊情況,確保實(shí)現(xiàn)的正確性和魯棒性。
- 完整性:std::string 類是一個(gè)非常復(fù)雜的數(shù)據(jù)類型,模擬實(shí)現(xiàn)時(shí)需要盡可能完整地實(shí)現(xiàn)其功能和接口。
雖然模擬實(shí)現(xiàn) std::string 是一個(gè)復(fù)雜的任務(wù),但它也是一個(gè)很好的學(xué)習(xí)練習(xí),可以加深對(duì) C++ 內(nèi)存管理、字符串處理等方面的理解。
3.經(jīng)典的string類問題
上一篇文章已經(jīng)對(duì)string類進(jìn)行了簡單的介紹,大家只要能夠正常使用即可。在面試中,面試官總喜歡讓學(xué)生自己來模擬實(shí)現(xiàn)string類,最主要是實(shí)現(xiàn)string類的構(gòu)造、拷貝構(gòu)造、賦值運(yùn)算符重載以及析構(gòu)函數(shù)。大家看下以下string類的實(shí)現(xiàn)是否有問題?
// 為了和標(biāo)準(zhǔn)庫區(qū)分,此處使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 錯(cuò)誤示范
//String(const char* str = nullptr) 錯(cuò)誤示范
String(const char* str = "")
{
// 構(gòu)造String類對(duì)象時(shí),如果傳遞nullptr指針,可以認(rèn)為程序非法
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 測試
void TestString()
{
String s1("hello bit!!!");
String s2(s1);
}
上述String類沒有顯式定義其拷貝構(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)致的問題是,s1、s2共用同一塊內(nèi)存空間,在釋放時(shí)同一塊空間被釋放多次而引起程序崩潰,這種拷貝方式,稱為淺拷貝。
什么是淺拷貝
淺拷貝:也稱位拷貝,編譯器只是將對(duì)象中的值拷貝過來。如果對(duì)象中管理資源,最后就會(huì)導(dǎo)致多個(gè)對(duì)象共享同一份資源,當(dāng)一個(gè)對(duì)象銷毀時(shí)就會(huì)將該資源釋放掉,而此時(shí)另一些對(duì)象不知道該資源已經(jīng)被釋放,以為還有效,所以當(dāng)繼續(xù)對(duì)資源進(jìn)項(xiàng)操作時(shí),就會(huì)發(fā)生發(fā)生了訪問違規(guī)。其實(shí)我們可以采用深拷貝解決淺拷貝問題,即:每個(gè)對(duì)象都有一份獨(dú)立的資源,不要和其他對(duì)象共享。
什么是深拷貝
深拷貝是指在進(jìn)行對(duì)象拷貝時(shí),不僅復(fù)制對(duì)象本身的成員變量,還復(fù)制對(duì)象所指向的動(dòng)態(tài)分配的資源(例如堆內(nèi)存)到新的對(duì)象中。這意味著拷貝后的對(duì)象和原對(duì)象擁有獨(dú)立的資源副本,彼此之間不會(huì)相互影響。
當(dāng)對(duì)象中含有動(dòng)態(tài)分配的資源,如指針指向的內(nèi)存塊,或者其他動(dòng)態(tài)分配的資源(文件句柄、網(wǎng)絡(luò)連接等),進(jìn)行深拷貝是非常重要的,以避免多個(gè)對(duì)象共享同一塊資源導(dǎo)致釋放重復(fù)、懸掛指針等問題。
如果一個(gè)類中涉及到資源的管理,其拷貝構(gòu)造函數(shù)、賦值運(yùn)算符重載以及析構(gòu)函數(shù)必須要顯式給出。一般情況都是按照深拷貝方式提供。
4.寫時(shí)拷貝
“寫時(shí)拷貝”(Copy on Write,簡稱為 COW)是一種優(yōu)化技術(shù),通常應(yīng)用于操作系統(tǒng)的內(nèi)存管理或數(shù)據(jù)結(jié)構(gòu)中,目的是節(jié)省內(nèi)存和提高性能。在 COW 中,當(dāng)多個(gè)對(duì)象共享同一份資源時(shí),只有在某個(gè)對(duì)象試圖修改資源內(nèi)容時(shí),才會(huì)進(jìn)行實(shí)際的拷貝操作,否則所有對(duì)象共享相同的原始資源。這樣可以避免在修改前對(duì)整個(gè)資源進(jìn)行拷貝,節(jié)省了內(nèi)存和執(zhí)行時(shí)間。
COW 最常見的應(yīng)用是在操作系統(tǒng)中的進(jìn)程管理和內(nèi)存分配。當(dāng)一個(gè)進(jìn)程 fork(復(fù)制)自身時(shí),通常會(huì)采用 COW 機(jī)制。在 fork 時(shí),子進(jìn)程會(huì)與父進(jìn)程共享相同的內(nèi)存空間,即物理頁框。只有當(dāng)子進(jìn)程或父進(jìn)程中的一個(gè)試圖修改其中的內(nèi)容時(shí),操作系統(tǒng)才會(huì)執(zhí)行實(shí)際的拷貝,將要修改的頁框內(nèi)容復(fù)制到新的頁框中,使得兩個(gè)進(jìn)程的內(nèi)存空間獨(dú)立開來。這樣,父子進(jìn)程可以共享大部分資源,而無需進(jìn)行大規(guī)模的內(nèi)存拷貝,從而提高了 fork 操作的效率。
在編程語言或數(shù)據(jù)結(jié)構(gòu)中,寫實(shí)拷貝也可以用于優(yōu)化數(shù)據(jù)結(jié)構(gòu)的復(fù)制操作。例如,在某些容器類(如字符串、數(shù)組、向量等)中,當(dāng)多個(gè)對(duì)象共享相同的數(shù)據(jù)時(shí),只有在其中一個(gè)對(duì)象試圖修改數(shù)據(jù)時(shí),才會(huì)進(jìn)行實(shí)際的拷貝操作,確保各個(gè)對(duì)象之間的數(shù)據(jù)相互獨(dú)立。
需要注意的是,COW 并不是適用于所有情況的通用優(yōu)化技術(shù),它的有效性取決于具體的應(yīng)用場景。在某些情況下,COW 可以帶來顯著的性能優(yōu)勢,但在其他情況下,可能會(huì)增加復(fù)雜性和開銷。因此,在實(shí)現(xiàn)時(shí)需要仔細(xì)權(quán)衡利弊,根據(jù)實(shí)際需求選擇合適的優(yōu)化策略。
其實(shí)寫時(shí)拷貝就是一種拖延癥,是在淺拷貝的基礎(chǔ)之上增加了引用計(jì)數(shù)的方式來實(shí)現(xiàn)的。
引用計(jì)數(shù):用來記錄資源使用者的個(gè)數(shù)。在構(gòu)造時(shí),將資源的計(jì)數(shù)給成1,每增加一個(gè)對(duì)象使用該資源,就給計(jì)數(shù)增加1,當(dāng)某個(gè)對(duì)象被銷毀時(shí),先給該計(jì)數(shù)減1,然后再檢查是否需要釋放資源,如果計(jì)數(shù)為1,說明該對(duì)象時(shí)資源的最后一個(gè)使用者,將該資源釋放;否則就不能釋放,因?yàn)檫€有其他對(duì)象在使用該資源。
一個(gè)常見的例子是字符串的寫實(shí)拷貝。
在許多編程語言中,字符串通常是不可變的(immutable),即一旦創(chuàng)建后,就無法修改其內(nèi)容。在這種情況下,當(dāng)多個(gè)變量或?qū)ο笠猛粋€(gè)字符串時(shí),如果其中一個(gè)變量試圖修改字符串的內(nèi)容,就需要?jiǎng)?chuàng)建一個(gè)新的字符串對(duì)象,而不是直接在原始字符串上進(jìn)行修改。
假設(shè)有兩個(gè)變量 str1 和 str2 都指向相同的字符串 “Hello”:
std::string str1 = "Hello";
std::string str2 = str1;
在這里,str2 是通過拷貝構(gòu)造函數(shù)從 str1 創(chuàng)建的。在傳統(tǒng)的拷貝情況下,這將導(dǎo)致整個(gè)字符串 “Hello” 的拷貝,即兩個(gè)變量 str1 和 str2 都指向不同的內(nèi)存地址,但其內(nèi)容是相同的。
但是,寫時(shí)拷貝可以優(yōu)化這種情況。在寫時(shí)拷貝中,當(dāng) str2 拷貝 str1 時(shí),并不會(huì)立即創(chuàng)建一個(gè)新的字符串副本。而是讓 str2 和 str1 共享同一個(gè)底層的字符串?dāng)?shù)據(jù)。只有當(dāng)其中一個(gè)字符串試圖修改其內(nèi)容時(shí),才會(huì)觸發(fā)實(shí)際的拷貝操作。
例如,如果現(xiàn)在對(duì) str2 進(jìn)行修改操作:
str2[0] = 'h'; // 修改第一個(gè)字符為小寫 'h'
在寫時(shí)拷貝機(jī)制下,會(huì)創(chuàng)建一個(gè)新的字符串 “hello”,然后 str2 的內(nèi)容指向新的字符串,而 str1 的內(nèi)容保持不變。這樣,兩個(gè)變量 str1 和 str2 仍然共享相同的底層數(shù)據(jù),但它們的內(nèi)容已經(jīng)不再相同。
寫時(shí)拷貝可以有效地節(jié)省內(nèi)存,尤其在字符串長期共享的情況下,避免了不必要的內(nèi)存復(fù)制。但在其他情況下,可能會(huì)增加復(fù)雜性和開銷。因此,在實(shí)現(xiàn)時(shí)需要仔細(xì)權(quán)衡利弊,根據(jù)實(shí)際需求選擇合適的優(yōu)化策略。
5.傳統(tǒng)版寫法的String類(參考)
class String
{
public:
String(const char* str = "")
{
// 構(gòu)造String類對(duì)象時(shí),如果傳遞nullptr指針,可以認(rèn)為程序非法
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
6.現(xiàn)代版寫法的String類(參考)
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(nullptr)
{
String strTmp(s._str);
swap(_str, strTmp._str);
}
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
/*
String& operator=(const String& s)
{
if(this != &s)
{
String strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
7.string類的模擬實(shí)現(xiàn)(講解)
根據(jù)上面提到的內(nèi)容和知識(shí),我們可以來實(shí)現(xiàn)string類框架和大部分的接口函數(shù),但在實(shí)際面試中,我們可能需要實(shí)現(xiàn)的功能并不多,所以我們這里只把最常見和常用的那些部分模擬實(shí)現(xiàn)。
7.1 命名空間string類的成員變量定義
namespace mystring
{
class string
{
public:
//...
private:
size_t _capacity;
size_t _size;
char* _str;
public:
// const static 語法特殊處理
// 直接可以當(dāng)成定義初始化
const static size_t npos = -1;
}
首先我們重新定義一個(gè)命名空間,防止和庫中的string類重定義,或者重新寫一個(gè)別的名字的string類也可以,類成員包括capacity,size和字符串str,npos定義成公有并初始化。
7.2 string類構(gòu)造函數(shù)
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
const char* str = “” 是構(gòu)造函數(shù)的默認(rèn)參數(shù)。默認(rèn)參數(shù)是在函數(shù)聲明中為函數(shù)參數(shù)提供默認(rèn)值的一種特性,它允許在調(diào)用函數(shù)時(shí),如果沒有提供相應(yīng)的參數(shù)值,就會(huì)使用默認(rèn)值作為參數(shù)的值,實(shí)際包含一個(gè)’\0’,分配足夠存儲(chǔ)字符串的內(nèi)存空間(_size + 1,其中 _size 是輸入字符串的長度),然后通過 strcpy 函數(shù)將輸入的 C 風(fēng)格字符串復(fù)制到 _str 成員變量中。
7.3 string類拷貝構(gòu)造函數(shù)
傳統(tǒng)寫法:
string(const string& s)
:_str(new char[s._capacity+1])
, _size(s._size)
, _capacity(s._capacity)
{
strcpy(_str, s._str);
}
現(xiàn)代寫法:
void swap(string& tmp)
{
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp); //this->swap(tmp);
}
第一段代碼中,拷貝構(gòu)造函數(shù)采用傳統(tǒng)的深拷貝方式。它首先分配了與源對(duì)象(s)相同大小的內(nèi)存空間(包括結(jié)尾的空字符),然后將源對(duì)象的內(nèi)容復(fù)制到新分配的內(nèi)存空間中。
這種實(shí)現(xiàn)方式確保了新創(chuàng)建的對(duì)象和源對(duì)象具有獨(dú)立的內(nèi)存空間,即它們不共享資源。這樣,當(dāng)一個(gè)對(duì)象修改其內(nèi)容時(shí),不會(huì)影響到另一個(gè)對(duì)象,從而保證了對(duì)象之間的數(shù)據(jù)隔離。
而在第二段代碼中,拷貝構(gòu)造函數(shù)使用了 C++11 引入的移動(dòng)語義。它先創(chuàng)建了一個(gè)名為 tmp 的臨時(shí)對(duì)象,并使用 s._str 初始化了這個(gè)臨時(shí)對(duì)象。接著,通過調(diào)用成員函數(shù) swap(tmp) 將當(dāng)前對(duì)象的成員和臨時(shí)對(duì)象的成員進(jìn)行交換。
swap 函數(shù)的實(shí)現(xiàn)會(huì)使當(dāng)前對(duì)象的成員指向臨時(shí)對(duì)象的內(nèi)存空間,而臨時(shí)對(duì)象的成員指向了當(dāng)前對(duì)象之前的內(nèi)存空間。這樣一來,原來的資源被交換了,臨時(shí)對(duì)象會(huì)在析構(gòu)時(shí)釋放當(dāng)前對(duì)象原來的資源,而當(dāng)前對(duì)象則擁有了 s 對(duì)象的資源。
這種實(shí)現(xiàn)方式通過避免了不必要的內(nèi)存拷貝,從而提高了拷貝構(gòu)造函數(shù)的性能。在 tmp 作為臨時(shí)對(duì)象被析構(gòu)時(shí),它會(huì)自動(dòng)釋放原來 s 對(duì)象的資源,因此沒有內(nèi)存泄漏。
兩種實(shí)現(xiàn)方式都是有效的拷貝構(gòu)造函數(shù),但第二種實(shí)現(xiàn)利用了移動(dòng)語義,可以在拷貝對(duì)象時(shí)避免不必要的內(nèi)存復(fù)制,提高性能。在 C++11 及以上版本中,推薦使用第二種實(shí)現(xiàn)方式。
7.4 string類賦值運(yùn)算符重載
傳統(tǒng)寫法:
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
現(xiàn)代寫法:
string& operator=(string s)
{
swap(s);
return *this;
}
在第一個(gè)函數(shù)中,賦值運(yùn)算符采用了傳統(tǒng)的深拷貝方式。它首先檢查目標(biāo)對(duì)象與源對(duì)象是否是同一個(gè)對(duì)象(地址比較),如果是同一個(gè)對(duì)象則不執(zhí)行賦值操作,避免了自賦值的情況。
然后,它創(chuàng)建一個(gè)臨時(shí)的 string 對(duì)象 tmp,并將源對(duì)象 s 的內(nèi)容復(fù)制到 tmp 中。接著,通過調(diào)用 swap(tmp),將當(dāng)前對(duì)象的成員和臨時(shí)對(duì)象的成員進(jìn)行交換。
這樣,原來的資源被交換了,當(dāng)前對(duì)象擁有了 s 對(duì)象的資源,而臨時(shí)對(duì)象在析構(gòu)時(shí)會(huì)自動(dòng)釋放當(dāng)前對(duì)象原來的資源。這樣實(shí)現(xiàn)了賦值操作,并在賦值時(shí)避免了不必要的內(nèi)存拷貝。
在第二個(gè)函數(shù)中,賦值運(yùn)算符使用了 C++11 引入的移動(dòng)語義。它的參數(shù)是一個(gè) string 對(duì)象 s,這里采用了按值傳遞,即通過值拷貝的方式傳遞參數(shù)。
在函數(shù)內(nèi)部,它直接通過 swap(s) 將當(dāng)前對(duì)象的成員和參數(shù) s 對(duì)象的成員進(jìn)行交換。由于參數(shù) s 是按值傳遞的,意味著在調(diào)用函數(shù)時(shí)會(huì)執(zhí)行一次拷貝構(gòu)造函數(shù)來創(chuàng)建 s 對(duì)象的副本,因此在 swap(s) 中,將 s 對(duì)象的資源交換給了當(dāng)前對(duì)象,同時(shí)臨時(shí)對(duì)象 s 會(huì)在函數(shù)結(jié)束時(shí)自動(dòng)析構(gòu)并釋放當(dāng)前對(duì)象原來的資源。
這樣,通過移動(dòng)語義實(shí)現(xiàn)了賦值操作,并在賦值時(shí)避免了不必要的內(nèi)存復(fù)制。
區(qū)別總結(jié):
參數(shù)傳遞方式:第一個(gè)函數(shù)采用了常量引用傳遞,而第二個(gè)函數(shù)采用了按值傳遞。
拷貝控制技術(shù):第一個(gè)函數(shù)使用了深拷貝和交換資源的方式,而第二個(gè)函數(shù)利用了移動(dòng)語義和 swap 操作來避免拷貝。
兩者都能正確實(shí)現(xiàn)賦值操作,并避免了不必要的內(nèi)存拷貝。然而,第二個(gè)函數(shù)在 C++11 及以上版本中更推薦,因?yàn)樗昧艘苿?dòng)語義,性能更高效。如果你的代碼環(huán)境支持 C++11 或更高版本,建議優(yōu)先考慮使用第二種實(shí)現(xiàn)方式。
7.5 string類析構(gòu)函數(shù)和易實(shí)現(xiàn)的成員函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
這里的析構(gòu)函數(shù)通過 delete[] 操作釋放 _str 指向的動(dòng)態(tài)分配的字符數(shù)組(字符串內(nèi)存),然后將 _str 置為 nullptr,同時(shí)將 _size 和 _capacity 設(shè)置為 0。這樣確保對(duì)象被銷毀時(shí)內(nèi)存得到正確的釋放,防止內(nèi)存泄漏。
const char* c_str() const
{
return _str;
}
c_str()函數(shù)用于返回指向存儲(chǔ)字符串的字符數(shù)組的指針。
size_t size() const
{
return _size;
}
size()函數(shù)用于返回字符串的大小,即字符串中實(shí)際存儲(chǔ)的字符個(gè)數(shù),返回類型為 size_t。這里的 _size 成員變量表示實(shí)際存儲(chǔ)的字符個(gè)數(shù),因此直接返回 _size 即可。
size_t capacity() const
{
return _capacity;
}
capacity()函數(shù)用于返回字符串的容量,即字符串中當(dāng)前分配的內(nèi)存空間大小,返回類型為 size_t。這里的 _capacity 成員變量表示當(dāng)前的容量,因此直接返回 _capacity 即可。
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
operator[](size_t pos) const是 const 版本的下標(biāo)操作符重載函數(shù),用于訪問字符串中指定位置 pos 處的字符。函數(shù)返回類型為 const char&,表示返回的是常量字符的引用,即不允許通過該引用修改字符內(nèi)容。這是為了確保字符串的不可變性。
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
operator[](size_t pos)是非 const 版本的下標(biāo)操作符重載函數(shù),功能與上面的 const 版本類似,但這個(gè)函數(shù)返回類型是 char&,表示返回的是可修改字符的引用,允許通過該引用修改字符內(nèi)容。
void clear()
{
_str[0] = '\0';
_size = 0;
}
clear 函數(shù)用于清空字符串,即將字符串內(nèi)容全部置為空,并將實(shí)際大小 _size 設(shè)為 0,將字符數(shù)組的第一個(gè)字符(即字符串的起始位置)設(shè)置為空字符 ‘\0’,以將字符串內(nèi)容清空。
7.6 string類reserve函數(shù)
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
`void reserve(size_t n):這是 std::string 類中的 reserve 函數(shù)的聲明,表示該函數(shù)將預(yù)留 n 個(gè)字符的內(nèi)存空間。n 是傳入的參數(shù),表示需要預(yù)留的字符個(gè)數(shù)。
if (n > _capacity)
:這里通過比較傳入的 n 和當(dāng)前字符串的容量 _capacity,來判斷是否需要增加字符串的容量。只有當(dāng)需要預(yù)留的字符個(gè)數(shù) n 大于當(dāng)前容量 _capacity 時(shí),才需要進(jìn)行內(nèi)存擴(kuò)展操作。char* tmp = new char[n + 1];
:如果需要增加容量,首先創(chuàng)建一個(gè)新的字符數(shù)組 tmp,長度為 n + 1,即預(yù)留的字符個(gè)數(shù)加上結(jié)尾的空字符。這里將字符串的容量設(shè)置為 n,是為了預(yù)留額外的一個(gè)位置來存儲(chǔ)結(jié)尾的空字符。strcpy(tmp, _str);
:將原來的字符串內(nèi)容復(fù)制到新創(chuàng)建的字符數(shù)組 tmp 中。delete[] _str;
:釋放原來字符串 _str 指向的動(dòng)態(tài)分配的字符數(shù)組,即釋放原來的內(nèi)存空間。_str = tmp;
:將原來的指針 _str 指向新的字符數(shù)組 tmp,這樣字符串的內(nèi)存空間得到了擴(kuò)展。_capacity = n;
:將 _capacity 更新為新的容量 n。
這樣,當(dāng)需要預(yù)留更多的內(nèi)存空間時(shí),reserve 函數(shù)會(huì)創(chuàng)建一個(gè)新的字符數(shù)組,并將原來的字符串內(nèi)容復(fù)制到新數(shù)組中,然后釋放原來的內(nèi)存空間,并將 _str 指向新的字符數(shù)組,更新容量 _capacity 為新的預(yù)留值 n。
7.7 string類resize函數(shù)
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
// 插入數(shù)據(jù)
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else
{
// 刪除數(shù)據(jù)
_str[n] = '\0';
_size = n;
}
}
resize 函數(shù)用于改變字符串的大小,即增加或減少字符串中的字符個(gè)數(shù)。這里簡單解釋一下這個(gè)函數(shù)的實(shí)現(xiàn):
void resize(size_t n, char ch = '\0')
:這是 std::string 類中的 resize 函數(shù)的聲明,表示該函數(shù)將改變字符串的大小為 n。n 是傳入的參數(shù),表示新的字符串大小。參數(shù) ch 是可選的,默認(rèn)值為 ‘\0’,用于在擴(kuò)展字符串大小時(shí)填充新增的字符。if (n > _size)
:在這個(gè)條件分支中,判斷需要增加字符串大小的情況。如果傳入的新大小 n 大于當(dāng)前字符串大小 _size,表示需要在字符串末尾添加新的字符。reserve(n);
:首先調(diào)用 reserve 函數(shù)來預(yù)留足夠的內(nèi)存空間,確保字符串有足夠的容量來容納新增的字符。for (size_t i = _size; i < n; ++i)
:然后在字符串中新增的位置,從當(dāng)前字符串的大小 _size 開始循環(huán)添加字符。這里將新增的字符都設(shè)置為 ch,即傳入的第二個(gè)參數(shù)。_str[n] = '\0';
:在循環(huán)結(jié)束后,將字符串的新末尾字符設(shè)置為空字符 ‘\0’,保證新的字符串正確終止。_size = n;
:最后將字符串的大小 _size 更新為新的大小 n。else
:在這個(gè)條件分支中,處理需要減小字符串大小的情況。如果傳入的新大小 n 小于當(dāng)前字符串大小 _size,表示需要?jiǎng)h除字符串中多余的字符。_str[n] = '\0';
:將字符串的新末尾字符設(shè)置為空字符 ‘\0’,保證新的字符串正確終止。_size = n;
:最后將字符串的大小 _size 更新為新的大小 n。
這樣,resize 函數(shù)可以根據(jù)傳入的大小 n,擴(kuò)展或縮小字符串的大小,并在必要時(shí)添加或刪除字符。
7.8 string類insert函數(shù)、append函數(shù)、push_back函數(shù)、+=重載
insert函數(shù)
insert的模擬實(shí)現(xiàn)主要實(shí)現(xiàn)字符和字符串插入兩種
字符插入
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;
}
insert 函數(shù)在字符串中指定位置插入一個(gè)字符。這里簡單解釋一下這個(gè)函數(shù)的實(shí)現(xiàn):
string& insert(size_t pos, char ch)
:這是 std::string 類中的 insert 函數(shù)的聲明,表示該函數(shù)將在指定位置 pos 插入字符 ch。pos 是傳入的參數(shù),表示插入位置的索引;ch 是要插入的字符。assert(pos <= _size);
:使用 assert 斷言來確保插入位置 pos 不超過字符串的實(shí)際大小 _size。如果斷言失?。╬os 大于 _size),則會(huì)觸發(fā)斷言失敗錯(cuò)誤,幫助調(diào)試找到錯(cuò)誤的位置。if (_size == _capacity)
:檢查當(dāng)前字符串是否已滿(即 _size 等于 _capacity)。如果字符串已滿,則需要擴(kuò)容,以確保有足夠的容量來插入新字符。這里使用 reserve 函數(shù)擴(kuò)容,使字符串有足夠的容量來容納新字符。size_t end = _size + 1;
:在插入字符前,先將字符串的末尾位置(實(shí)際字符個(gè)數(shù) _size 后面)向后移動(dòng)一個(gè)位置,為新字符留出空間。這樣做是為了將插入位置 pos 之后的字符后移。while (end > pos)
:通過一個(gè)循環(huán),將插入位置 pos 之后的字符依次向后移動(dòng)一個(gè)位置。_str[pos] = ch;
:將字符 ch 插入到指定的插入位置 pos。++_size;
:插入字符后,將字符串的實(shí)際大小 _size 增加 1。return *this;
:返回當(dāng)前 std::string 對(duì)象的引用,以支持鏈?zhǔn)秸{(diào)用。
字符串插入
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 挪動(dòng)數(shù)據(jù)
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
與上一個(gè) insert 函數(shù)相比,這里的參數(shù) str 是一個(gè) C-style 字符串(const char*),而不是一個(gè)單個(gè)字符。函數(shù)的功能是在字符串中指定位置插入一個(gè) C-style 字符串?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
string& insert(size_t pos, const char* str)
:這是 std::string 類中的 insert 函數(shù)的聲明,表示該函數(shù)將在指定位置 pos 插入一個(gè) C-style 字符串 str。pos 是傳入的參數(shù),表示插入位置的索引;str 是要插入的 C-style 字符串。assert(pos <= _size);
:使用 assert 斷言來確保插入位置 pos 不超過字符串的實(shí)際大小 _size。如果斷言失?。╬os 大于 _size),則會(huì)觸發(fā)斷言失敗錯(cuò)誤,幫助調(diào)試找到錯(cuò)誤的位置。size_t len = strlen(str);
:計(jì)算要插入的 C-style 字符串 str 的長度,即字符個(gè)數(shù)。if (_size + len > _capacity)
:檢查插入后的字符串大小是否超過當(dāng)前的容量 _capacity,如果超過,則需要擴(kuò)容,以確保有足夠的容量來容納插入的字符串。reserve(_size + len);
:調(diào)用 reserve 函數(shù)來擴(kuò)容,保證有足夠的容量來容納插入的字符串。size_t end = _size + len;
:在插入字符串前,先將字符串的末尾位置(實(shí)際字符個(gè)數(shù) _size 后面)向后移動(dòng) len 個(gè)位置,為新字符串留出空間。while (end >= pos + len)
:通過一個(gè)循環(huán),將插入位置 pos 之后的字符依次向后移動(dòng) len 個(gè)位置,為新字符串的插入留出空間。strncpy(_str + pos, str, len);
:使用 strncpy 函數(shù)將 C-style 字符串 str 復(fù)制到指定的插入位置 pos,并且只復(fù)制 len 個(gè)字符。_size += len;
:插入字符串后,將字符串的實(shí)際大小 _size 增加 len,以反映插入后的新大小。return *this;
:返回當(dāng)前 std::string 對(duì)象的引用,以支持鏈?zhǔn)秸{(diào)用。
append函數(shù)
void append(const char* str)
{
size_t len = strlen(str);
// 滿了就擴(kuò)容
if (_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str + _size, str);
//strcat(_str, str); 需要找\0,效率低
_size += len;
}
append 函數(shù)用于在字符串末尾添加一個(gè) C-style 字符串?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
void append(const char* str)
:這是 std::string 類中的 append 函數(shù)的聲明,表示該函數(shù)將在字符串末尾添加一個(gè) C-style 字符串 str。str 是傳入的參數(shù),表示要添加的 C-style 字符串。size_t len = strlen(str);
:計(jì)算要添加的 C-style 字符串 str 的長度,即字符個(gè)數(shù)。if (_size + len > _capacity)
:檢查添加后的字符串大小是否超過當(dāng)前的容量 _capacity,如果超過,則需要擴(kuò)容,以確保有足夠的容量來容納添加的字符串。reserve(_size + len);
:調(diào)用 reserve 函數(shù)來擴(kuò)容,保證有足夠的容量來容納添加的字符串。strcpy(_str + _size, str);
:使用 strcpy 函數(shù)將 C-style 字符串 str 復(fù)制到字符串末尾,即從 _str 的實(shí)際字符個(gè)數(shù) _size 處開始復(fù)制。_size += len;
:添加字符串后,將字符串的實(shí)際大小 _size 增加 len,以反映添加后的新大小。
這樣,append 函數(shù)將 C-style 字符串 str 添加到字符串末尾,并且在必要時(shí)進(jìn)行了內(nèi)存擴(kuò)容。
當(dāng)然你可以對(duì)insert函數(shù)復(fù)用
void append(const char* str)
{
insert(_size, str);
}
push_back函數(shù)
void push_back(char ch)
{
// 滿了就擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
push_back 函數(shù)用于在字符串末尾添加一個(gè)字符?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
void push_back(char ch)
:這是 std::string 類中的 push_back 函數(shù)的聲明,表示該函數(shù)將在字符串末尾添加一個(gè)字符 ch。ch 是傳入的參數(shù),表示要添加的字符。if (_size == _capacity)
:檢查當(dāng)前字符串是否已滿(即 _size 等于 _capacity)。如果字符串已滿,則需要擴(kuò)容,以確保有足夠的容量來容納新增的字符。這里使用 reserve 函數(shù)擴(kuò)容,使字符串有足夠的容量來容納新字符。_str[_size] = ch;
:將字符 ch 添加到字符串末尾,即在 _str 的實(shí)際字符個(gè)數(shù) _size 處添加字符。++_size;
:字符串的實(shí)際大小 _size 增加 1,以反映添加后的新大小。_str[_size] = '\0';
:在字符串末尾添加一個(gè)空字符 ‘\0’,以保證新的字符串正確終止。
這樣,push_back 函數(shù)將字符 ch 添加到字符串末尾,并在必要時(shí)進(jìn)行了內(nèi)存擴(kuò)容。
同樣的,push_back 函數(shù)你也可以對(duì)insert函數(shù)復(fù)用
void push_back(char ch)
{
insert(_size, ch);
}
+=重載
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
operator+= 運(yùn)算符重載用于在現(xiàn)有字符串后追加字符或 C-style 字符串?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
string& operator+=(char ch)
:這是 operator+= 運(yùn)算符重載的第一個(gè)版本,表示該運(yùn)算符將在字符串末尾追加一個(gè)字符 ch。在這個(gè)版本中,直接調(diào)用了 push_back 函數(shù),將字符 ch 添加到字符串末尾。string& operator+=(const char* str)
:這是 operator+= 運(yùn)算符重載的第二個(gè)版本,表示該運(yùn)算符將在字符串末尾追加一個(gè) C-style 字符串 str。在這個(gè)版本中,直接調(diào)用了 append 函數(shù),將 C-style 字符串 str 添加到字符串末尾。
在兩個(gè)版本的實(shí)現(xiàn)中,都返回當(dāng)前 std::string 對(duì)象的引用,以支持鏈?zhǔn)秸{(diào)用。
7.9 string類erase函數(shù)
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;
}
}
erase 函數(shù)用于從字符串中刪除指定位置開始的一定長度的字符?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
void erase(size_t pos, size_t len = npos)
:這是 std::string 類中的 erase 函數(shù)的聲明,表示該函數(shù)將從指定位置 pos 開始刪除一定長度 len 的字符。pos 是傳入的參數(shù),表示刪除的起始位置的索引;len 是要?jiǎng)h除的字符個(gè)數(shù),默認(rèn)值為 npos,表示刪除從起始位置開始的所有字符。assert(pos < _size);
:使用 assert 斷言來確保刪除的起始位置 pos 不超過字符串的實(shí)際大小 _size。如果斷言失?。╬os 大于等于 _size),則會(huì)觸發(fā)斷言失敗錯(cuò)誤,幫助調(diào)試找到錯(cuò)誤的位置。if (len == npos || pos + len >= _size)
:檢查是否要?jiǎng)h除從起始位置 pos 開始的所有字符(即 len 等于 npos),或者是否要?jiǎng)h除的字符個(gè)數(shù)超過字符串末尾(即 pos + len 大于等于 _size)。如果是其中一種情況,表示要?jiǎng)h除從 pos 開始的所有字符或從 pos 開始直到末尾的所有字符。_str[pos] = '\0'; 和 _size = pos;
:在上述情況下,將字符串從位置 pos 處截?cái)?,即將字符?shù)組的第 pos 個(gè)字符設(shè)置為空字符 ‘\0’,并更新字符串的實(shí)際大小 _size 為 pos,以反映刪除后的新大小。else
:如果要?jiǎng)h除的字符個(gè)數(shù)小于字符串末尾的字符個(gè)數(shù),則需要將后面的字符向前移動(dòng)。strcpy(_str + pos, _str + pos + len);
:將從位置 pos + len 開始的字符復(fù)制到位置 pos,覆蓋掉要?jiǎng)h除的字符。_size -= len;
:刪除字符后,將字符串的實(shí)際大小 _size 減去 len,以反映刪除后的新大小。
7.10 string類erase函數(shù)
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
find 函數(shù)用于在字符串中查找指定字符或子串,并返回其位置?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
size_t find(char ch, size_t pos = 0) const
:這是 std::string 類中的 find 函數(shù)的第一個(gè)版本,表示該函數(shù)將在字符串中從位置 pos 開始查找字符 ch。pos 是傳入的參數(shù),表示查找的起始位置的索引,默認(rèn)值為 0,表示從字符串的開頭開始查找。assert(pos < _size);
:使用 assert 斷言來確保查找的起始位置 pos 不超過字符串的實(shí)際大小 _size。如果斷言失?。╬os 大于等于 _size),則會(huì)觸發(fā)斷言失敗錯(cuò)誤,幫助調(diào)試找到錯(cuò)誤的位置。
在這個(gè)版本中,使用了簡單的循環(huán)遍歷,從位置 pos 開始遍歷字符串,查找是否存在字符 ch。如果找到了,就返回該字符的位置索引;如果未找到,則返回 npos。
size_t find(const char* sub, size_t pos = 0) const
{
assert(sub);
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
size_t find(const char* sub, size_t pos = 0) const
:這是 std::string 類中的 find 函數(shù)的第二個(gè)版本,表示該函數(shù)將在字符串中從位置 pos 開始查找子串 sub。sub 是傳入的參數(shù),表示要查找的子串;pos 是傳入的參數(shù),表示查找的起始位置的索引,默認(rèn)值為 0,表示從字符串的開頭開始查找。assert(sub);
:使用 assert 斷言來確保傳入的子串 sub 不為空指針。如果斷言失?。╯ub 為空指針),則會(huì)觸發(fā)斷言失敗錯(cuò)誤,幫助調(diào)試找到錯(cuò)誤的位置。assert(pos < _size);
:同樣,使用 assert 斷言來確保查找的起始位置 pos 不超過字符串的實(shí)際大小 _size。
在這個(gè)版本中,使用 strstr 函數(shù)在字符串中查找子串 sub,如果找到了,就返回子串的位置索引;如果未找到,則返回 npos。
7.11 string類substr 函數(shù)
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 sub;
for (size_t i = 0; i < realLen; ++i)
{
sub += _str[pos + i];
}
return sub;
}
substr 函數(shù)用于從字符串中提取子串,從指定位置 pos 開始,并且可選地指定子串的長度 len?,F(xiàn)在來解釋這個(gè)函數(shù)的實(shí)現(xiàn):
string substr(size_t pos, size_t len = npos) const
:這是 std::string 類中的 substr 函數(shù)的聲明,表示該函數(shù)將從指定位置 pos 開始提取子串,并且可選地指定子串的長度 len。pos 是傳入的參數(shù),表示提取子串的起始位置的索引;len 是傳入的參數(shù),表示要提取的子串的長度,默認(rèn)值為 npos,表示提取從起始位置 pos 開始的所有字符。assert(pos < _size);
:使用 assert 斷言來確保提取子串的起始位置 pos 不超過字符串的實(shí)際大小 _size。如果斷言失?。╬os 大于等于 _size),則會(huì)觸發(fā)斷言失敗錯(cuò)誤,幫助調(diào)試找到錯(cuò)誤的位置。size_t realLen = len;
:定義一個(gè)變量 realLen,用于存儲(chǔ)實(shí)際要提取的子串的長度。初始值為傳入的參數(shù) len。if (len == npos || pos + len > _size)
:檢查是否要提取從起始位置 pos 開始的所有字符(即 len 等于 npos),或者是否要提取的字符個(gè)數(shù)超過字符串末尾(即 pos + len 大于等于 _size)。如果是其中一種情況,表示要提取從 pos 開始的所有字符或從 pos 開始直到末尾的所有字符。此時(shí),將 realLen 更新為從 pos 開始到末尾的字符個(gè)數(shù)。創(chuàng)建一個(gè)名為 sub 的新的 std::string 對(duì)象,用于存儲(chǔ)提取的子串。使用循環(huán)從位置 pos 開始,逐個(gè)字符地將子串添加到 sub 中。返回提取的子串 sub。
7.12 string類比較運(yùn)算符重載
bool operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
這是大于運(yùn)算符 > 的重載版本,表示該運(yùn)算符用于比較當(dāng)前字符串與另一個(gè)字符串 s 的大小關(guān)系。在這個(gè)版本中,使用 strcmp 函數(shù)比較兩個(gè)字符串 _str 和 s._str 的字典序。如果 _str 大于 s._str,則返回 true,否則返回 false。
bool operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
這是等于運(yùn)算符 == 的重載版本,表示該運(yùn)算符用于比較當(dāng)前字符串與另一個(gè)字符串 s 是否相等。同樣,使用 strcmp 函數(shù)比較兩個(gè)字符串 _str 和 s._str 的內(nèi)容是否相同。如果相同,返回 true,否則返回 false。
bool operator>=(const string& s) const
{
return *this > s || *this == s;
}
這是大于等于運(yùn)算符 >= 的重載版本,表示該運(yùn)算符用于比較當(dāng)前字符串是否大于或等于另一個(gè)字符串 s。在這個(gè)版本中,直接使用已經(jīng)定義好的大于運(yùn)算符 > 和等于運(yùn)算符 == 進(jìn)行組合,如果當(dāng)前字符串大于 s 或者與 s 相等,則返回 true,否則返回 false。
bool operator<=(const string& s) const
{
return !(*this > s);
}
這是小于等于運(yùn)算符 <= 的重載版本,表示該運(yùn)算符用于比較當(dāng)前字符串是否小于或等于另一個(gè)字符串 s。同樣,直接使用已經(jīng)定義好的大于等于運(yùn)算符 >= 進(jìn)行取反,如果當(dāng)前字符串小于 s,則返回 true,否則返回 false。
bool operator<(const string& s) const
{
return !(*this >= s);
}
這是小于運(yùn)算符 < 的重載版本,表示該運(yùn)算符用于比較當(dāng)前字符串是否小于另一個(gè)字符串 s。同樣,直接使用已經(jīng)定義好的大于等于運(yùn)算符 >= 進(jìn)行取反,如果當(dāng)前字符串不大于等于 s,則說明當(dāng)前字符串小于 s,返回 true,否則返回 false。
bool operator!=(const string& s) const
{
return !(*this == s);
}
這是不等于運(yùn)算符 != 的重載版本,表示該運(yùn)算符用于比較當(dāng)前字符串是否不等于另一個(gè)字符串 s。同樣,直接使用已經(jīng)定義好的等于運(yùn)算符 == 進(jìn)行取反,如果當(dāng)前字符串與 s 不相等,則返回 true,否則返回 false。
其實(shí)和之前類和對(duì)象的文章中講到的日期類比較運(yùn)算符重載一樣,先實(shí)現(xiàn)> ==
或< ==
后面的都可以進(jìn)行復(fù)用。
7.13 string類流插入<<和流提取>>重載
首先這里要注意的是,流插入和流提取在這里定義為全局函數(shù),因此我們不要再類中定義,而是在類外,即全局定義。這樣定義的運(yùn)算符重載函數(shù)不屬于類的成員,因此在其實(shí)現(xiàn)中不能直接訪問類的私有成員,而需要通過類的公有接口進(jìn)行訪問。
運(yùn)算符重載函數(shù)可以作為成員函數(shù)或全局非成員函數(shù)進(jìn)行定義,具體取決于使用場景和設(shè)計(jì)需求。通常情況下,如果運(yùn)算符的操作數(shù)為類對(duì)象本身或需要直接訪問類的私有成員,可以考慮將其定義為成員函數(shù)。而如果運(yùn)算符的操作數(shù)為類對(duì)象外的其他類型,或者運(yùn)算符涉及的操作不僅限于類對(duì)象本身,可以考慮將其定義為全局非成員函數(shù)。
流插入<<
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
這是輸出運(yùn)算符 << 的重載版本,表示將 std::string 類對(duì)象 s 輸出到輸出流 out 中。
使用一個(gè)循環(huán)遍歷 s 中的每個(gè)字符,并將每個(gè)字符依次輸出到輸出流 out 中。最后,將輸出流 out 返回,以支持鏈?zhǔn)捷敵觥?/p>
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const size_t N = 32;
char buff[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
return in;
}
這是輸入運(yùn)算符 >> 的重載版本,表示將輸入流 in 中的數(shù)據(jù)讀取并存儲(chǔ)到 std::string 類對(duì)象 s 中。
首先調(diào)用 s.clear() 函數(shù),將 s 的內(nèi)容清空,以便接收新的輸入。然后,使用一個(gè)循環(huán)從輸入流 in 中逐個(gè)讀取字符 ch。如果字符 ch 不是空格或換行符,就將字符添加到一個(gè)臨時(shí)字符數(shù)組 buff 中,并增加索引 i。一旦 buff 已滿(i == N - 1),就將 buff 最后一個(gè)元素設(shè)為空字符 ‘\0’,然后將 buff 添加到 s 中,然后將索引 i 重置為 0,以繼續(xù)接收后續(xù)字符。如果字符 ch 是空格或換行符,說明一個(gè)單詞的輸入結(jié)束,將 buff 最后一個(gè)元素設(shè)為空字符 ‘\0’,然后將 buff 添加到 s 中。最后,將輸入流 in 返回,以支持鏈?zhǔn)捷斎搿?mark hidden color="red">文章來源:http://www.zghlxwxcb.cn/news/detail-618443.html
8.string類的模擬實(shí)現(xiàn)(完整代碼)
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace mystring
{
class string
{
public:
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;
}
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 傳統(tǒng)寫法
//string(const string& s)
// :_str(new char[s._capacity+1])
// , _size(s._size)
// , _capacity(s._capacity)
//{
// strcpy(_str, s._str);
//}
// 現(xiàn)代寫法
void swap(string& tmp)
{
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// //string tmp(s._str);
// string tmp(s);
// swap(tmp); // this->swap(tmp);
// }
// return *this;
//}
string& operator=(string s)
{
swap(s);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
// 插入數(shù)據(jù)
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else
{
// 刪除數(shù)據(jù)
_str[n] = '\0';
_size = n;
}
}
void push_back(char ch)
{
// 滿了就擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
//insert(_size, ch);
}
void append(const char* str)
{
size_t len = strlen(str);
// 滿了就擴(kuò)容
if (_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str + _size, str);
//strcat(_str, str); 需要找\0,效率低
_size += len;
//insert(_size, str);
}
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);
// 滿了就擴(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;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 挪動(dòng)數(shù)據(jù)
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
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;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
size_t find(char ch, size_t pos = 0) const
{
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) const
{
assert(sub);
assert(pos < _size);
// kmp/bm
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
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 sub;
for (size_t i = 0; i < realLen; ++i)
{
sub += _str[pos + i];
}
return sub;
}
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);
}
private:
size_t _capacity;
size_t _size;
char* _str;
public:
const static size_t npos = -1;
};
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const size_t N = 32;
char buff[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
return in;
}
}
結(jié)語
有興趣的小伙伴可以關(guān)注作者,如果覺得內(nèi)容不錯(cuò),請給個(gè)一鍵三連吧,蟹蟹你喲?。?!
制作不易,如有不正之處敬請指出
感謝大家的來訪,UU們的觀看是我堅(jiān)持下去的動(dòng)力
在時(shí)間的催化劑下,讓我們彼此都成為更優(yōu)秀的人吧?。。?span toymoban-style="hidden">文章來源地址http://www.zghlxwxcb.cn/news/detail-618443.html
到了這里,關(guān)于C++初階之一篇文章讓你掌握string類(模擬實(shí)現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!