目录
1. 遍历
1.1. 下标+operator[ ]
1.2. c_str
1.3. 迭代器
1.4. 范围for
2. 增
2.1. push_back
2.2. 重载+=(char ch)
2.3. appand
2.4. 重载+=(char* ch)
2.5. insert(任意位置插入)
2.5.1. 任意位置插入一个字符
2.5.2. 任意位置插入一个字符串
3. 删
3.1. earse
情况一:len==npos说明要全部删除完
情况二:并不全部删除完
4. 查
4.1. 查找一个字符
4.2. 查找一个字符串
4.3. 字符串比较
5. 改
5.1. reserve
5.2. resize
情况一:resize的n大于当前字符串空间长度(注意是空间长度)
情况二:resize的n小于_capacity,但是大于_size
情况三:resize的n小于当前字符串长度
上一篇文章我们讲解了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';}}
(本篇完)