? ? ? ? ? ? ? ??
目录
?string简易版模拟实现
成员变量
string(const char* str = "")
string(const string& s)
深浅拷贝
?传统写法
现代写法
?string operator=(const string& s)
传统写法
现代写法
?~string()
string简易版完整代码
string完整版模拟实现
成员变量
迭代器
string(const char* str = "")
string(const string& s)
string& operator=(string s)
void swap(string& s)
~string()
const char* c_str() const?
size_t size() const
size_t capacity() const
char& operator[](size_t pos)
const char& operator[](size_t pos) const
void reserve(size_t n)
void resize(size_t n, char ch = '\0')
void push_back(char ch)
void pop_back()
void append(const char* str)
string& operator+=(char ch)
string& operator+=(const char* str)
size_t find(const char* s, size_t pos = 0)?
string& insert(size_t pos, char ch)
?string& insert(size_t pos, const char* s)
string erase(size_t pos ,size_t len = npos)
void clear()
bool operator>(const string& s1, const string& s2)
bool operator==(const string& s1, const string& s2)
bool operator>=(const string& s1, const string& s2)
bool operator<(const string& s1, const string& s2)
bool operator<=(const string& s1, const string& s2)
bool operator!=(const string& s1, const string& s2)
ostream& operator<<(ostream& out, const string& s)?
istream& operator>>(istream& in, string& s)
模拟完整版string的代码
string.h
测试
Test.c
?string简易版模拟实现
? ? ? ? 我们先不直接实现完整版的string,先实现简易版的string来基本了解下它的框架,以及来学习深浅拷贝的问题。这样有循序渐进的过程嘛~
我们首先定义一个cyq的命名空间,把我们自己实现的string写在里面,防止命名冲突。
成员变量
private:
char* _str;
string(const char* str = "")
//构造函数
string(const char* str = "")
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
?? ??? ?//构造函数 ?? ??? ?string(const char* str = "") //这里我们给一个缺省值,是空串"",str是传过来的字符串 ?? ??? ?{ ?? ??? ??? ?_str = new char[strlen(str) + 1]; //这里我们开辟strlen(str) + 1大小的空间,为什么要+1呢?
?? ??? ??? ?strcpy(_str, str); //把str空间的字符串的内容拷贝到_str指向的空间中,直到遇到'\0'并拷贝完为止。
?? ??? ?}
string(const string& s)
深浅拷贝
为什么会存在深拷贝的问题?
我们如果不实现深拷贝的拷贝构造会怎么样?
我们使用默认的拷贝构造来实现浅拷贝,把析构函数提前写上,我们看看会有什么样的结果?
我们发现这时候程序崩溃了。为什么呢?博主来画个图解释下就清楚了:
? ? ? ? 这时候我们就清楚了浅拷贝在这里对于内部成员变量有资源需要清理的情况是不行的,我们这时候就需要进行深拷贝解决问题。
如下图展示:
?传统写法
//传统写法 -- 本本分分完成深拷贝
string(const string& s)
{
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
?? ?//传统写法 -- 本本分分完成深拷贝 ?? ??? ?string(const string& s)//拷贝构造也是特殊的构造函数,调用拷贝构造,构造函数就不会调用了(这时候记得开辟空间来存储字符串)。 ?? ??? ?{ ?? ??? ??? ?_str = new char[strlen(s._str) + 1];//开辟一块新空间来存储字符串,记得为'\0'预留位置。 ?? ??? ??? ?strcpy(_str, s._str);//已构造好对象里面的字符串拷贝到为构造好的对象_str开辟好的空间中。 ?? ??? ?}
现代写法
//现代写法 -- 通过复用的方式完成深拷贝
//s2(s1)
string(const string& s)
:_str(nullptr) //注意这里_str要置空
{
string tmp(s._str);
swap(_str, tmp._str);
}
?? ??? ?//现代写法 -- 通过复用的方式完成深拷贝 ?? ??? ?//s2(s1) ?? ??? ?string(const string& s) ?? ??? ??? ?:_str(nullptr) //注意这里_str要置空 ?? ??? ?{ ?? ??? ??? ?string tmp(s._str); //调用构造函数,构造一个新对象tmp,这时候相当于又开辟了一块空间来存储字符串。 ?? ??? ??? ?swap(_str, tmp._str); //将_str和构造好的临时对象的tmp._str指针进行交换。这样_str相当于间接性的完成深拷贝了。同时原_str交给临时对象tmp,tmp会自动调用析构函数进行资源处理。但是,在这里_str是没有调用构造函数的,是没有在堆上开辟空间,这时候是随机值,当delete[] tmp._str时,就会把没有new出来的空间就进行释放空间,这时候程序会崩溃。这时候把_str提前置空指针就好了,释放nullptr相当于什么也不干。
我们来用图展示这个过程:
?? ?}
?string operator=(const string& s)
传统写法
string operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
?? ?//s1 = s3 ?? ?//s1 = s1?? ??? ?
? ? ? ?string operator=(const string& s) //传引用效率高 ?? ??? ?{ ?? ??? ??? ?if (this != &s) //?//s1 = s1? 避免当自己给自己赋值的时候,到最后把自己的空间给释放掉了。 ?? ??? ??? ?{ ?? ??? ??? ??? ?char* tmp = new char[strlen(s._str) + 1]; //开辟好空间,并+1预留一个'\0'的位置。先用一个指针记录开辟好空间的返回值,先不使用_str接收(使用的时候要提前释放_str空间),避免开辟空间失败后,导致因为自己提前释放_str,原来空间内容也没有了(新空间也没有开辟好)。 ?? ??? ??? ??? ?strcpy(tmp, s._str); ?? ??? ??? ??? ?delete[] _str; ?? ??? ??? ??? ?_str = tmp; ?? ??? ??? ?} ?? ??? ??? ?return *this; //返回构造好的对象 ?? ??? ?}
现代写法
方法一
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(_str, tmp._str);
}
return *this;
}
?? ??? ?string& operator=(const string& s) ?? ??? ?{ ?? ??? ??? ?if (this != &s)? //避免自己给自己赋值 ?? ??? ??? ?{ ?? ??? ??? ??? ?string tmp(s); //调用构造函数构造临时对象 ?? ??? ??? ??? ?swap(_str, tmp._str); //临时对象和以初始化好的对象进行资源的交换。同时临时对象tmp出了作用域后会自动调用析构函数,完成原来_str对应资源的清理。
?? ??? ??? ?} ?? ??? ??? ?return *this; //返回赋值好的对象 ?? ??? ?}
方法二
string& operator=(string s)
{
swap(_str, s._str);
return *this;
}
?? ?string& operator=(string s) //传参的时候提前拷贝构造一个临时对象s ?? ??? ?{ ?? ??? ??? ?swap(_str, s._str); //将临时对象的s._str与_str进行交换。同时临时对象s出了作用域后会自动调用析构函数,完成原来_str对应资源的清理。
?? ??? ??? ?return *this; ?? ??? ?}?
?~string()
~string()
{
delete[] _str;
_str = nullptr;
}
?? ??? ?~string() ?? ??? ?{ ?? ??? ??? ?delete[] _str;? //释放_str指向的空间,完成对应资源的清理。 ?? ??? ??? ?_str = nullptr; //_str置为空指针 ?? ??? ?}
string简易版完整代码
namespace cyq
{
class string
{
public:
//构造函数
string(const char* str = "")
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
//传统写法 -- 本本分分完成深拷贝
//string(const string& s)
//{
// _str = new char[strlen(s._str) + 1];
// strcpy(_str, s._str);
//}
s1 = s3
s1 = s1
//string operator=(const string& s)
//{
// if (this != &s)
// {
// char* tmp = new char[strlen(s._str) + 1];
// strcpy(tmp, s._str);
// delete[] _str;
// _str = tmp;
// }
// return *this;
//}
//现代写法 -- 通过复用的方式完成深拷贝
//s2(s1)
string(const string& s)
:_str(nullptr) //注意这里_str要置空
{
string tmp(s._str);
swap(_str, tmp._str);
}
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// string tmp(s);
// swap(_str, tmp._str);
// }
// return *this;
//}
string& operator=(string s)
{
swap(_str, s._str);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
};
}
string完整版模拟实现
? ? ? ?通过上面的介绍详细的解释了深浅拷贝的问题,有了上面的储备知识接下来就进入实现完整版的string模拟实现吧~可能和库里面的有些出入,因为我们去模拟实现它不是为了造更好的轮子,而是为了更加深刻的理解它。
成员变量
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos = -1;
?? ?private: ?? ??? ?char* _str; //_str指向在堆上开辟好空间的地址 ?? ??? ?size_t _size; //记录当前字符串中有效自负的个数(不包含'\0') ?? ??? ?size_t _capacity; //_str指向的空间的大小(不包含'\0') ?? ?public: ?? ??? ?static const size_t npos = -1; //设置一个全局变量npos = -1
迭代器
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str+_size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
?? ??? ?typedef char* iterator; //在string里面的迭代器是char*指针 ?? ??? ?typedef const char* const_iterator; //定义一个const版本的迭代器 ?? ??? ?const_iterator begin() const ?? ??? ?{ ?? ??? ??? ?return _str;? //返回字符串中第一个元素的地址 ?? ??? ?} ?? ??? ?const_iterator end() const ?? ??? ?{ ?? ??? ??? ?return _str+_size;? //返回字符串中最后一个元素的下一个位置的地址 ?? ??? ?} ?? ??? ?iterator begin() ?? ??? ?{ ?? ??? ??? ?return _str;??//返回字符串中第一个元素的地址 ?? ??? ?} ?? ??? ?iterator end() ?? ??? ?{ ?? ??? ??? ?return _str + _size;??//返回字符串中最后一个元素的下一个位置的地址 ?? ??? ?}
我们来画图表示一下这个左闭右开的区间:
? ? ? ? ? ? ??
?
string(const char* str = "")
//构造函数
string(const char* str = "")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity+ 1];
strcpy(_str, str);
}
?? ??? ?//构造函数 ?? ??? ?string(const char* str = "") ?? ??? ??? ?:_size(strlen(str)) //记录要构造字符串的长度 ?? ??? ??? ?,_capacity(_size) //记录容量(实际上是_size+1,在这里我们只记录有效字符个数) ?? ??? ?{ ?? ??? ??? ?_str = new char[_capacity+ 1];? // +1预留一个'\0'字符的空间 ?? ??? ??? ?strcpy(_str, str); //把要拷贝的字符串(str)拷贝到开辟好的空间中(_str) ?? ??? ?}
string(const string& s)
string(const string& s)
:_str(nullptr) //注意这里_str要置空
{
string tmp(s._str);
//swap(_str, tmp._str);
//swap(_size, tmp._size);
//swap(_capacity, tmp._capacity);
swap(tmp);
}
? ? ? ?//现代写法 -- 通过复用的方式完成深拷贝 ?? ??? //s2(s1)?? ???
? ? ? ?string(const string& s) ?? ??? ??? ?:_str(nullptr) //注意这里_str要置空,在上面已经介绍过原因了 ?? ??? ?{ ?? ??? ??? ?string tmp(s._str); //复用构造函数构造一个临时对象 ?? ??? ??? ?//swap(_str, tmp._str); //交换tmp和要构造对象中的_str ?? ??? ??? ?//swap(_size, tmp._size);?//交换tmp和要构造对象中的_size ?? ??? ??? ?//swap(_capacity, tmp._capacity);?//交换tmp和要构造对象中的_capacity ?? ??? ??? ?swap(tmp); //由于以上三行的swap和下面的赋值重载代码雷同,所以在这里定义一个swap函数来进行调用。 ?? ??? ?}
string& operator=(string s)
string& operator=(string s)
{
//swap(_str, s._str);
//swap(_size, s._size);
//swap(_capacity, s._capacity);
swap(s);
return *this;
}
? ? ? ?//现代写法 -- 通过复用的方式完成深拷贝?? ??? ?
? ? ? ?string& operator=(string s) //传参的时候调用了一次拷贝构造 ?? ??? ?{ ?? ??? ??? ?//swap(_str, s._str);?//交换tmp和要构造对象中的_str ?? ??? ??? ?//swap(_size, s._size);? //交换tmp和要构造对象中的_size ?? ??? ??? ?//swap(_capacity, s._capacity);? //交换tmp和要构造对象中的_capacity ?? ??? ??? ?swap(s); //交换s这个对象和要赋值对象的资源 ?? ??? ??? ?return *this;? //返回赋值好后的对象 ?? ??? ?}
void swap(string& s)
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
?? ??? ?void swap(string& s) //有一个隐含的this指针,对象s是右操作数 ?? ??? ?{ ?? ??? ??? ?std::swap(_str, s._str); //调用std命名空间中库里面的swap,这里一定要指定std,否则swap会识别错误 ?? ??? ??? ?std::swap(_size, s._size); //同上 ?? ??? ??? ?std::swap(_capacity, s._capacity); //同上 ?? ??? ?}
~string()
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
?? ??? ?~string()? //定义析构函数,完成对象中资源的清理 ?? ??? ?{ ?? ??? ??? ?delete[] _str; //释放掉_str指向的空间 ?? ??? ??? ?_str = nullptr; //_str指针置空 ?? ??? ??? ?_size = _capacity = 0; //对象的_size和_capacity置为0 ?? ??? ?}
const char* c_str() const?
const char* c_str() const //C形式的字符串
{
return _str;
}
?? ??? ?const char* c_str() const? ?//C形式的字符串 ?? ??? ?{ ?? ??? ??? ?return _str; //返回地址_str,这种形式打印遇到'\0'就终止打印 ?? ??? ?}
size_t size() const
size_t size() const
{
return _size;
}
?? ??? ?size_t size() const? //返回字符串中字符的个数(不包含最后的'\0') ?? ??? ?{ ?? ??? ??? ?return _size; //返回记录的_size ?? ??? ?}
size_t capacity() const
size_t capacity() const
{
return _capacity;
}
?? ?size_t capacity() const //返回_str指向的空间的大小(不包含预留的'\0'的这个1字节空间) ?? ??? ?{ ?? ??? ??? ?return _capacity; //返回记录的_capacity ?? ??? ?}
char& operator[](size_t pos)
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
?? ??? ?char& operator[](size_t pos)? //重载[],支持方括号遍历,可以像数组一样使用 ?? ??? ?{ ?? ??? ??? ?assert(pos < _size); //返回字符串中pos的位置要小于字符串的长度_size ?? ??? ??? ?return _str[pos]; //返回字符串中pos位置的那个字符,返回可以修改 ?? ??? ?}
const char& operator[](size_t pos) const
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
?? ??? ?const char& operator[](size_t pos) const //重载的const版本,const修饰的是this指针,按照对象是不是const类型来匹配 ?? ??? ?{ ?? ??? ??? ?assert(pos < _size); ?? ??? ??? ?return _str[pos]; //返回pos位置字符的引用不能被修改 ?? ??? ?}
void reserve(size_t n)
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) //开辟/追加空间到n个字符 ?? ??? ?{ ?? ??? ??? ?if (n > _capacity) //如果n大于原来的空间就进行增容,否则就不操作,一般的编译器实现也是这样,不会实现缩容。 ?? ??? ??? ?{ ?? ??? ??? ??? ?char* tmp = new char[n + 1]; //开辟n+1字节的空间,+1是为了给'\0'预留空间 ?? ??? ??? ??? ?strcpy(tmp, _str); //把_str指向的字符串内容拷贝到新空间tmp中 ?? ??? ??? ??? ?delete[] _str;//释放旧空间 ?? ??? ??? ??? ?_str = tmp; //_str指向指针指向的空间改成tmp ?? ??? ??? ??? ?_capacity = n; //增容后_capacity变成n ?? ??? ??? ?} ?? ??? ?}
void resize(size_t n, char ch = '\0')
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size, ch, n - _size);
_size = n;
_str[_size] = '\0';
}
}
?? ??? ?void resize(size_t n, char ch = '\0') //对字符串中的_size进行操作,改成n个,超出的用ch进行填充 ?? ??? ?{ ?? ??? ??? ?if (n <= _size) //如果指定的长度n小于等于原来长度,则不用填充传了,直接将_size更新到n,并在最后加上'\0' ?? ??? ??? ?{ ?? ??? ??? ??? ?_size = n; //更新_size的位置 ?? ??? ??? ??? ?_str[_size] = '\0'; //最后一个字符的下一个位置赋值成'\0'
?? ??? ??? ?} ?? ??? ??? ?else ?? ??? ??? ?{ ?? ??? ??? ??? ?if (n > _capacity) //如果resize的大小超过当前容量,这时候就需要扩容 ?? ??? ??? ??? ?{ ?? ??? ??? ??? ??? ?reserve(n); //把容量扩大到n (实际上是n+1)
?? ??? ??? ??? ?} ?? ??? ??? ??? ?memset(_str + _size, ch, n - _size); //从_str+size开始,往后n-_size的空间大小初始化成字符ch ?? ??? ??? ??? ?_size = n; //_size进行改变成n ?? ??? ??? ??? ?_str[_size] = '\0'; //_size(最后一个字符的下一个位置赋值成'\0')
?
?? ??? ??? ?} ?? ??? ?}
void push_back(char ch)
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
?? ??? ?void push_back(char ch) //在字符串后面插入字符ch ?? ??? ?{ ?? ??? ??? ?if (_size == _capacity) //如果_size==_capacity这时候放满了,需要扩容 ?? ??? ??? ?{ ?? ??? ??? ??? ?reserve(_capacity == 0 ? 4 : _capacity * 2);//注意_capacity==0的情况,这时候需要特殊处理,否则0*2还是0,_capacity还是没变。 ?? ??? ??? ?} ?? ??? ??? ?_str[_size] = ch; //_size的位置进行插入字符ch ?? ??? ??? ?_size++; //插入后字符个数++ ?? ??? ??? ?_str[_size] = '\0'; //在字符串最后插入'\0' ?? ??? ?}
void pop_back()
void pop_back()
{
assert(_size != 0);
_size--;
_str[_size] = '\0';
}
?? ??? ?void pop_back()? //尾删 ?? ??? ?{ ?? ??? ??? ?assert(_size != 0); //当_size!=0时,说明有字符就可以尾删 ?? ??? ??? ?_size--; //删除后字符个数-- ?? ??? ??? ?_str[_size] = '\0'; //最后一个字符得下一个位置给成'\0' ?? ??? ?}
void append(const char* str)
void append(const char* str)
{
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(_size + len); //扩容
}
strcpy(_str + _size, str);
_size += len;
}
?? ?void append(const char* str) //在字符串后面追加字符串 ?? ??? ?{ ?? ??? ??? ?size_t len = strlen(str); //记录str字符串的长度 ?? ??? ??? ?if (len + _size > _capacity) //如果插入字符后所需容量大于当前容量,则需要扩容 ?? ??? ??? ?{ ?? ??? ??? ??? ?reserve(_size + len); //扩容 ?? ??? ??? ?} ?? ??? ??? ?strcpy(_str + _size, str);//把字符串str追加到_str后面 ?? ??? ??? ?_size += len; //追加字符串后_str的_size进行更新(+len) ?? ??? ?}
string& operator+=(char ch)
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(char ch) //+=运算符进行重载,在字符串末尾追加一个字符ch ?{ ? ? ? ?push_back(ch); //复用push_back() ? ? ? ?return *this; //+=完之后返回 +=完之后的对象 ?}
string& operator+=(const char* str)
string& operator+=(const char* str)
{
append(str);
return *this;
}
?? ??? ?string& operator+=(const char* str) //在_str后面追加字符串str ?? ??? ?{ ?? ??? ??? ?append(str); //复用append() ?? ??? ??? ?return *this; //返回+= 完之后字符串的对象 ?? ??? ?}
size_t find(const char* s, size_t pos = 0)?
size_t find(const char* s, size_t pos = 0) //正向查找
{
const char* ptr = strstr(_str + pos, s); //查找子串
if (ptr == nullptr)
{
return npos;//-1
}
else
{
return ptr - _str; //画图分析
}
}
?? ??? ?size_t find(const char* s, size_t pos = 0) //正向查找字符串s,默认从下标为0的位置开始查找 ?? ??? ?{ ?? ??? ??? ?const char* ptr = strstr(_str + pos, s); //strstr 查找子串 ?? ??? ??? ?if (ptr == nullptr) //如果ptr ==nullptr,说明没有找到 ?? ??? ??? ?{ ?? ??? ??? ??? ?return npos;//-1 ?? ??? ??? ?} ?? ??? ??? ?else //找到了,返回找到的子串的第一个字符的地址 ?? ??? ??? ?{ ?? ??? ??? ??? ?return ptr - _str; //返回找到子串首字母的下标
? ? ? ? ? ? ? ? ? ?? ?? ??? ??? ?} ?? ??? ?}
string& insert(size_t pos, char ch)
string& insert(size_t pos, char ch)
{
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, char ch) //在pos位置之前插入字符ch ?? ??? ?{ ?? ??? ??? ?assert(pos <= _size); //pos位置<=_size,当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; //pos位置插入字符ch ?? ??? ??? ?_size++; //插入后_size个数+1 ?? ??? ??? ?return *this; //返回插入字符后的对象 ?? ??? ?}
?string& insert(size_t pos, const char* s) ?
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end - len >= pos)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
?? ??? ?string& insert(size_t pos, const char* s) //在pos位置之前插入字符串s ?? ??? ?{ ?? ??? ??? ?assert(pos <= _size); //pos位置要小于_size,等于_size相当于尾插 ?? ??? ??? ?size_t len = strlen(s); //记录插入的字符的长度 ?? ??? ??? ?if (_size + len > _capacity)?//如果插入字符后所需容量大于当前容量,则需要扩容 ?? ??? ??? ?{ ?? ??? ??? ??? ?reserve(_size + len); ?? ??? ??? ?} ?? ??? ??? ?size_t end = _size + len; ?? ??? ??? ?while (end - len >= pos) ?? ??? ??? ?{ ?? ??? ??? ??? ?_str[end] = _str[end - len]; ?? ??? ??? ??? ?end--; ?? ??? ??? ?}
下面我用图来解释这个比较复杂的过程:
?? ??? ??? ?strncpy(_str + pos, s, len); ?? ??? ??? ?_size += len; ?? ??? ??? ?return *this; ?? ??? ?}
string erase(size_t pos ,size_t len = npos)
string erase(size_t pos ,size_t len = npos) //从pos位置开始删除len个字符
{
assert(pos < _size);
if (pos + len >= _size || len == npos)
{
_size = pos;
_str[pos] = '\0';
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
?? ??? ?string erase(size_t pos ,size_t len = npos) //从pos位置开始删除len个字符 ?? ??? ?{ ?? ??? ??? ?assert(pos < _size); ?? ??? ??? ?if (pos + len >= _size || len == npos) //当从pos位置开始删除的字符的个数超过了当前字符串的末尾 ?? ??? ??? ?{ ?? ??? ??? ??? ?_size = pos; //_size直接变成下标pos ?? ??? ??? ??? ?_str[pos] = '\0';//pos位置给成'\0' ?? ??? ??? ?} ?? ??? ??? ?else ?? ??? ??? ?{ ?? ??? ??? ??? ?strcpy(_str + pos, _str + pos + len); //strcpy对本身字符串往前拷贝是没有问题的,往后拷贝是有问题的 ?? ??? ??? ??? ?_size -= len; //更新_size位置 ?? ??? ??? ?} ?? ??? ??? ?return *this; ?? ??? ?}
void clear() ?
void clear()
{
_str[0] = '\0';
_size = 0;
}
?? ??? ?void clear() //清楚当前对象中的字符串 ?? ??? ?{ ?? ??? ??? ?_str[0] = '\0'; //0位置处赋值成'\0',后面的不用处理就好了 ?? ??? ??? ?_size = 0;//直接把对象中字符串的有效字符个数给成0就好了 ?? ??? ?}
bool operator>(const string& s1, const string& s2)
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)
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)
bool operator>=(const string& s1, const string& s2)
{
//return !(strcmp(s1.c_str(), s2.c_str()) < 0);
return s1 > s2 || s1 == s2;
}
bool operator<(const string& s1, const string& s2)
bool operator<(const string& s1, const string& s2)
{
return !(s1 >= s2);
}
bool operator<=(const string& s1, const string& s2)
bool operator<=(const string& s1, const string& s2)
{
return !(s1 > s2);
}
bool operator!=(const string& s1, const string& s2) ?
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
? ? ? ? 以上的运算符重载比较大小由于比较简单在这里就不讲了,其实,他们之间都是在相互复用。
说明:其实上面的几个运算符重载可以搞成友元函数来处理。上面的情况是以c_str形式的字符串来比较,当然也可以用_str来比较。
ostream& operator<<(ostream& out, const string& s)?
ostream& operator<<(ostream& out, const string& s) //输出
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
?? ?ostream& operator<<(ostream& out, const string& s) //out是cout的引用,类型是ostream,定义在全局是为了避免抢参数位置的问题,因为out是必须要传的,这时候s默认传递作为左参数,out作为右参数,这和预期不符合:s<<out;很显然这种写法很反常规~ ?? ?{ ?? ??? ?for (size_t i = 0; i < s.size(); i++) //按照字符串中的_size个数来输出,和c_str是有区别的 ?? ??? ?{ ?? ??? ??? ?out << s[i]; ?? ??? ?} ?? ??? ?return out; //返回out(ostream),可以支持连续输出 ?? ?}
istream& operator>>(istream& in, string& s)
istream& operator>>(istream& in, string& s) //输入
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n') //只要有一个是' ' 或'\n'就不进入循环
{
s += ch;
ch = in.get();
}
return in;
}
?? ?istream& operator>>(istream& in, string& s) //in是cin的引用,定义在全局是为了避免抢参数位置的问题,因为in是必须要传的,这时候s默认传递作为左参数,in作为右参数,这和预期不符合:s>>in;很显然这种写法很反常规~ ?? ?{ ?? ??? ?s.clear(); //先清空原来对象中_str里面的字符串(不是释放空间) ?? ??? ?//in >> s.c_str;//这种方式不搞友元是不行的 ?? ??? ?char ch = in.get(); //一次读取一个字符,再进行下面的判断 ?? ??? ?while (ch != ' ' && ch != '\n')? //只要有一个是' ' 或'\n'就不进入循环 ?? ??? ?{ ?? ??? ??? ?s += ch; //不断尾插 ?? ??? ??? ?ch = in.get();// ch再不断读取字符 ?? ??? ?} ?? ??? ?return in; //返回in(istream),支持连续输入 ?? ?}
模拟完整版string的代码
string.h
namespace cyq
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str+_size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//构造函数
string(const char* str = "")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity+ 1];
strcpy(_str, str);
}
//现代写法 -- 通过复用的方式完成深拷贝
//s2(s1)
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_str(nullptr) //注意这里_str要置空
{
string tmp(s._str);
//swap(_str, tmp._str);
//swap(_size, tmp._size);
//swap(_capacity, tmp._capacity);
swap(tmp);
}
string& operator=(string s)
{
//swap(_str, s._str);
//swap(_size, s._size);
//swap(_capacity, s._capacity);
swap(s);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const //C形式的字符串
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
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)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size, ch, n - _size);
_size = n;
_str[_size] = '\0';
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void pop_back()
{
assert(_size != 0);
_size--;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (len + _size > _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;
}
size_t find(const char* s, size_t pos = 0) //正向查找
{
const char* ptr = strstr(_str + pos, s); //查找子串
if (ptr == nullptr)
{
return npos;//-1
}
else
{
return ptr - _str; //画图分析
}
}
string& insert(size_t pos, char ch)
{
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* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end - len >= pos)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
string erase(size_t pos ,size_t len = npos) //从pos位置开始删除len个字符
{
assert(pos < _size);
if (pos + len >= _size || len == npos)
{
_size = pos;
_str[pos] = '\0';
}
else
{
strcpy(_str + pos, _str + pos + len); //strcpy对本身字符串往前拷贝是没有问题的,往后拷贝是有问题的
_size -= len;
}
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos = -1;
};
//以下重载可以搞友元函数
//比较大小运算符重载
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);
return s1 > s2 || s1 == s2;
}
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);
}
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();
//in >> s.c_str;//不搞友元是不行的
char ch = in.get();
while (ch != ' ' && ch != '\n') //只要有一个是' ' 或'\n'就不进入循环
{
s += ch;
ch = in.get();
}
return in;
}
}
测试
我们来检测一下上面的代码是否正确:
我们把测试代码也写到cyq的命名空间中,可别看错了偶~
Test.c
void Test()
{
string s1("wmm");
string s2(s1);
string s3;
s3 = s2;
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << (s1 > s2) << endl;
cout << (s1 == s2) << endl;
//cin >> s3;
//cout << "输入后s3:" << s3 << endl;
s2.push_back('c');
//s2.pop_back();
s2.erase(0, 1);
cout << s2 << endl;
string s4("hello world");
s4.erase(0, 3);
s4 += "cyq";
cout << s4 << endl;
s4.insert(2, "wmm");
cout << s4 << endl;
cout << s4.find("world") << endl;
string::iterator it = s4.begin();
while (it != s4.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
运行结果:
?看到这里,希望读者们点赞+关注+收藏~ 支持一下~
|