string的介绍
string是一个类,可以看成是一个用模板写的顺序表,它的底层结构和顺序表基本是一样的,一个字符指针和一个表示存储数据的个数的size,还有一个表示容量大小的capacity,我们知道C++需要兼容C语言,所以它的字符串后面也是需要有一个‘\0’,的但是size和capacity的大小是不包含这个‘\0’的,因此实际的容量要比capacity大一个。
- 字符串是表示字符序列的类。
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
- string是表示字符串的字符串类。
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
- 不能操作多字节或者变长字符的序列。
结构
string的结构和顺序表很相似,一个存放字符的指针,还有一个标记个数的size,还有一个标记容量的capacity。
class { private: char* _str; size_t _size; size_t _capacity; }
string的常用接口
构造和析构
构造函数我们可以用个缺省参数,直接搞定无参和有参的情况,然后使用初始化列表把size和capacity给初始化一下,然后开个空间把参数拷贝过去就可以了。
析构函数的话很简单,直接释放空间即可,把其他值置一下0即可。
//构造 string(const char* s = "") : _size(strlen(s)) , _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, s); } //析构 ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
遍历string
- 我们可以使用for循环来遍历
因为库里的string支持了[]运算符重载,我们可以就像遍历数组那样来遍历string。
int main() { string s("123456"); for (int i = 0; i < 6; i++) { cout << s[i] << " "; } cout << endl; return 0; }
因为它是传引用返回的,所以我们可读可写。它重载了一个const版本,所以当你传const对象时,就只能读不能写。
int main() { string s("123456"); for (int i = 0; i < 6; i++) { s[i]++; } cout << s << endl; return 0; }
- 使用迭代器遍历
迭代器我们可以理解为一个指针,然后需要一个范围,库里有几个函数,可以帮我们确定这个范围。
如果我们要正着遍历可以使用begin和end。
它的返回值是一个iterator的迭代器类型,然后这个类型在string类里面的,所以我们需要确定类域。
int main() { string s("123456"); string::iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; it++; } cout << endl; return 0; }
如果要倒着遍历,就需要使用了,rbegin和rend。
只不过他们的返回类型是reverse_iterator。
int main() { string s("123456"); string::reverse_iterator it = s.rbegin(); while (it != s.rend()) { cout << *it << " "; it++; } cout << endl; return 0; }
这两对都是可读可写的,还剩两对前面带c的,是只能读,不能写。具体可戳标题详解。
- 使用范围for
int main() { string s("123456"); for (auto e : s) { cout << e << " "; } cout << endl; return 0; } int main()
size和length
size和length的功能是一样的,都是返回字符串的长度,和C语言的strlen很相似。
int main() { string s("123456"); cout << s.size() << endl; cout << s.length() << endl; return 0; }
capacity
capacity是返回已经开辟的容量的大小。
int main() { string s("123456"); cout << s.capacity() << endl; return 0; }
模拟实现
const size_t size() const { return _size; } const size_t capacity() const { return _capacity; }
resize和reserve
- resize
resize可以修改size的大小。
库里重载两个版本,resize分3个版本,它可能比capacity大,也可能在size和capacity中间,也有可能比size小。如果他比capacity大的话,会扩容,并且如果我们给字符参数,就会在我们原本的字符串后面全补我们给的那个字符,直到size变成我们设置的那个,我们没给字符的话,会补‘\0’,如果在size和capacity中间,不会扩容,但是还是会补充,如果比我们的size还小的话,就充当删除的功能了。
int main() { string s("123456"); s.resize(20); s.resize(20,'x'); s.resize(10); s.resize(4); return 0; }
- reserve
reserve可以设置capacity的大小,如果我们知道字符串多大的话,我们可以提前开好空间,因为让系统自己自动扩容的话,可能会频繁扩容,消耗太大了,所以我们在知道需要存储的字符串多大时可以提起开好空间。
int main() { string s("123456"); cout << s.capacity() << endl; s.reserve(100); cout << s.capacity() << endl; return 0; }
模拟实现
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 c = '\0') { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); size_t begin = _size; _size = n; while (begin < n) { _str[begin] = c; begin++; } _size = n; _str[begin] = '\0'; } }