前言
在C语言中,字符串是以\0结尾的一些字符的集合,为了方便操作,C标准库中提供了一些str系列的库函数。但是这些库函数与字符串是分离开的,不太符合面向对象(OOP)的思想,而且底层空间需要用户自己去管理,稍不留神可能就会出现越界访问。为了解决上面的这些问题,C++中引入了string类,它给我们带来了极大的便利。
一、介绍
string是表示字符串的字符串类。
该类的接口与常规容器(vector、list等)的接口基本相同,再添加了一些专门用来操作string的常规操作。
string在底层实际是:用basic_string模板类实例化出来的一个类,typedef basic_string<char> string;。
不能操作多字节或者变长字符的序列。
小Tips:在使用string类时,需要包含头文件#include <string>,以及使用using namespace std展开命名空间。
二、string类的常用接口说明
2.1 常见的构造接口
该类的默认构造函数,用于构造空的string类对象,即空字符串。
int main() { string s1; cout << s1 << endl; return 0; }
小Tips:string类对象支持流插入和流提取,下文将进行介绍,这里大家直接使用即可。
用C-string来构造string类对象,即用一个C的字符串(或字符数组)来构造一个string
类的对象。
int main() { string s1("Hello C++!"); cout << s1 << endl; return 0; }
用n个字符c来构建一个string类对象。
int main() { string s1(5, 'x'); cout << s1 << endl; return 0; }
string类的拷贝构造,用于构建一个和已存在的string类对象s一模一样的对象。
int main() { string s1(5, 'x'); string s2(s1); cout << s1 << endl; cout << s2 << endl; return 0; }
📖string (const string& str, size_t pos, size_t len = npos)
复制str中从字符位置pos开始并跨越len字符的部分(如果 str 太短或 len 是字符串npos,则直到 str 的末尾)。简单来说就是使用一个已存在的string类对象的一部分来创建一个新的string类对象。
小Tips:nops是string类里面的一个静态成员变量,它是size_t类型,初始化为-1,即表示整型的最大值。此值如果在string的成员函数中作为形参len的缺省值,表示到字符串结束。如果作为string类中成员函数的返回值,一般表示没有匹配项。
int main() { string s1("Hello C++!"); string s2(s1, 0, 5);//用s1的部分来初始化创建s2 cout << s1 << endl; cout << s2 << endl; return 0; }
注意:对一个string类对象来说,它的第一个有效字符的下标是0。
📖string (const char* s, size_t n)
用s所指向字符串(或字符数组)的前n个来初始化创建一个string
类对象。
int main() { char str[] = "Hello C++!"; string s1(str, 5);//用字符数组str的前5个字符来构建一个string类对象 cout << str << endl; cout << s1 << endl; return 0; }
2.2 与容量有关的接口
返回字符串的有效字符长度。
int main() { string s1("Hello C++!"); string s2("Good morning!"); cout << "s1的size:" << s1.size() << endl; cout << "s2的size:" << s2.size() << endl; return 0; }
返回字符串的有效字符长度。
int main() { string s1("Hello C++!"); string s2("Good morning!"); cout << "s1的length:" << s1.length() << endl; cout << "s2的length:" << s2.length() << endl; return 0; }
小Tips:从上面的打印结果可以看出,size()和length()接口的功能一模一样,甚至底层实现原理也完全相同,都是返回字符串的有效字符个数,最初只有length()接口,引入size()接口的原因是为了与其他容器的接口保持以致,一般情况下基本都使用size()接口。这也从侧面说明string诞生的时间比STL要早。
📖capacity()
返回一个string对象中空间的大小。
int main() { string s1("Hello C++!"); string s2("Good morning chunren!"); cout << "s1的capacity:" << s1.capacity() << endl; cout << "s2的capacity:" << s2.capacity() << endl; return 0; }
小Tips:同一个string对象,在不同平台下的capacity()(空间容量)可能不同,因为string在底层就是一个存储字符的动态顺序表,空间不够了要进行扩容,而不同平台底层的扩容机制可能有所不同,这就导致了最终capacity()的结果不同。将上面的代码放到Linux环境下使用g++编译器再来试试:
小Tips:capacity()返回一个string对象中空间的大小,这个空间指的是可以存储有效字符的空间,底层实际上的空间会多一个,因为还要存储\0。
📖VS下的扩容机制
int main() { string s1("Hello!"); size_t old = s1.capacity(); cout << s1.capacity() << endl; for (size_t i = 0; i < 100; i++) { s1 += 'v'; if (old != s1.capacity()) { cout << "扩容:" << s1.capacity() << endl; old = s1.capacity(); } } return 0; }
VS下一上来会有15个空间用来存储数据(本质上是开16个空间,因为还要存\0),第一次扩容是2倍,后面都是以1.5倍的大小去扩容。
📖Linux下的扩容机制
int main() { string s1("Hello!"); size_t old = s1.capacity(); cout << s1.capacity() << endl; for (size_t i = 0; i < 100; i++) { s1 += 'v'; if (old != s1.capacity()) { cout << "扩容:" << s1.capacity() << endl; old = s1.capacity(); } } return 0; }
将上面的代码放到Linux环境下使用g++编译器再来试试:
可见在Linux下,最初对象需要多少空间就开多少,后面一次按照2倍的大小进行扩容。
检测字符串是否为空串,是返回true,否则返回false
。
int main() { string s1;// if (s1.empty()) { cout << "s1是一个空串" << endl; } return 0; }
清空有效字符。
int main() { string s1("Hello C++!"); cout << "清空之前的size:" << s1.size() << endl; cout << "清空之前的capacity:" << s1.capacity() << endl; s1.clear();//清空 cout << "清空之后的size:" << s1.size() << endl; cout << "清空之后的capacity:" << s1.capacity() << endl; return 0; }
小Tips:从打印结果可以看出,clear()清空操作不会影响capacity()容量,也就是说string对象并不会主动的去缩容。
📖reserve (size_t n = 0)
为字符串预留空间。直接一次申请n个空间,可以用来存储n个有效字符,避免了每次都要去扩容。大部分的扩容都是异地扩容,扩容次数过多会影响效率。
int main() { string s1; s1.reserve(100);//知道要尾插100个字符就先直接申请100个,避免后面再去扩容 size_t old = s1.capacity(); cout << s1.capacity() << endl; for (size_t i = 0; i < 100; i++) { s1 += 'v'; if (old != s1.capacity()) { cout << "扩容:" << s1.capacity() << endl; old = s1.capacity(); } } s1.reserve(20); cout << "第二次执行reserve(20):" << s1.capacity() << endl; return 0; }
小Tips:实际申请到的有效空间可能比我们需要的多,但是一定不可能比我们需要的少,如s1.reserve(100)去申请100个有效空间,但实际上申请了111个有效空间。当n小于当前对象的容量时,一般的编译器都不会执行缩容。(视具体情况而定)
将字符串大小调整为n个长度的大小,当n小于当前字符串的长度size(),会保留前n个字符,将第n个字符后面的所以字符删除;当n大于当前字符串的长度size(),先会进行扩容,以满足存储n个有效字符的需求,如果指定了字符c,会将新元素初始化为c的副本,否则全部初始化为空字符,即\0。
int main() { string s1("Hello C++!"); cout << "最初的s1.size():" << s1.size() << endl; cout << "最初的s1.capacity():" << s1.capacity() << endl; string s2 = s1;//将s1拷贝一份 s1.reserve(100); cout << "reserve(100)后的s1.size():" << s1.size() << endl; cout << "reserve(100)后的s1.capacity():" << s1.capacity() << endl; s2.resize(100); cout << "resize(100)后的s1.size():" << s2.size() << endl; cout << "resize(100)后的s1.capacity():" << s2.capacity() << endl; return 0; }
小Tips:reserve和resize的区别可总结为:前者只会影响容量,负责申请空间,不会改变字符串的有效字符个数,即会改变capacity,不会改变size;后者在申请空间的基础上还会对空间进行初始化,这样就会对有效字符的个数产生影响,所以它即会改变capacity也会改变size