
目录
情况一:resize的n大于当前字符串空间长度(注意是空间长度)
情况二:resize的n小于_capacity,但是大于_size
上一篇文章我们讲解了string类的构造、拷贝构造、赋值及其模拟实现
下面是我们本篇文章的主要内容:

1. 遍历
1.1. 下标+operator[ ]
char& operator[](size_t pos) { assert(pos < strlen(_str)); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < strlen(_str)); return _str[pos]; } int main() { string s1("hello world"); for (int i = 0; i < s1.size(); ++i) { cout << s1[i]; } return 0; }
1.2. c_str
也就是说c_str返回的值指向该字符串并且包含“\0”的字符序列,使用c_str( )来打印字符串,当碰到“\0”时就停止(这是因为C/C++中字符串处理函数(如
printf,cout,strcpy等)都遵循一个约定:将以null字符('\0')作为字符串的结束标志)。const char* c_str() const { return _str; }
1.3. 迭代器
迭代器:string的迭代器的底层其实就是一个char*的原生指针,所以使用string迭代器只需要像使用普通指针一样即可。但是其他容器的底层不应该否是原生指针。
typedef char* iterator; typedef const char* const_iterator; iterator begin() { return _str; } iterator begin() const { return _str; } iterator end() { return _str + _size; } iterator end() const { return _str + _size; }然后我们使用迭代器来进行字符串的遍历:
int main() { s::string s1("Hello World"); s::string::iterator it = s1.begin(); while (it != s1.end()) { cout << *it << ""; it++; } }PS:这个地方要注意的是要注意使用的是C++string类中的迭代器还是我们自定义类string中的迭代器。
1.4. 范围for
for的使用:
for (auto e : s1) { cout << e << " "; }
其实范围for的底层机制同样是一个迭代器,我们可以通过下面的方式进行验证,我们将迭代器给注释掉,我们来看一下发生什么:
2. 增
2.1. push_back
可以看到,push_back的作用是将一个字符添加到原有字符串后面,具体步骤:
- 我们先要判断时候需要扩容:如果size==capacity,说明满了。扩容:开辟一个两倍内存的新空间,然后原有字符串拷贝至新空间,释放原空间
- 添加的新字符应该放在size的位置
- 然后处理“\0”即可
2.2. 重载+=(char ch)
但是我们在日常写代码中并不会经常使用push_back,而是使用 += ,所以我们来重载一下 += ,它的实现中也可以服用push_back:
string& operator+=( char ch) { push_back(ch); return *this; }
2.3. appand
这里可以注意到,前面两个接口都是将单个字符添加到已有字符串结尾,而这个接口是将一个字符串添加到原来的字符串,步骤如下:
- 首先需要考虑要不要扩容,而且这里的扩容不能只是简单地将空间变为两倍,因为这样并不能保证空间足够。应该是将原字符串长度和新添加字符串长度相加,这样得到的空间就一定能满足要求,这里我们使用reserve函数(见下面的5.1)
- 然后更新 _size
void append(const char* str) { size_t len = _size + strlen(str); if (len > _capacity) { reserve(len); } strcpy(_str + _size, str); _size = len; //insert(_size, str); }
2.4. 重载+=(char* ch)
与单个字符的重载复用push_back一样,复用appand接口:
string& operator+=(const char* str) { append(str); return *this; }
2.5. insert(任意位置插入)
2.5.1. 任意位置插入一个字符
- 首先判断pos位置是否合法
- 然后判断是否需要扩容
- 然后从结尾的"\0"开始,从后往前一次向后移动一位,到pos为止
- 插入新字符,然后更新_size
string& insert(size_t pos, char ch) { assert(pos <= _size); if (_capacity == _size) { reserve(_capacity == 0 ? 4 : _capacity * 2); } size_t end = _size + 1; while (end > pos) { _str[end - 1] = _str[end]; --end; } _str[pos] = ch; ++_size; return *this; }2.5.2. 任意位置插入一个字符串
- 插入位置是否合法
- 判断是否需要扩容,这里需要扩大到原字符串长度+插入字符串长度
- 将原字符串包括pos在内的后面所有字符(包括"\0")往后移动len=strlen(ch)位
- 然后将新字符串插入,然后更新_size
string& insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (len == 0) { return *this; } if (_size + len > _capacity) { reserve(_size + len); } //挪动数据 size_t end = _size + len; //while(end >= end+len) while (end > pos+len-1) { _str[end] = _str[end-len]; --end; } //插入数据 size_t i = 0; while (i < len) { _str[pos+i] = str[i]; ++i; } _size += len; return *this; }
3. 删
3.1. earse
我们看这里提到了一个参数npos:
所以可以知道,earse的作用就是从pos开始,往后len个长度,把这些字符删去。
情况一:len==npos说明要全部删除完
那其实我们逻辑上删除它只需要将pos位置赋值"\0"即可:
情况二:并不全部删除完
首先找到需要删除的子字符串的后一位,将其定为begin,然后我们需要做的就是将begin后面的所有字符全部往前移动,覆盖掉需要删除的字符:
string& earse(size_t pos, size_t len = std::string::npos) { assert(pos < _size); if (len == std::string::npos || len + pos >= _size) { _str[pos] = '\0'; _size = pos; return *this; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; ++begin; } _size -= len; return *this; } }
4. 查
4.1. 查找一个字符
find查找字符串,返回查到的第一个满足的字符:
size_t find(char ch, size_t pos = 0) { for (; pos < _size; ++pos) { if (_str[pos] == ch) { return pos; } } return std::string::npos; }
4.2. 查找一个字符串
size_t find(const char* ch, size_t pos) { const char* p = strstr(_str + pos, ch); if (nullptr == p) { return std::string::npos; } else { return p - _str; } }
4.3. 字符串比较
bool operator < (const string& s) { return strcmp(this->c_str(), s.c_str()) < 0; } bool operator == (const s::string& s) { return strcmp(this->c_str(), s.c_str()) == 0; } bool operator<=(const string& s) { return *this < s || *this == s; } bool operator>(const string& s) { return !(*this <= s); } bool operator>=(const string& s) { return !(*this < s); } bool operator!=(const string& s) { return !(*this == s); }对于这段代码有疑问的可以看下【C++】类和对象--类中6个默认成员函数(2) --运算符重载,这里涉及到成员函数隐藏this指针的问题。
5. 改
5.1. reserve
reserve的作用就是是个string的容量变成n:
- 如果n小于等于_capacity,则不进行扩容
- 否则开辟一个容量为n的char*,然后进行拷贝,注意释放掉原有的内存
void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
5.2. resize
resize是将字符串大小修改为n。
resize和reserve的区别是:reserve只对空间进行处理,但是resize不仅对空间进行影响,而且会改变_size的值。
情况一:resize的n大于当前字符串空间长度(注意是空间长度)
看下面这个例子,假设空间长度为15,字符串长度为11:
也就是扩容了之后会使用字符串参数填充满:
情况二:resize的n小于_capacity,但是大于_size
这种情况下不需要扩容,所以_capacity不会变:
情况三:resize的n小于当前字符串长度
这个时候只会保存原字符串的前n个字符:
代码如下:
//扩空间+初始化 //删除部分数据,保留前n个 void resize(size_t n, char ch = '\0') { if (n < _size) { _size = n; _str[_size] = '\0'; } else { if (n > _capacity) { reserve(n); } for (size_t i = _size; i < n; ++i) { _str[i] = ch; } _size = n; _str[_size] = '\0'; } }
(本篇完)





























