STL中的string你了解吗
作者:TangguTae
STL(standard template libaray
-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL的六大组件:容器、迭代器、适配器、空间配置器、仿函数、算法。
string的行为与普通容器类似,但是并不能说它是个容器,因为无法接受所有类型的数据。
string是表示字符串的字符串类。
string在底层实际是:basic_string模板类的别名
typedef basic_string<char, char_traits, allocator>string;
头文件: #include<string>
模拟实现一个string类
首先成员变量最少需要一个字符类型的指针、字符串的大小、总共能存多少有效字符。
其次还需要构造函数、遍历的方法、增删查改、运算符重载等。
成员变量
class MyString { private: char *_str;//字符串指针 size_t _size;//字符串大小 size_t _capacity;//总共能存多少有效字符,不包括'\0' static size_t npos;//迭代器相关 } size_t MyString:: npos = -1;
构造函数
//构造函数 MyString(const char* str = "")//缺省参数 { _size = strlen(str);//初始化 _capacity = _size; _str = new char[_capacity + 1];//'\0'的空间+1 strcpy(_str, str); } //析构函数 ~MyString() { delete[] _str;//释放内存 _str = nullptr;//将指针置空 _size = 0;//清理工作 _capacity = 0; } //拷贝构造函数 MyString(const MyString& str) { _size = str._size; _capacity = str._capacity; _str = new char[_capacity + 1]; strcpy(_str, str._str); } //赋值运算符重载 MyString& operator=(const MyString& str) { if (_str != str._str) { delete[] _str; _size = str._size; _capacity = str._capacity; _str = new char[_capacity + 1]; strcpy(_str, str._str); } return *this; }
遍历
1、[ ]的重载
我们在C语言中使用字符串时是可以通过[ ]进行随机访问的,所以在设计string类时,通过重载[ ]实现相同的效果。
char& operator[](size_t index) { assert(index < _size&&index >= 0); return _str[index]; } const char& operator[](size_t index)const { assert(index < _size&&index >= 0); return _str[index]; }
需要两种类型的operator[ ],一个是针对非const类型对象,一个是针对const类型对象。const类型的对象是没有办法调用非const修饰*this的成员函数和重载,原因:权限扩大了。
2、迭代器
除了用[ ]来遍历类里面的字符串以外,另外的方法就是通过迭代器。
对于string的迭代器我们只需要宏定义一下
typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; }
测试一下代码
void test_string() { MyString ms; ms = "123"; MyString::iterator it = ms.begin(); while (it != ms.end()) { cout << *it << endl; it++; } }
rbegin与rend是反向迭代器,即反向遍历字符串。
前面带c的cbegin、cend等等是常字符串类型的对象
const iterator cbegin()const { return _str; } const iterator cend()const { return _str + _size; }
与容量相关的成员函数
实现几个比较常用的函数接口
//返回字符串大小 size_t size()const { return _size; } size_t capacity()const { return _capacity; } //判断是否为空字符串 bool empty()const { return _size == 0; } //更改容量 void reserve(size_t n = 0) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } //更改大小 //resize分三种情况 void resize(size_t n = 0,char ch = '\0') { if (n >= 0 && n <= _size) { _size = n; _str[_size] = '\0'; } else if (n > _size && n <= _capacity) { for (size_t i = _size; i < n; i++) _str[i] = ch; _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'; } else assert(0); }
size
、capacity
、empty
只需要设置成const类型,因为不需要修改内容。
reserve只修改_capacity的大小。
resize的实现需要分三种情况,当n的长度小于等于_size的时候,只需要修改一下_size的大小,然后把_size的位置设置为'\0'。当n的长度大于_size且小于_capacity的时候,需要新插入n-_size个ch;如果大于_capacity,说明需要重新开辟空间了,并插入n-_size个ch。
运算符的重载
1、+=的重载
平常用string类的时候发现+=进行字符串拼接很方便。
MyString& operator+=(const char* str) { int len = strlen(str); if (len + _size > _capacity)//判断是否超出容量 { reserve(len + _size); } strcpy(_str + _size, str); _size += len; return *this; } MyString& operator+=(char ch) { if (_size == _capacity)//扩容 { size_t newcapacity = (_capacity) == 0 ? 2 : _capacity * 2; reserve(newcapacity); } _str[_size] = ch; _size++; _str[_size] = '\0';//尾插过后会把'\0给覆盖了,重新在最后一个位置补一个'\0' return *this; }
2、<< 和 >>的重载
为了保持和标准输入输出的使用形式是一样的,建议在类外面重载<<和>>。
//需要在类外面重载 //输出流 ostream& operator<<(ostream& out, const MyString& str) { for (size_t i = 0; i < str.size(); i++) { out << str[i]; } return out; } //输入流 istream& operator>>(istream& in, MyString& str) { while (1) { char ch = in.get(); if (ch != ' '&&ch != '\n')//cin遇到空格和'\n'会结束 { str += ch; } else break; } return in; }
补充getline函数:遇到'\n'才结束
用法:getline(cin,对象);
//getline是遇到'\n'才结束 istream& getline(istream& in, MyString& s) { while (1) { char ch; ch = in.get();//从缓存去读入所有输入字符 if (ch != '\n') { s += ch; } else break; } return in; }
修改器
push_back尾插
void push_back(char ch)//插入一个字符,尾插 { if (_size == _capacity) { size_t newcapacity = (_capacity) == 0 ? 2 : _capacity * 2; reserve(newcapacity); } _str[_size] = ch; _size++; _str[_size] = '\0';//尾插过后会把'\0给覆盖了,重新在最后一个位置补一个'\0' }
insert任意位置插入字符或者字符串
MyString& insert(size_t pos, const char ch) { assert(pos <= _size && pos >= 0); if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 2 : 2 * _capacity; reserve(newcapacity); } int end = _size; while (end >= (int)pos)//为什么要强转,如果是头插,end最终=-1, { //-1和无符号比较会向无符号转变成一个32位的最大值,成为死循环 _str[end + 1] = _str[end]; end--; } _str[pos] = ch; _size++; return *this; } MyString& insert(size_t pos, const char* str) { assert(pos <= _size && pos >= 0); size_t len = strlen(str); if (_size+ len > _capacity) { reserve(_capacity + len); } int end = _size; while (end >= (int)pos)//往后挪 { _str[end + len] = _str[end]; end--; } for (size_t i = 0; i < len; i++) { _str[pos + i] = str[i]; } _size += len; return *this; }
erase删除
//npos = -1(无符号整型最大值) MyString& erase(size_t pos, size_t len = npos) { assert(pos >= 0 && pos < _size); if (pos + len >= _size || len == npos) { _size = pos; _str[_size] = '\0'; } else { for (size_t i = 0; i < _size - len - pos; i++) { _str[i + pos] = _str[i + pos + len]; } _size -= len; } _str[_size] = '\0'; return *this; }
可以看出删除和任意位置插入还是挺费时间的,需要整体挪动字符串。
常用的几个字符串函数
find
、substr
、c_str
也得掌握
find的接口比较多,可以查找string类、查找char*的字符串也可以查找单个字符
返回值为对应的下标,没找到返回npos。
substr获得一个子串,返回值为string类型
pos表示从哪里开始,len表示子串长度。
c_str 将C++的字符串类转化成C语言中char*类型。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!