前言
本篇博客僅僅實(shí)現(xiàn)存儲字符的string。同時由于C++string庫設(shè)計(jì)的不合理,博主僅實(shí)現(xiàn)一些最常見的增刪查改接口!
接下來給出的接口都是基于以下框架:
namespace achieveString
{
class string
{
private:
char* _str;
size_t _capacity;
size_t _size;
};
}
一、string默認(rèn)構(gòu)造、析構(gòu)函數(shù)、拷貝構(gòu)造、賦值重載
1.1 默認(rèn)構(gòu)造
博主在這僅僅提供如無參和帶參默認(rèn)構(gòu)造接口:
//無參默認(rèn)構(gòu)造
string()
:_str(new char[1]{'\0'})
,_capacity(0)
,_size(0)
{ }
//帶參默認(rèn)構(gòu)造
string(const char* str = "")
:_capacity(strlen(str))
,_size(_capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
小tips:
- C++string標(biāo)準(zhǔn)庫中,無參構(gòu)造并不是空間為0,直接置為空指針。而是開一個字節(jié),并存放‘\0’。(C++中支持無參構(gòu)造一個對象后,直接在后面插入數(shù)據(jù),也從側(cè)面說明了這點(diǎn))
- 由于C++構(gòu)造函數(shù)不管寫不寫都會走初始化列表的特性,所以這里博主也走初始化列表。
- string中,_capacity和_size都不包含空指針,所以帶參構(gòu)造要多開一個空間,用來存儲’\0’。
1.2 析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
1.3 拷貝構(gòu)造
傳統(tǒng)寫法:
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
現(xiàn)代寫法:現(xiàn)代寫法的核心在于:將拷貝數(shù)據(jù)的工作交給別人來做,最后將成果交換一樣即可。
//交換
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//現(xiàn)代寫法
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
tips:現(xiàn)代寫法中,拷貝構(gòu)造是數(shù)據(jù)需初始化為空。原因在于C++中,編譯器對內(nèi)置類型不會做處理(個別如vs2019等編譯器會做處理的),這也就意味這_str是一個隨機(jī)值,指向任意一塊空間。調(diào)用析構(gòu)函數(shù)時會報(bào)錯。
1.4 賦值重載
賦值重載同樣分為傳統(tǒng)寫法和現(xiàn)代寫法。
傳統(tǒng)寫法:
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
現(xiàn)代寫法:
//現(xiàn)代寫法
//法一
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}*/
//法二
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
二、迭代器和范圍for
在C++中,范圍for在底層是通過迭代器來實(shí)現(xiàn)的。所以只要實(shí)現(xiàn)了迭代器,就支持范圍for。
而迭代器類似于指針,迭代器可以被看作是指針的一種泛化,它提供了類似指針的功能,可以進(jìn)行解引用操作、指針運(yùn)算等。
?
以下提供了const迭代器和非const迭代器:
typedef char* iterator;
const typedef 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;
}
三、元素相關(guān):operator[ ]
這里我們和庫中一樣,提供以下兩個版本
//可讀可寫
char operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只讀
const char operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
四、容量相關(guān):size、resize、capacity、reserve
4.1 size、capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
4.2 reserve
在C++中,我們一般不縮容。
所以實(shí)現(xiàn)reserve時(容量調(diào)整到n),首先判斷目標(biāo)容量n是否大于當(dāng)前容量。如果小于就不做處理,否則先開辟n+1個內(nèi)存空間(多出來的一個用于存儲‘\0’),然后將原有數(shù)據(jù)拷貝到新空間(strcpy會將’\0’一并拷貝過去)。然后釋放就空間,并讓_str指向新空間,同時更新_capacity。
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
4.3 resize
resize到目標(biāo)大小分為以下3中情況:
- 當(dāng)n<_size時,只需將下標(biāo)為n的地址處的數(shù)據(jù)改為’\0’。
- 其他情況,我們直接統(tǒng)一處理。直接復(fù)用reserve()函數(shù)將_capacity擴(kuò)到n。然后用將[_size, n)中的數(shù)據(jù)全部初始化為ch。(這里博主給ch一個初始值’\0’,但ch不一定為’\0’,所以要將下標(biāo)為n處的地址初始化為’\0’)
void resize(size_t n, char ch='\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
while (_size < n)
{
_str[_size] = ch;
_size++;
}
_str[_size] = '\0';
}
}
五、數(shù)據(jù)相關(guān):push_bach、append、operator+=、insert、erase
5.1 尾插:push_back
尾插首先檢查擴(kuò)容,在插入數(shù)據(jù)
void push_back(char ch)
{
//擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//插入數(shù)據(jù)
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
5.2 append尾部插入字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)//擴(kuò)容
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
5.3 operator+=()字符、字符串
operator+=()字符、字符串可以直接復(fù)用push_back和append。
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
5.4 insert插入字符、字符串
5.4.1 insert插入字符(在這提醒下,博主是所有的拷貝數(shù)據(jù)都是從’\0’開始,這樣就不需要單獨(dú)對’\0’做處理)
insert插入字符邏輯上還是很簡單的。
首先判斷插入字符時是否需要擴(kuò)容。然后從下標(biāo)為pos開始,所有數(shù)據(jù)依次往后挪動。最后將待插入字符給到pos處即可。
初學(xué)者最容易范的一個錯誤
但對于初學(xué)者來說,貌似也不太輕松。。。。。。
下面給出各位初學(xué)者容易犯的錯誤:
void insert(size_t pos, char ch)
{
assert(pos <= _size);
//擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪動數(shù)據(jù)
size_t end = _size;
while (end >= pos)
{
_str[end+1] = _str[end];
end--;
}
_str[pos] = ch;
_size++
}
這樣對嗎?答案是錯誤的。
假設(shè)是在頭插字符,end理論上和pos(即0)比較完后就減到-1,在下一次循環(huán)條件比較時失敗,退出循環(huán)。
遺憾的是end是size_t類型,始終>=0, 會導(dǎo)致死循環(huán)。
博主在這給出兩種解決方法:
- 將pos強(qiáng)轉(zhuǎn)為整型。
void insert(size_t pos, char ch)
{
assert(pos <= _size);
//擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪動數(shù)據(jù)
int end = _size;
while (end >= (int)pos)
{
_str[end+1] = _str[end];
end--;
}
_str[pos] = ch;
_size++
}
2.從end從最后數(shù)據(jù)的后一位開始,每次將前一個數(shù)據(jù)移到當(dāng)前位置。最后條件判斷就轉(zhuǎn)化為end>pos,不會出現(xiàn)死循環(huán)這種情況。
void insert(size_t pos, char ch)
{
assert(pos <= _size);
//擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪動數(shù)據(jù)
size_t end = _size+1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
//插入數(shù)據(jù),更新_size
_str[pos] = ch;
_size++;
}
5.4.2 insert插入字符串
insert同樣存在相同問題,并且思路一樣。博主就直接給出代碼了。
法一:
void insert(size_t pos, const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
法二:
void insert(size_t pos, const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size+1;
while (end > pos)
{
_str[end + len-1] = _str[end-1];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
5.5 erase
erase分兩種情況:
- 從pos開始,要刪的數(shù)據(jù)個數(shù)超過的字符串,即將pos后序所有數(shù)據(jù)全部情況。(直接將pos處數(shù)據(jù)置為’\0’即可)
- 從pos開始,要刪的數(shù)據(jù)個數(shù)沒有超出的字符串。所以只需要從pos+len位置后的所有數(shù)據(jù)向前移動從pos位置覆蓋原數(shù)據(jù)即可。
void erase(size_t pos, size_t len = npos)
{
if (len==npos || pos + len >= _size)
{
//有多少,刪多少
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
}
六、 關(guān)系操作符重載:< 、 ==、 <=、 >、>=、!=
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);
}
七、find查找字符、字符串、substr
7.1 find查找字符
size_t find(char ch, size_t pos = 0)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
7.2 find查找字符串
size_t find(const char* sub, size_t pos = 0)
{
const char* p = strstr(_str + pos, sub);
if (p)
{
return p - _str;
}
else
{
return npos;
}
}
7.3 strsub( ) 模擬實(shí)現(xiàn)
strsub目標(biāo)長度可能越界string,也可能還有沒有。但不管是那種情況,最后都需要拷貝數(shù)據(jù)。所以這里我們可以先將len真實(shí)長度計(jì)算出來,在拷貝數(shù)據(jù)。
string substr(size_t pos, size_t len = npos)const
{
string s;
size_t end = pos + len;
//目標(biāo)字符越界string,更新len
if (len == npos || end >= _size)
{
len = _size - pos;
end = _size;
}
//拷貝數(shù)據(jù)
s.reserve(len);
for (size_t i = pos; i < end; i++)
{
s += _str[i];
}
return s;
}
八、流插入和流提?。?lt;<、>>)(實(shí)現(xiàn)在string類外)
8.1 流插入<<
由于前面我們實(shí)現(xiàn)了迭代器,所以最簡單的方式就是范圍for
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;
}
8.1 流提取>>
流提取比較特殊。在流提取前需要將原有數(shù)據(jù)全部清空。同時由于>>無法獲取空字符和換行符()(都是作為多個值之間的間隔),直接流提取到ostream對象中,沒法結(jié)束。(類似于C語言中scanf, 換行符和空字符僅僅只是起到判斷結(jié)束的作用,但scanf無法獲取到它們)
所以這里博主直接調(diào)用istream對象中的get()函數(shù)。(類似于C語言中的getchar()函數(shù))
get詳細(xì)文檔
class string
{
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
上面這種方法雖然可以達(dá)到目的。但還有一個問題,每次插入數(shù)據(jù)都面臨可擴(kuò)容問題。那如何優(yōu)化呢?文章來源:http://www.zghlxwxcb.cn/news/detail-757945.html
優(yōu)化
其中一種辦法就是調(diào)用reserve()提前開好空間,但這樣面臨這另一個問題:開大了浪費(fèi)空間;開小了,同樣面臨這擴(kuò)容的問題。
所以在這博主采用和vs底層實(shí)現(xiàn)的思路:首先開好一段數(shù)組(包含’\0’,以16為例)。當(dāng)數(shù)據(jù)個數(shù)小于16時,字符串存在數(shù)組中;當(dāng)數(shù)據(jù)個數(shù)大于等于16時,將數(shù)據(jù)存在_str指向的空間。
這是一種以空間換時間的思路,同時也能很好的減少內(nèi)存碎片的問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-757945.html
class string
{
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[16];
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 16)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
九、所有代碼
namespace achieveString
{
class string
{
public:
typedef char* iterator;
const typedef 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()
:_str(new char[1]{'\0'})
,_capacity(0)
,_size(0)
{ }*/
string(const char* str = "")
:_capacity(strlen(str))
, _size(_capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
const char* c_str() const
{
return _str;
}
//析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//拷貝構(gòu)造
/*string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
//交換
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//現(xiàn)代寫法
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
// 賦值重載
/*string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
//現(xiàn)代寫法
//法一
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}*/
//法二
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
//可讀可寫
char operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只讀
const char operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return _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 resize(size_t n, char ch='\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
while (_size < n)
{
_str[_size] = ch;
_size++;
}
_str[_size] = '\0';
}
}
void push_back(char ch)
{
//擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//插入數(shù)據(jù)
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void insert(size_t pos, char ch)
{
assert(pos <= _size);
//擴(kuò)容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪動數(shù)據(jù)
size_t end = _size+1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
//插入數(shù)據(jù),更新_size
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//法一
/*int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, str, len);
_size += len;*/
//法二
size_t end = _size+1;
while (end > pos)
{
_str[end + len-1] = _str[end-1];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
void erase(size_t pos, size_t len = npos)
{
if (len==npos || pos + len >= _size)
{
//有多少,刪多少
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
}
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);
}
size_t find(char ch, size_t pos = 0)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* sub, size_t pos = 0)
{
const char* p = strstr(_str + pos, sub);
if (p)
{
return p - _str;
}
else
{
return npos;
}
}
string substr(size_t pos, size_t len = npos)
{
string s;
size_t end = pos + len;
if (len == npos || end >= _size)
{
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < end; i++)
{
s += _str[i];
}
return s;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _capacity;
size_t _size;
//const static size_t npos = -1; // C++支持const整型靜態(tài)變量在聲明時給值初始化,但不建議
//const static double npos = 1.1; // 不支持
const static size_t npos;
};
const size_t string::npos = -1;
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;
}
//istream& operator>>(istream& in, string& s)
//{
// s.clear();
// char ch;
// //in >> ch;
// ch = in.get();
// while (ch != ' ' && ch != '\n')
// {
// s += ch;
// //in >> ch;
// ch = in.get();
// }
// return in;
//}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[16];
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 16)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
到了這里,關(guān)于C++_String增刪查改模擬實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!