🔑🔑博客主页:阿客不是客
🍓🍓系列专栏:从C语言到C++语言的渐深学习
欢迎来到泊舟小课堂
😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注
一、模拟实现 string 库
1.1 string 的成员变量
string 简单来说就是一个被封装可动态增长的字符数组,这与我们在数据结构中学的串非常类似,所以我们可以借助实现串的思路来大致模拟 string 的结构。
下面是string的成员变量:
namespace betty
{
class string
{
public:
//...
private:
size_t _size;//当前有效字符的个数
size_t _capacity;//当前容量的大小,方便扩容
char* _str;//存储的字符串
};
}
值得注意的是\0
既不占据有效长度的大小,也不占据容量的大小。
1.2 string的构造和销毁(析构)
💬 string 构造函数的实现:
我们先来模拟实现一个不带参的构造函数,也被称为默认构造:
string()//无参初始化
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}
这里我们开一个空间给 \0,既然都这么写了,我们不如直接加入缺省值实现带参的构造:
string(const char* str = "")
{
_size = strlen(str);
// capacity不包括 \0
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
一般的类都是提供全缺省的,值得注意的是,这里缺省值给的是 " "
有人看到指针 char* 在缺省值的位置就忍不住想给个空 nullptr:
string(const char* str = nullptr)
不能给!给了就崩。因为 strlen 是不会去检查空的,它是去找 \0 ,也就相当于直接对这个字符串进行解引用了,这里的字符串又是空,所以会引发空指针问题。所以我们这里给的是一个空的字符串 " ",常量字符串默认就带有 \0,这样就不会出问题。
我们有了构造,那么同时也要有销毁,销毁需要一个析构函数。
💬 ~string 析构函数的实现:
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
1.3 返回字符指针c_str
📚 c_str() 在库中是返回的是C语言字符串的指针常量,是可读不写的。
实现方式很简单:
/* 返回C格式字符串:c_str */
const char* c_str() const
{
return _str;
}
const char*,因为是可读不可写的,所以我们需要用 const 修饰。c_str 返回的是当前字符串的首字符地址,这里我们直接 return _str 即可实现。
我们来测试一下:
void TestString1()
{
string s1("hello world");
string s2;
cout << s1.c_str() << endl;
}
🚩 运行结果如下:
1.4 size() 和 operator[] 的实现
💬 size() 的实现:
size_t size() const
{
return _size;
}
size() 只需要返回成员函数 _size 即可,考虑到不需要修改,我们加上 const,capacity() 同理。
💬 capacity() 的实现:
size_t capacity() const
{
return _capacity;
}
💬 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];
}
直接返回字符串对应下标位置的元素,因为返回的是一个字符,所以我们这里引用返回 char。
我们来测试一下,遍历整个字符串:
void TestString2()
{
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
}
🚩 运行结果如下:
我们再来测试一下 operator[] 的 "写" 功能:
void TestString1()
{
string s1("123456");
cout << s1.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
s1[i] += 2;
}
cout << s1.c_str() << endl;
}
🚩 运行结果如下:
要注意的是:
- 普通对象可以调用,但是 const 对象呢?所以我们还需要写一个 const 对象的重载版本
- 我们需要考虑一下越界的问题,这里我们使用断言暴力处理一下
二、实现迭代器
2.1 迭代器的实现
在上一章中,我们首次讲解迭代器,为了方便理解,我们当时解释其为像指针一样的类型。实际上,迭代器并非指针,而是类模板。 只是它表现地像指针,模拟了指针的部分功能。
对于 string 类型的迭代器的实现非常简单,它就是一个 char* 的指针罢了,后面我们讲解 list 的时候它不是指针了,又是自定义类型了。所以迭代器其实只是一个类模板,根据要实现的功能来决定。
💬 实现迭代器的 begin() 和 end() :
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
💬 我们来测试一下:
void TestString1()
{
string s1;
string s2("123456");
cout << s2.c_str() << endl;
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
🚩 运行结果如下:
const 迭代器就是可以读但是不可以写的迭代器。
💬 const 迭代器:
typedef char* const_iterator;
const const_iterator begin() const
{
return _str;
}
const const_iterator end() const
{
return _str + _size;
}
迭代器的底层是连续地物理空间,给原生指针++解引用能正好贴合迭代器的行为,就能做到遍历。但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。但是,强大的迭代器通过统一的封装,无论是树、链表还是数组……它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处。
2.2 范围for
上一章讲 string 类对象的遍历时,我们讲的第三种方式就是范围 for,我们现在就来演示一下范围 for 的实现:
for (auto e : s1)
{
cout << e << " ";
}
cout << endl;
你会发现根本就不需要自己实现,你只要把迭代器实现好,范围 for 直接就可以用。范围 for 的本质是由迭代器支持的,编译时范围 for 会被替换成迭代器。
三、string 的增删查改
3.1 reserve 的实现
💬 我们先实现一下 reserve 增容:
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
这里可以检查一下是否真的需要增容,万一接收的 n 比 _capacity 小,就不动。
这里我们之前讲数据结构用的是 realloc,现在我们熟悉熟悉用 new,还是用申请新空间、原空间数据拷贝到新空间,再释放空间地方式去扩容。我们的 _capacity 存储的是有效字符,没算 \0,所以这里还要 +1 为 \0 开一个空间。
3.2 push_back 的实现
💬 push_back:
void string::push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
首先检查是否需要增容,如果需要就调用我们上面实现的 reserve 函数,参数传递可以用三目操作符,防止容量是0的情况,0乘任何数都是0从而引发问题的情况。然后在 \0 处插入要追加的字符 append_ch,然后 _size++ 并手动添加一个新的 \0 即可。
我们来测试一下效果如何:
void TestString2()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back('!');
cout << s1.c_str() << endl;
s1.push_back('A');
cout << s1.c_str() << endl;
}
🚩 运行结果如下:
3.3 append 的实现
💬 append:
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 大于2倍要多少扩多少,小于2倍扩2倍
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
append 是追加字符串的,首先我们把要追加的字符串长度计算出来,然后看容量够不够,不够我们就交给 reserve 去扩容,大于容量2倍要多少扩多少,小于2倍扩2倍。
3.4 operator+= 的实现
这就是我们一章说的 "用起来爽到飞起" 的 += ,因为字符和字符串都可以用 += 去操作,所以我们需要两个重载版本,一个是字符的,一个是字符串的。我们不需要自己实现了,直接复用 push_back 和 append 就好了。
💬 operator+=
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
3.5 insert 的实现
💬 insert:字符
string& string::insert(size_t pos, char c)
{
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] = c;
_size++;
return *this;
}
💬 insert:字符串
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
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;
}
测试一下:
void TestString3()
{
string s1 = "hello world";
s1.insert(1, '#');
cout << s1.c_str() << endl;
s1.insert(0, "*******");
cout << s1.c_str() << endl;
}
🚩 运行结果如下:
insert 都实现了,那 push_back 和 append 直接复用,岂不美哉?
⚡ 修改 push_back 和 append:
void string::push_back(char c)
{
//if (_size == _capacity)
//{
// reserve(_capacity == 0 ? 4 : _capacity * 2);
//}
//_str[_size] = c;
//_size++;
//_str[_size] = '\0';
insert(_size, c);
}
void string::append(const char* str)
{
//size_t len = strlen(str);
//if (_size + len > _capacity)
//{
// // 大于2倍要多少扩多少,小于2倍扩2倍
// reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
3.6 erase 的实现
删除指定位置之后长度为 len 的字符,如果超过字符串末尾,则删除到最后一个字符为止
💬 erase:
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
//while (pos + len <= _size)
//{
// _str[pos] = _str[pos + len];
// pos++;
//}
_size -= len;
}
return *this;
}
测试一下:
void TestString4()
{
string s1 = "hello world";
cout << s1.c_str() << endl;
s1.erase(0, 1);
cout << s1.c_str() << endl;
s1.erase(1, 2);
cout << s1.c_str() << endl;
s1.erase(1, 6);
cout << s1.c_str() << endl;
s1.erase(0, 2);
cout << s1.c_str() << endl;
}
🚩 运行结果如下:
3.7 resize 的实现
我们为了扩容,先实现了 reverse,现在我们再顺便实现一下 resize。这里再提一下 reverse 和 resize 的区别:resize 分给初始值和不给初始值的情况,所以有两种:
但是我们上面讲构造函数的时候说过,我们可以使用全缺省的方式,这样就可以二合一了resize 实现的难点是要考虑种种情况,我们来举个例子分析一下:
如果欲增容量比 _size 小的情况:
因为标准库是没有缩容的,所以我们实现的时候也不考虑去缩容。我们可以加一个 \0 去截断。
如果预增容量比 _size 大的情况:
resize 是开空间 + 初始化,开空间的工作我们就可以交给已经实现好的 reserve,然后再写 resize 的初始化的功能,我们这里可以使用 memset 函数。
💬 resize:
void string::resize(size_t n, char c)
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
// 起始位置 初始化字符 初始化个数
memset(_str + _size, c, n - _size);
_size = n;
_str[_size] = '\0';
}
}
3.8 find 的实现
find 是用于查找字符或者字符串,返回对应下标,我们先来实现较为简单的查找字符的功能。
💬 find:查找字符
size_t string::find(char c, size_t pos) const
{
if (pos >= _size)
return npos;
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
遍历整个字符串,找到了目标字符 ch 就返回对应的下标。如果遍历完整个字符串都没找到,就返回 npos(找到库的来)。
💬 这个 npos 我们可以在成员变量中定义:
...
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
};
const size_t string::npos = -1; // 无符号整型的-1,即整型的最大值。
...
}
💬 find:查找字符串
size_t string::find(const char* s, size_t pos) const
{
if (pos >= _size)
return npos;
size_t len = strlen(s);
for (size_t i = pos; i <= _size - len; ++i)
{
if (!strncmp(&_str[i], s, len))
return i;
}
return npos;
}
strncmp是用来比较字符串大小的,小于返回<0,大于返回>0,相等返回 0。
3.9 clear 的实现
💬 clear:清理string
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
3.10 empty 的实现
💬 empty:判断字符串是否为空,为空返回 true,不为空返回 false。
bool string::empty() const
{
return _size == 0;
}
四、运算符重载
4.1 operator< 的实现
💬 我们在全局实现:
bool string::operator<(const string& s)
{
if (_size < s._size)
return true;
if (_size > s._size)
return false;
for (size_t i = 0; i < _size; i++)
{
if (_str[i] < s._str[i])
return true;
if (_str[i] > s._str[i])
return false;
}
return false;
}
当然,我们还可以实现的更简单些,直接用 strcmp 偷个懒:
bool string::operator<(const string& s)
{
return strcmp(_str, s.c_str()) < 0;
}
4.2 operator> 的实现
bool string::operator>(const string& s)
{
//if (_size > s._size)
// return true;
//if (_size < s._size)
// return false;
//for (size_t i = 0; i < _size; i++)
//{
// if (_str[i] > s._str[i])
// return true;
// if (_str[i] < s._str[i])
// return false;
//}
//return false;
return strcmp(_str, s.c_str()) > 0;
}
4.3 复用
有了上面两种的基础,剩下的都可以直接进行复用:
bool string::operator<=(const string& s)
{
return !(*this > s);
}
bool string::operator>=(const string& s)
{
return !(*this < s);
}
bool string::operator==(const string& s)
{
return !(*this < s) && !(*this > s);
}
bool string::operator!=(const string& s)
{
return !(*this == s);
}
五、流插入与流提取
我们当时实现日期类的流插入和流提取时,也详细讲过这些,当时讲解了友元。在友元那一章我们说过 "占参问题" ,这里就不再多做解释了。
如果我们重载成成员函数,第一个位置就会被隐含的 this 指针占据。这样实现出来的流插入必然会不符合我们的使用习惯,所以我们选择在全局实现。在全局里不存在隐含的 this 指针了。
💬 operator<<
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
💬 operator>>
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
六、深浅拷贝
6.1 引入
我们前面完成了string类型的构造和析构,但是这里的写法其实是有一个很大的问题的,不知道同学们发现了没有,让我们看看下列的测试案例:
void TestString9()
{
string s1 = "hello world";
cout << s1 << endl;
}
🚩 顺利编译通过:
再看另一种情况:
void TestString9()
{
string s1 = "hello world";
string s2(s1);
cout << s1 << endl << s2 << endl;
}
🚩 代码发生崩溃(提示语句中的返回值):
🔑 详细解析:
打印的时候还没有上什么问题,但当出作用域的时候需要调用析构:
❓ 如何解决这样的问题呢?
我们 s2 拷贝构造你 s1,本意并不是想跟你指向一块空间!我们的本意是想让 s2 有一块自己的空间,并且能内容是 s1 里的 hello world
所以这里就涉及到了深浅拷贝的问题,我们下面就来探讨一下深浅拷贝的问题。
6.2 深浅拷贝问题
举个最简单的例子 —— 拷贝就像是在抄作业!
浅拷贝:直接无脑照抄,连名字都不改。
(直接把内存无脑指过去)
深拷贝:聪明地抄,抄的像是我自己写的一样。
(开一块一样大的空间,再把数据拷贝下来,指向我自己开的空间)
- 浅拷贝就是 原封不动 地把成员变量按字节依次拷贝过去。
- 深拷贝就是进行深一个层次的拷贝,不是直接拷贝,而是 拷贝你指向的空间。
6.3 拷贝构造的实现
我们之前实现日期类的时候,用自动生成的拷贝构造(浅拷贝)是可以的,所以当时我们不用自己实现拷贝构造,让它默认生成就足够了。但是像 string 这样的类,它的拷贝构造我们不得不亲自写。
💬 string 的拷贝构造:
string(const string& s)
{
_str = new char[s._capacity];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
6.4 赋值的深拷贝
💬 现在有一个 s3,如果我们想把 s3 赋值给 s1:
void TestString8()
{
string s1 = "hello world";
string s2(s1); // 拷贝构造
string s3 = "bac";
s1 = s3; // 赋值
cout << s1 << endl << s2 << endl << s3 << endl;
}
如果你不自己实现赋值,就和之前一样,会是浅拷贝,也会造成崩溃:
所以,我们仍然需要自己实现一个 operator= ,实现思路如下:
💬 代码实现 operator=
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
🔑 代码解析:
- 根据我们的实现思路,首先释放原有空间,然后开辟新的空间,最后把 s3 的值赋值给 s1。
- 为了防止自己给自己赋值,我们可以判断一下。
- 这时我们还要考虑一个难以发现的问题,如果 new 失败了怎么办?
- 抛异常!抛异常!抛异常!失败了没问题,也不会走到 strcpy,但问题是我们已经把原有的空间释放掉了,神不知鬼不觉地,走到析构那里二次释放可能会炸,所以我们得解决这个问题!
七、传统写法和现代写法
7.1 拷贝构造的现代写法
现在我们来介绍一种现代写法,它和传统写法本质工作是一样的,即完成深拷贝。现代写法的方式不是本本分分地去按着 Step 一步步干活,而是 "投机取巧" 地去完成深拷贝。
string(const string& s)
: _str(nullptr)
{
string tmp(s._str);
swap(tmp);
}
为此,我们还要为 string 类设计一个 swap :
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
现代写法的本质就是复用了构造函数。我想拷贝,但我又不想自己干,我把活交给工具人 swap 来帮我干,交换了指向空间的指针。
❓ 我们为什么要在初始化列表中,给 _str 个空指针:
我们可以设想一下,如果我们不对他进行处理,那么它的默认指向会是个随机值。
这样交换看上去没啥问题,确实能完成深拷贝,但是会引发一个隐患!
tmp 是一个局部对象,我们把 s2 原来的指针和 tmp 交换了,那么 tmp 就成了个随机值了。tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,怎么释放?都不是你自己的 new / malloc 出来的,你还硬要对它释放,就可能会引发崩溃。
我们这里初始化列表中把 nullptr 给 _str,是为了交换完之后, nullptr 能交到 tmp 手中,这样 tmp 出了作用域调用析构函数就不会翻车了。
7.2 赋值重载的现代写法
💬 现代写法:复用拷贝构造:
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}
我们先通过 s3 拷贝构造出 tmp,这样 tmp 就是 _str 的工具人了。交换完之后,正好让 tmp 出作用域调用析构函数,属实是一石二鸟的美事。把 tmp 压榨的干干净净,还让 tmp 帮忙把屁股擦干净(释放空间)。
⚡ 还有更简洁的写法:将拷贝构造放在缺省值里面
string& operator=(string tmp)
{
if (this != &s)
{
swap(tmp);
}
return *this;
}
和上面的写法本质是一样的。这种写法不用引用传参,它利用了拷贝构造。
总结:
传统写法和现代写法就是交换 内容 和交换 指向内容的指针 的区别
现代写法在 string 中体现的优势还不够大,因为好像和传统写法差不多,那是因为string 在底层实现只是一个 char* 的类型,可以使用 strcpy 进行交换,只是多了两个大小和容量的成员变量。
但是到后面我们实现 vector、list 的时候,你会发现现代写法的优势真的是太大了。现代写法写起来会更简单些,比如如果是个链表,传统写法就不是 strcpy 这么简单的了,你还要一个一个结点使用 strcpy或者其他的办法 将 next 结点拷贝过去,但是现代写法只需要调用 swap 交换一下首元素的结点就可以了。
现代写法更加简洁,只是在 string 这里优势体现的不明显罢了,我们后面可以慢慢体会。
八、完整代码
💬 string.h:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<cassert>
using namespace std;
namespace Bit
{
class string
{
public:
typedef char* iterator;
typedef char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const const_iterator begin() const
{
return _str;
}
const const_iterator end() const
{
return _str + _size;
}
// 默认构造
//string()//无参初始化
// :_str(new char[1]{'\0'})
// ,_size(0)
// ,_capacity(0)
//{}
string(const char* str = "")
{
_size = strlen(str);
// capacity不包括 \0
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 深拷贝问题
// 拷贝构造
/*string(const string& s)
{
_str = new char[s._capacity];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
//现代写法:
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
// 赋值
/*string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
// 现代写法
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// string tmp(s._str);
// swap(tmp);
// }
// return *this;
//}
// 最终简化版
// 拷贝构造一定要引用,不然会产生自己调用自己
// 赋值可以调用拷贝构造,可以不用引用
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
const char* c_str() const
{
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];
}
// string的增删查改
void reserve(size_t n);
void push_back(char c);
void append(const char* str);
string& operator+=(char c);
string& operator+=(const char* str);
// 在pos位置上插入 字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
// 从pos位置上删除len个字符
string& erase(size_t pos, size_t len = npos);
void resize(size_t n, char c = '\0');
// 返回 字符c/子串s 在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
void clear();
void swap(string& s);
bool empty()const;
bool operator<(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator==(const string& s);
bool operator!=(const string& s);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
static const size_t npos = -1;
};
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& out, string& s);
}
💬 string.cpp:
#include"string.h"
namespace Bit
{
// static const size_t npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char c)
{
//if (_size == _capacity)
//{
// reserve(_capacity == 0 ? 4 : _capacity * 2);
//}
//_str[_size] = c;
//_size++;
//_str[_size] = '\0';
insert(_size, c);
}
void string::append(const char* str)
{
//size_t len = strlen(str);
//if (_size + len > _capacity)
//{
// // 大于2倍要多少扩多少,小于2倍扩2倍
// reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
string& string::insert(size_t pos, char c)
{
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] = c;
_size++;
return *this;
}
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
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& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
//while (pos + len <= _size)
//{
// _str[pos] = _str[pos + len];
// pos++;
//}
_size -= len;
}
return *this;
}
void string::resize(size_t n, char c)
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
// 起始位置 初始化字符 初始化个数
memset(_str + _size, c, n - _size);
_size = n;
_str[_size] = '\0';
}
}
size_t string::find(char c, size_t pos) const
{
if (pos >= _size)
return npos;
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
size_t string::find(const char* s, size_t pos) const
{
if (pos >= _size)
return npos;
size_t len = strlen(s);
for (size_t i = pos; i <= _size - len; ++i)
{
if (!strncmp(&_str[i], s, len))
return i;
}
return npos;
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
bool string::empty() const
{
return _size == 0;
}
bool string::operator<(const string& s)
{
//if (_size < s._size)
// return true;
//if (_size > s._size)
// return false;
//for (size_t i = 0; i < _size; i++)
//{
// if (_str[i] < s._str[i])
// return true;
// if (_str[i] > s._str[i])
// return false;
//}
//return false;
return strcmp(_str, s.c_str()) < 0;
}
bool string::operator>(const string& s)
{
//if (_size > s._size)
// return true;
//if (_size < s._size)
// return false;
//for (size_t i = 0; i < _size; i++)
//{
// if (_str[i] > s._str[i])
// return true;
// if (_str[i] < s._str[i])
// return false;
//}
//return false;
return strcmp(_str, s.c_str()) > 0;
}
bool string::operator<=(const string& s)
{
return !(*this > s);
}
bool string::operator>=(const string& s)
{
return !(*this < s);
}
bool string::operator==(const string& s)
{
return !(*this < s) && !(*this > s);
}
bool string::operator!=(const string& s)
{
return !(*this == s);
}
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();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
}