目錄
引言
外層包裝
成員變量設(shè)計(jì)
接口實(shí)現(xiàn)
在之前的博客中我簡(jiǎn)單介紹了string的相關(guān)使用方法和接口,現(xiàn)在我們自己來(lái)模擬實(shí)現(xiàn)一下它的底層(注:不同編譯器底層實(shí)現(xiàn)不同,這里只是其中一種的實(shí)現(xiàn))。
外層包裝本來(lái)應(yīng)該是在外層套個(gè)basic_string
namespace sak
{
class string
{
public:
//...
private:
//...
};
Test();
};
成員變量設(shè)計(jì)我的 string類中成員變量設(shè)置三個(gè):char* _str , size_t size(有效數(shù)據(jù)大小) , size_t capacity(容量)
因?yàn)樽址婕暗?'\0' 問(wèn)題,要多給一個(gè)空間儲(chǔ)存 '\0',所以在開(kāi)辟空間時(shí)給_str 多開(kāi)一個(gè)字節(jié)空間,capacity不包含 '\0' 的大小,表示有效數(shù)據(jù)的容量。
class string
{
public:
//...
private:
size_t capacity;
size_t size;
char*_str;
};
接口實(shí)現(xiàn)1、構(gòu)造函數(shù)接口
構(gòu)造可以用字符串構(gòu)造,也可以用string類型對(duì)象構(gòu)造。注意成員變量聲明的順序,為了盡可能減少strlen函數(shù)的調(diào)用,先對(duì)_capacity初始化,再對(duì)_size和_str初始化,因?yàn)槌蓡T變量在類中聲明次序就是其在初始化列表中的初始化順序,與其在初始化列表中的先后 次序無(wú)關(guān),所以要先聲明_capacity,再聲明其他的。
string(const char* str)
:_capacity(strlen(str))
,_size(_capacity)
,_str(new char[_capacity+1])
{
strcpy(_str, str);
}
string(const string& s)
:_capacity(s._capacity)
,_size(s._size)
,_str(new char[s._capacity+1])
{
strcpy(_str, s._str);
}
這里不能直接將str 指針賦值給_str,雖然確實(shí)將str指向的內(nèi)容賦給了_str,但是如果參數(shù)str是常量字符串,那么后面調(diào)用push_back接口需要擴(kuò)容的話,無(wú)法對(duì)常量字符串操作,所以這里先給_str開(kāi)辟空間(上面說(shuō)了因?yàn)?'\0'的原因多開(kāi)一個(gè)空間)。
還有一種情況:無(wú)參類型的構(gòu)造
string()
{
_str = new char[1];
_str[0] = '\0';
_size = _capacity = 0;
}
或者這里可以通過(guò)字符串類型參數(shù)的缺省來(lái)實(shí)現(xiàn)無(wú)參類型的構(gòu)造:
string(const char* str = "")
:_capacity(strlen(str))
,_size(_capacity)
,_str(new char[_capacity+1])
{
strcpy(_str, str);
}
string(const char* str = "")
這里為什么是"",需要先辨析清楚 '\0' , " \0" , "" 三者之間的不同。
'\0' :字符\0,ASCLL碼值是0,即就代表了0
" \0" :字符串都以 '\0' 結(jié)尾,所以是\0\0,有2個(gè)?
"" : 空字符串,里面有一個(gè) \0
size_t strlen ( const char * str );
strlen函數(shù)的參數(shù)部分是const char* 類型的,會(huì)計(jì)算 '\0'之前有幾個(gè)字符。因此這里不能是
string(const char* str = '\0'或者nullptr)? 而是? string(const char* str = "")
2、析構(gòu)函數(shù)接口
~string()
{
delete[]_str;
_str = nullptr;
_capacity = _size = 0;
}
3、c_str 接口
const char* c_str() const
{
return _str;
}
在尚未實(shí)現(xiàn)? 流輸出<< 和?? 流提取>>操作符時(shí),可以用c_str打印string類型對(duì)象。
4、size 接口
size_t size() const
{
return _size;
}
5、capacity 接口
size_t capacity() const
{
return _capacity;
}
這里capacity實(shí)現(xiàn)的和VS編譯器下不同,所以表現(xiàn)結(jié)果也不同
6、operator[] 接口(重載[ ])
//可讀可寫(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];
}
7、迭代器實(shí)現(xiàn)(char*指針?lè)绞剑?/p>
迭代器底層實(shí)現(xiàn)有很多方法,內(nèi)部類、指針.......? 這里我用指針實(shí)現(xiàn)。
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string::iterator it = s2.begin();
while (it != s2.end())
{
(*it)++;
it++;
}
cout<< s0.c_str()<< endl;
范圍for底層其實(shí)就是迭代器,是直接用迭代器替換的,所以范圍for并沒(méi)有想象的那么神奇。
底層是將對(duì)象賦給 it變量,再將*it 賦給e,其實(shí)很簡(jiǎn)單。
8、reserve接口
當(dāng)要改變的空間大小n< capacity,對(duì)容量沒(méi)有變化,當(dāng)n< capacity ,reserve實(shí)質(zhì)上就是擴(kuò)容的實(shí)現(xiàn)。在C語(yǔ)言中擴(kuò)容有realloc,C++這里我用的new,需要自己實(shí)現(xiàn)一下擴(kuò)容。
void reserve(size_t n)
{
if (n >_capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
9、push_back、append、+= 接口
void push_back(char c)
{
if (_size == _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
void append(const char* s)
{
size_t len = strlen(s);
if (len + _size >_capacity) {
reserve(_size + len);
}
strcpy(_str + _size, s);
_size += len;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
10、insert、erase接口
insert函數(shù)方便我們?cè)谌魏挝恢貌迦霐?shù)據(jù)。和上面一樣,分為插入1個(gè)字符或字符串。
在插入之前要先挪動(dòng)數(shù)據(jù)(尾插除外),挪動(dòng)的過(guò)程中要注意邊界問(wèn)題。
string& insert(char ch, size_t pos)
{
assert(pos<= _size);
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
size_t end = _size+1;
while (end >= pos)
{
_str[end] = _str[end-1];
--end;
}
_str[pos] = ch;
_size++;
_str[_size] = '\0';
return *this;
}
string& insert(const char* str, size_t pos)
{
size_t len = strlen(str);
if (len + _size >_capacity)
{
reserve(len + _size);
}
size_t end = _size+len;
while (end >= pos + len)
{
_str[end] = _str[end-len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
erase刪除同樣需要挪動(dòng)數(shù)據(jù),給刪除個(gè)數(shù)一個(gè)缺省值npos,定義為常量-1,對(duì)于size_t來(lái)說(shuō)是2^32,當(dāng)沒(méi)有給定刪除個(gè)數(shù)或要?jiǎng)h除的個(gè)數(shù)超出剩余的字符數(shù)就意味著刪除pos位置之后所有數(shù)據(jù);否則刪除pos位置后面len個(gè)字符,可以用strcpy直接覆蓋。
string& erase(size_t pos,size_t len = npos)
{
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
}
else
{
strcpy(_str + pos,_str+pos+len);
_size -= len;
}
return *this;
}
const static int npos = -1;
11、find接口
find接口不僅要支持查找,還要能支持從指定位置開(kāi)始查找。同樣的,有查找單個(gè)字符的功能,還有查找字符串的功能。找到返回下標(biāo),找不到返回npos。
查找單個(gè)字符比較簡(jiǎn)單,直接遍歷即可。
查找字符串可以用C函數(shù)庫(kù)的strstr函數(shù)暴力查找,注意該函數(shù)返回的是指針,所以最后返回下標(biāo)值時(shí)要用返回的指針 - 頭指針。
const static int npos = -1;
size_t find(char ch, size_t pos = 0)
{
assert(pos< _size);
while (pos< _size)
{
if (_str[pos] == ch)
return pos;
++pos;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos< _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
return npos;
else
return ptr - _str;
}
12、resize接口
resize是改變?nèi)萘康暮瘮?shù),可以變大也可以變小。
void resize(size_t n,char ch = '\0')
{
if (n >_size)
{
reserve(n);
_size = n;
for (size_t i = _size; i< n; ++i)
{
_str[i] = ch;
}
_str[n] = '\0';
}
else
{
_size = n;
_str[n] = '\0';
}
}
13、流插入cout
流插入操作符<<重載,需要注意[ ]的重載,上面只重載了[ ]的非const版本,對(duì)于只讀對(duì)象調(diào)用需要const版本的。我們?cè)僦剌d一個(gè)只讀的[ ]方法。
const char& operator[](size_t pos)const
{
assert(pos< _size);
return _str[pos];
}
ostream& operator<<(ostream& out,const string& s)
{
for (size_t i = 0; i< s.size(); ++i)
{
out<< s[i];
}
return out;
}
有了c_str還不夠,還需要<<流插入。這兩者打印的方式是不一樣的。
c_str是按char*方式打印,字符串中間出現(xiàn) '\0' 不會(huì)打印后面的內(nèi)容;<<碰到 '\0' 依然會(huì)繼續(xù)打印,'\0' 打印顯示的是不可見(jiàn)字符。
14、流提取 >>
與流插入對(duì)應(yīng)。流提取碰到空格或換行就停下,后面部分進(jìn)入緩沖區(qū)。
想要拿到空格之后的內(nèi)容需要用到get函數(shù)。
istream& operator>>(istream& in,string& s)
{
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
這種方式有一個(gè)缺陷,字符串很長(zhǎng)時(shí)會(huì)擴(kuò)容很多次。想解決這個(gè)問(wèn)題也不好先reserve給定capacity大小,因?yàn)閏apacity給大了浪費(fèi),小了沒(méi)有效果。
因此可以借鑒C++標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)方式,先設(shè)定一個(gè)buff數(shù)組,分段將字符串放入。
istream& operator>>(istream& in, string& s)
{
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)//滿了
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i >0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
15.拷貝構(gòu)造
這里我們必須手動(dòng)寫(xiě)一個(gè)拷貝構(gòu)造函數(shù),否則系統(tǒng)默認(rèn)的是淺拷貝,對(duì)于自定義類型來(lái)說(shuō)會(huì)出現(xiàn)指針指向同一空間以及內(nèi)存泄漏的問(wèn)題。
拷貝構(gòu)造有兩種寫(xiě)法:傳統(tǒng)寫(xiě)法和現(xiàn)代寫(xiě)法。
1、傳統(tǒng)寫(xiě)法
//傳統(tǒng)寫(xiě)法
string(const string& s)
{
_str = new char[s._capacity + 1];
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
傳統(tǒng)寫(xiě)法就是創(chuàng)建一個(gè)和拷貝對(duì)象一樣大小的新空間,再將數(shù)據(jù)拷貝到新空間中。
2、現(xiàn)代寫(xiě)法
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
//現(xiàn)代寫(xiě)法
string(const string& s)
:_str(nullptr)
,_capacity(0)
,_size(0)
{
string tmp(s._str); //構(gòu)造函數(shù)
swap(tmp);
}
現(xiàn)代寫(xiě)法就是先用構(gòu)造函數(shù)構(gòu)造一個(gè)tmp,此時(shí)tmp擁有和拷貝對(duì)象s0一樣的空間和數(shù)據(jù),交換tmp和s1,使兩者的空間和數(shù)據(jù)完全交換。因?yàn)閠mp最后要調(diào)用析構(gòu)函數(shù)清理空間,和s1交換后tmp指向的是隨機(jī)值,所以提前讓s1指向空,避免釋放野指針的問(wèn)題。
注意:現(xiàn)代寫(xiě)法并不能提高效率,只是簡(jiǎn)化了代碼(結(jié)合賦值函數(shù)來(lái)看)
16、賦值重載
賦值也有傳統(tǒng)和現(xiàn)代寫(xiě)法,與拷貝構(gòu)造類似。
1、傳統(tǒng)寫(xiě)法
//傳統(tǒng)寫(xiě)法
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp,s._str);
delete[]_str;
_str = tmp;
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
2、現(xiàn)代寫(xiě)法
//現(xiàn)代寫(xiě)法1(不推薦)
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
//現(xiàn)代寫(xiě)法2(推薦√)
string& operator=(string s)
{
swap(s);
return *this;
}
現(xiàn)代寫(xiě)法1還是先拷貝構(gòu)造tmp,再將tmp與s1交換,這種寫(xiě)法tmp有點(diǎn)多余了。
其實(shí)可以直接傳值傳參,在傳參的過(guò)程中拷貝構(gòu)造s(寫(xiě)了拷貝構(gòu)造這里是深拷貝),直接交換s和s1即可。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
當(dāng)前名稱:string(四)————底層實(shí)現(xiàn)-創(chuàng)新互聯(lián)
本文URL:http://chinadenli.net/article18/dggggp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供服務(wù)器托管、網(wǎng)站設(shè)計(jì)、商城網(wǎng)站、手機(jī)網(wǎng)站建設(shè)、ChatGPT、營(yíng)銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容