string類的成員變量分別是存儲字符串的一段空間_str,表示字符串的有效字符個數(shù)_size和表示存儲有效字符空間的_capacity。
private:
char *_str;
size_t _size;// 有效字符的個數(shù)
size_t _capacity;// 存儲有效字符的空間
2.構造函數(shù)
(1)無參構造函數(shù)string類的無參構造函數(shù)非常簡單,_size和_capacity都設置為0,但是_str不能設置為nullptr,因為根據(jù)標準庫里的string設計,無參構造函數(shù)里的_str設置為空串。
string()
:_size(0)
,_capacity(0)
{// 按照標準庫里的string設計無參構造函數(shù)
// _str存放一個空串,而不是直接設置為nullptr
_str = new char[1];
_str[0] = '\0';
}
(2)有參構造函數(shù)首先,string類的有參構造函數(shù)其實可以設計為全缺省函數(shù),缺省值設置為空串,當沒有傳入?yún)?shù)時使用缺省值,將_str設置為空串,這樣就可以不需要定義無參構造函數(shù)了。其次,如果傳入了參數(shù),就將_size和_capacity設置為形參的長度,然后開一段大小和形參相同的空間給_str,最后將值拷貝過去即可。
string(const char *str = "")
:_size(strlen(str))
,_capacity(_size)
{// strcpy函數(shù)會將\0也拷貝過去
_str = new char[_capacity + 1];
strcpy(_str, str);
}
3.c_str函數(shù)string類的c_str函數(shù)是為了方便string字符串配合C語言的字符串函數(shù)接口使用而設計的,它可以返回string對象的char * 類型的指針,這個函數(shù)實現(xiàn)起來非常簡單,直接返回我們成員變量_str即可,它就是一個char * 類型的指針。需要注意的是,c_str函數(shù)的返回類型是 const char*,要加上const的原因調用該函數(shù)只能獲取指針,并不能對指針進行修改。
const char *c_str() const
{return _str;
}
4.operator[]string類中對運算符[]的重載是很重要的,提供這個運算符重載可以方便我們對string對象進行下標訪問,也可以修改該下標對應的值。函數(shù)的返回類型是char&,一般引用返回是為了減少拷貝,但這里我們只需要返回一個字符,拷貝成本并不大。這里使用引用返回的目的是允許修改,因為引用就是返回值的一個別名,我們就可以對返回值進行修改。
char &operator[](size_t pos)
{return _str[pos];
}
// 再提供一個const版本,讓const對象也能調用,就不會出現(xiàn)權限放大的問題
const char &operator[](size_t pos) const
{return _str[pos];
}
5.深淺拷貝問題
(1)淺拷貝在C++的類設計中,如果我們沒有寫拷貝構造函數(shù),那么會使用默認的拷貝構造函數(shù),默認的拷貝構造函數(shù)是淺拷貝,也就是簡單地將一個對象的值拷貝給另一個對象。如果對于內置類型的話淺拷貝沒什么太大的影響,但如果是自定義類型,就比如我們的string類,將一個string對象的值拷貝給另一個string對象本質上是將指針內容進行拷貝,這樣就會導致兩個對象指向同一塊空間。
這樣淺拷貝的話會引發(fā)一些問題,比如析構函數(shù)的時候會被析構兩次,或者一個對象改變自己字符串的值會影響另一個對象字符串的值。
(2)深拷貝深拷貝和淺拷貝不同的是深拷貝是另外開一塊同樣的空間,然后將字符串的內容拷貝下來,這樣就讓兩個對象指向不同的空間,但這兩個不同空間的字符串值是相同的,這樣就解決了自定義類型淺拷貝帶來的問題。
要完成深拷貝的話就需要我們自己寫拷貝構造函數(shù)了,不能使用默認的拷貝構造函數(shù):
string(const string &s)
:_size(strlen(s._str))
,_capacity(_size)
{_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
賦值運算符重載和拷貝構造一樣,如果我們沒有寫賦值運算符重載,默認使用的是淺拷貝,所以我們也要自己寫賦值運算符重載。
賦值運算符重載的寫法和拷貝構造函數(shù)的寫法不同,不能直接復制過來,因為賦值運算符重載面對的是兩個都已經(jīng)存在了的開好空間了的對象,有可能需要被賦值的對象的空間比較小,會存在越界訪問的問題;也有可能需要被賦值的對象的空間比較大,雖然不會出現(xiàn)越界訪問但會造成空間的浪費。所以簡單粗暴的就是先釋放原有的空間,再進行復制。
string& operator=(const string& s)
{// 防止出現(xiàn)自己給自己賦值導致的錯誤
if (this != &s)
{delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
6.size函數(shù)和capacity函數(shù)這兩個函數(shù)很簡單,分別返回_size的值和_capacity的值即可。
size_t size() const
{return _size;
}
size_t capacity() const
{return _capacity;
}
7.reserve函數(shù)reserve函數(shù)是擴容函數(shù),當容量不足時,會擴大到指定的容量。這個函數(shù)實現(xiàn)起來比較簡單,當指定容量大于當前容量時說明要擴容,我們定義一個新的空間,把這塊新空間的大小設置為指定的新容量,然后將原來字符串的值拷貝到新開的空間上,再將原來的空間釋放,讓_str指針指向新的空間,最后更新_capacity的值即可。
void reserve(size_t n)
{// 如果容量不夠,就要擴容
if (n >_capacity)
{char* tmp = new char[n + 1];// 保留一個位置給\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
8.resize函數(shù)resize函數(shù)也是擴容函數(shù),但是它改變的是_size的值,這個函數(shù)的實現(xiàn)分三種情況討論(假設新的size值為newsize):
void resize(size_t n, char ch = '\0')
{// 如果容量不夠,首先要擴容
if (n >_capacity)
{reserve(n);
}
// 到這里代表容量一定足夠
for (size_t i = _size; i< n; i++)
{_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
9.string的插入函數(shù)
(1)push_back函數(shù)push_back函數(shù)是在字符串末尾插入一個字符,首先需要考慮容量是否滿了,如果容量滿了就需要先擴容。擴容的時候還需要特別關注一下如果字符串是空串,那么_capacity的值是0,需要特殊處理一下。擴容完以后就是簡單的尾插操作即可。
void push_back(char ch)
{// 說明容量滿了,需要先擴容
if (_size == _capacity)
{// 需要考慮到_capacity是不是等于0這個情況
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
(2)append函數(shù)我們只實現(xiàn)append插入一個常量字符串的函數(shù),首先是要計算新插入的字符串加上原來的字符串一共有多長,然后要判斷這個長度是否大于_capacity,如果是的話就需要擴容。最后直接用strcpy函數(shù)拼接起來即可。
void append(const char* str)
{// 先計算插入字符串以后的長度
int len = _size + strlen(str);
// 如果容量不大需要擴容
if (len >_capacity)
{reserve(len);
}
strcpy(_str + _size, str);
_size = len;
_str[_size] = '\0';
}
(3)operator+=函數(shù)這個函數(shù)只需要復用push_back函數(shù)和append函數(shù)即可。
string& operator+=(const char* str)
{append(str);
return *this;
}
string& operator+=(char ch)
{push_back(ch);
return *this;
}
(4)insert函數(shù)insert函數(shù)我們實現(xiàn)兩個版本,一個是插入一個字符,一個是插入一個常量字符串。這個函數(shù)實現(xiàn)的邏輯也比較簡單,就是將pos位置到_size位置的字符往后挪動,然后在空位處插入新的字符或者字符串。
string& insert(size_t pos, char ch)
{// 斷言防止pos出現(xiàn)非法范圍,當pos=_size時就是push_back
// 所以push_back可以復用這個insert
assert(pos<= _size);
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);
}
size_t end = _size + len;
while (end >pos + len -1)
{_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
10.字符串比較函數(shù)字符串比較函數(shù)需要實現(xiàn)各種比較符號的運算符重載函數(shù),字符串比較的原理是根據(jù)字符的ASCII碼值的大小進行比較,這可以利用C語言的strcmp函數(shù)進行比較。其實我們只需要實現(xiàn)其中個別幾個函數(shù),剩下的直接復用即可。
bool operator<(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str())< 0;
}
bool operator<=(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str())<= 0;
}
bool operator==(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator>(const string& s1, const string& s2)
{return !(s1<= s2);
}
bool operator>=(const string& s1, const string& s2)
{return !(s1< s2);
}
bool operator!=(const string& s1, const string& s2)
{return !(s1 == s2);
}
11.迭代器string類的迭代器非常簡單,其實就是 char* 類型的指針,與迭代器配合使用的begin函數(shù)和end函數(shù)實現(xiàn)起來也非常簡單,begin函數(shù)只要返回字符串第一個字符的地址即可,end函數(shù)只要返回字符串最后一個字符的下一個位置的地址。同時,我們也要實現(xiàn)一個const的迭代器和const修飾的begin函數(shù)和end函數(shù),讓const對象也可以調用。
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的遍歷方式有三種,分別是通過下標遍歷、通過迭代器遍歷和通過范圍for來遍歷,通過下標遍歷借助operator[]函數(shù)即可,通過迭代器遍歷只要實現(xiàn)迭代器即可,范圍for遍歷其實底層也是通過迭代器來遍歷的,例如下面的例子:ch是迭代器解引用的一份拷貝,假設迭代器變量為it,等價于ch = *it;
void test_string1()
{JJP::string s("hello");
for (auto ch : s)
{cout<< ch;
}
cout<< endl;
}
所以我們就能意識到一個問題,如果我們對ch的值進行改變的話,原來的字符串的值并不會發(fā)生改變,因為改變的是ch這個變量的值,并沒有改變*it的值,因此,如果想要改變原來字符串的值,需要帶上引用。
void test_string1()
{JJP::string s("hello");
// 只改變ch的值,不能改變原字符串的值
for (auto ch : s)
{ch -= 1;
cout<< ch;
}
cout<< endl;
cout<< s.c_str()<< endl;
// 加上引用就可以改變原來字符串的值
for (auto& ch : s)
{ch -= 1;
cout<< ch;
}
cout<< endl;
cout<< s.c_str()<< endl;
}
12.erase函數(shù)string的刪除函數(shù)與插入函數(shù)相反,挪動數(shù)據(jù)覆蓋即可。
string& erase(size_t pos, size_t len = npos)
{assert(pos<= _size);
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;
}
}
13.find函數(shù)find函數(shù)實現(xiàn)起來也非常簡單,只需要遍歷查找即可。我們實現(xiàn)一個查找字符函數(shù)和一個查找字符串函數(shù),查找字符函數(shù)挨著遍歷去查找即可,查找字符串函數(shù)可以使用C語言的庫函數(shù)strstr去查找子串。
size_t find(char ch, size_t pos = 0)
{for (; pos< _size; pos++)
{if (_str[pos] == ch)
{return pos;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{const char* p = strstr(_str + pos, str);
if (p == nullptr)
{return npos;
}
else
{return p-_str;
}
}
14.string類的流插入和流提取函數(shù)我們還需要實現(xiàn)以下流插入和流提取函數(shù),這樣方便我們輸入字符串和輸出字符串。
ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s)
{ out<< ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{// 先將字符串清空
s.resize(0, '\0');
// 這種方法可能存在多次擴容的情況,效率較低
//char ch;
in >>ch;
//ch = in.get();
//while (ch != ' ' && ch != '\n')
//{// s += ch;
// //in >>ch;
// ch = in.get();
//}
//return in;
char ch;
ch = in.get();
char buff[128] = {'\0'};
size_t i = 0;
while (ch != ' ' && ch != '\n')
{ buff[i++] = ch;
if (i == 127)
{ s += buff;
memset(buff, '\0', 128);
i = 0;
}
ch = in.get();
}
s += buff;
return in;
}
15.swap函數(shù)string類的swap函數(shù)是在底層直接交換兩個對象的指針,所以實現(xiàn)起來非常簡單。
void swap(string& s)
{std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
16.拷貝構造函數(shù)和賦值函數(shù)的現(xiàn)代寫法我們上面寫的拷貝構造函數(shù)方法太麻煩了,有一種更加方便的寫法,就是定義一個局部對象tmp,讓tmp去調用常量字符串構造函數(shù)從而完成深拷貝,最后將tmp與this交換即可。需要注意的是一開始的時候this的_str、_size和_capacity需要初始化一下,因為如果沒有初始化最后交換給tmp的就是隨機值,tmp在最后析構的時候可能會報錯。
// 現(xiàn)代寫法,讓tmp去完成深拷貝,復用常量字符串構造函數(shù)
string(const string &s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{string tmp(s._str);
swap(tmp);// 這個調用的是string類的swap函數(shù)
}
除了拷貝構造函數(shù)有現(xiàn)代寫法,賦值運算符重載函數(shù)也有現(xiàn)代寫法。第一種現(xiàn)代寫法和拷貝構造函數(shù)的寫法相似,也是借助局部對象tmp完成深拷貝,最后交換即可。
string& operator=(const string& s)
{if (this != &s)
{string tmp(s._str);
swap(tmp);
}
return *this;
}
第二種寫法更加的簡潔粗暴,我們可以直接將形參的對象與this交換即可。由于我們參數(shù)傳遞是傳值傳參,不是引用傳參,而是實參的一份臨時拷貝,所以交換以后并不會影響實參的值。
// 現(xiàn)代寫法更簡潔的版本
string& operator=(string s)
{swap(s);
return *this;
}
二、代碼#pragma once
#include#include#include
// 使用命名空間為了不讓庫的string和我們自己定義的string沖突
namespace JJP
{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;
}
// 可以不給無參構造函數(shù),直接將有參構造函數(shù)設計成全缺省
// 可以達到一樣的效果
// string()
// :_size(0)
// ,_capacity(0)
// {// // 按照標準庫里的string設計無參構造函數(shù)
// // _str存放一個空串,而不是直接設置為nullptr
// _str = new char[1];
// _str[0] = '\0';
// }
string(const char *str = "")
:_size(strlen(str))
,_capacity(_size)
{// strcpy函數(shù)會將\0也拷貝過去
_str = new char[_capacity + 1];
strcpy(_str, str);
}
~string()
{if (_str)
{delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
const char *c_str() const
{return _str;
}
char &operator[](size_t pos)
{return _str[pos];
}
const char &operator[](size_t pos) const
{return _str[pos];
}
// // 原始寫法,代碼比較多
// string(const string &s)
// :_size(strlen(s._str))
// ,_capacity(_size)
// {// _str = new char[_capacity + 1];
// strcpy(_str, s._str);
// }
void swap(string& s)
{ std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 現(xiàn)代寫法,讓tmp去完成深拷貝,復用常量字符串構造函數(shù)
string(const string &s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{string tmp(s._str);
swap(tmp);// 這個調用的是string類的swap函數(shù)
}
// 原始寫法
// string &operator=(const string &s)
// {// // 防止出現(xiàn)自己給自己賦值導致的錯誤
// if (this != &s)
// {// delete[] _str;
// _str = new char[s._capacity + 1];
// strcpy(_str, s._str);
// _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;
// }
// 現(xiàn)代寫法更簡潔的版本
string& operator=(string s)
{ swap(s);
return *this;
}
size_t size() const
{return _size;
}
size_t capacity() const
{return _capacity;
}
void push_back(char ch)
{// 說明容量滿了,需要先擴容
if (_size == _capacity)
{// 需要考慮到_capacity是不是等于0這個情況
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{// 先計算插入字符串以后的長度
int len = _size + strlen(str);
// 如果容量不大需要擴容
if (len >_capacity)
{reserve(len);
}
strcpy(_str + _size, str);
_size = len;
_str[_size] = '\0';
}
void reserve(size_t n)
{// 如果容量不夠,就要擴容
if (n >_capacity)
{char* tmp = new char[n + 1];// 保留一個位置給\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
string& operator+=(const char* str)
{append(str);
return *this;
}
string& operator+=(char ch)
{push_back(ch);
return *this;
}
void resize(size_t n, char ch = '\0')
{// 如果容量不夠,首先要擴容
if (n >_capacity)
{reserve(n);
}
// 到這里代表容量一定足夠
for (size_t i = _size; i< n; i++)
{_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
string& insert(size_t pos, char ch)
{// 斷言防止pos出現(xiàn)非法范圍,當pos=_size時就是push_back
// 所以push_back可以復用這個insert
assert(pos<= _size);
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);
}
size_t end = _size + len;
while (end >pos + len -1)
{_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, 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
{size_t begin = pos + len;
while (begin<= _size)
{_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
return *this;
}
size_t find(char ch, size_t pos = 0)
{for (; pos< _size; pos++)
{if (_str[pos] == ch)
{return pos;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{const char* p = strstr(_str + pos, str);
if (p == nullptr)
{return npos;
}
else
{return p-_str;
}
}
private:
char *_str;
size_t _size;// 有效字符的個數(shù)
size_t _capacity;// 存儲有效字符的空間
const static size_t npos;
};
const size_t string::npos = -1;
std::ostream& operator<<(std::ostream& out, const string& s)
{for (auto ch : s)
{ out<< ch;
}
return out;
}
std::istream& operator>>(std::istream& in, string& s)
{// 先將字符串清空
s.resize(0, '\0');
// 這種方法可能存在多次擴容的情況,效率較低
//char ch;
in >>ch;
//ch = in.get();
//while (ch != ' ' && ch != '\n')
//{// s += ch;
// //in >>ch;
// ch = in.get();
//}
//return in;
char ch;
ch = in.get();
char buff[128] = {'\0'};
size_t i = 0;
while (ch != ' ' && ch != '\n')
{ buff[i++] = ch;
if (i == 127)
{ s += buff;
memset(buff, '\0', 128);
i = 0;
}
ch = in.get();
}
s += buff;
return in;
}
bool operator<(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str())< 0;
}
bool operator<=(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str())<= 0;
}
bool operator==(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator>(const string& s1, const string& s2)
{return !(s1<= s2);
}
bool operator>=(const string& s1, const string& s2)
{return !(s1< s2);
}
bool operator!=(const string& s1, const string& s2)
{return !(s1 == s2);
}
}
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧