目录
string类是C++STL中的序列形容器之一,它是动态类型的顺序表,只能存储char类型的字符。使用时需要包含头文件 "string"。
string类主中的操作主要分为六大模块,构造、容量、元素访问、迭代器、修改、特殊操作。
在本文中只讲解最常用的一些函数(因为它的函数实在太多了)。
本文由于是介绍string类函数的用法,因此绝大部分内容都在代码中,文字叙述较少。(本文代码均在win10系统下的vs2019验证)
string类对象常用的五种构造如下表:
函数名称 | 功能说明 |
string() | 构造空的string对象,即空字符串 |
string(const char* s) | 用C语言中的字符串构造string类对象 |
string(size_t n,char c) | string类对象中包含n个字符c |
string(const string& s) | 拷贝构造函数 |
string(const char* s,size_t n) | 用C语言字符串的前n个字符构造string类对象 |
代码一:在此代码中展示以上五种构造函数的使用方法。
代码一:可以看到string类对象可以直接用cin>>来接收键盘输入的字符串,也可以用cout直接输出string类对象的内容。但是使用cin>>直接接收时,碰到空格会停止接收。
- //代码一
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s1;//构造空的string对象,即空字符串
-
- const char* s = "abcde";//C语言中的字符串
- string s2(s);//用C_string来构造string类对象
-
- string s3(10, 'A');//string类对象中包含10个字符‘A’
-
- string s4(s3);//拷贝构造函数
-
- string s5(s, 3);//用C语言字符串的前3个字符构造string类对象
-
- cin >> s5;
- cout << s5;
- }
- int main() {
- Test();
- }
string类在容量这一模块中的操作较多,其中有一部分的函数还具有自己的特性需要进行分析。常用的容量相关的操作如下表:
函数名称 | 功能说明 |
size() | 返回字符串有效字符长度 |
length() | 返回字符串有效字符长度 |
capacity() | 返回总空间的大小 |
empty() | 检测string类对象是否是空的,是返回true,否则返回false |
clear() | 清空有效字符 |
reserve(size_t n) | 为string类对象总空间大小进行重新设置 |
resize(size_t n,char c) | 将有效字符个数修改为参数中的n个,多出的空间用参数中的c填充,如果没有第二个参数,填充为0 |
注意:总空间是指string对象中一共有多少空间(容量)可以用来存储char类型数据,有效元素个数是指在总空间中有多少空间都存放了有效的char类型数据。
前五个函数较为简单,利用下述代码来展示用法:
代码二:s1.capacity() == 15 注意这个15,下面这个是要讲一讲的。
- //代码二
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s1("abcde");
- cout << s1.size() << endl;//输出 5
- cout << s1.length() << endl;//输出 5
- cout << s1.capacity() << endl;//输出 15
- cout << s1.empty() << endl;//输出 0
- s1.clear();
- cout << s1.empty() << endl;//输出 1
- }
- int main() {
- Test();
- }
reserve(size_t n)这个函数的作用是根据参数n来对string类对象的容量进行合适的改变(增长或缩小)。这个合适的就很有意思了,因为并不是n等于多少,它就一定把容量变成多少。
首先来看一下string类的大小,如代码三:
代码三:string类的大小是28,那么string类中都有什么?答案是:一个char*类型的变量,一个size_t类型的size,一个size_t类型的capacity,并且还维护了一个大小是16的数组(但在我的测试中发现,如果string类对象的字符数小于等于15个,容量是15。当字符数等于16时,容量变成31,发生扩容,所以推测第16个字节应该是为了存储'\0')。
- //代码三
- #include "iostream"
- #include "string"
- using namespace std;
-
- int main() {
- cout << sizeof(string) << endl;//输出 28
- }
用下面两段代码验证 reserve(size_t n) 合适的扩容机制;
代码四:当初始字符串中元素个数小于等于15时的扩容机制。
这段代码较长,但要仔细看完。此时字符串长度是10,可以看到,扩容后的容量并不总是和传递的实参相等,通常情况下是要大一些的。
还有一个现象是,在将参数n缩小后,发现刚开始的时候容量依然保持70不变,当n缩小到15后,容量变为15。当n的大小小于字符串长度后,容量保持不变,一直是15。
同时观察扩容的容量大小增加情况:31 -> 47 -> 70。(说明vs2019中大概是按1.5倍的方式递增)
- //代码四
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s1("abcde12345");
-
- s1.reserve(20);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//31
-
- s1.reserve(30);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//31
-
- s1.reserve(40);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//47
-
- s1.reserve(50);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//70
-
- s1.reserve(60);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//70
-
- s1.reserve(50);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//70
-
- s1.reserve(40);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//70
-
- s1.reserve(30);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//70
-
- s1.reserve(20);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//70
-
- s1.reserve(15);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//15
-
- s1.reserve(12);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//15
-
- s1.reserve(9);
- cout << s1.size() << endl;//10
- cout << s1.capacity() << endl;//15
- }
-
- int main() {
- Test();
- }
代码五:当初始字符串中元素个数大于15时的扩容机制。
此时字符串长度是0。可以看到,在n逐渐增加的过程中,在vs2019中也基本按照1.5倍大小扩容。但是在这一次缩小到15后,容量并没有变小。下面来总结一下它的性质。
- //代码五
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s1("abcde12345abcde12345");
-
- s1.reserve(20);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//31
-
- s1.reserve(30);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//31
-
- s1.reserve(40);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//47
-
- s1.reserve(50);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(60);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(50);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(40);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(30);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(20);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(15);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(12);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
-
- s1.reserve(9);
- cout << s1.size() << endl;//20
- cout << s1.capacity() << endl;//70
- }
-
- int main() {
- Test();
- }
1. reserve(size_t n) 不会改变有效元素的个数。(这就是为什么代码五中当n变为15时,容量不变,要是容量变成15,有效元素不就变少了吗?)
2.reserve(size_t newcapacity),假设当前容量是 oldcapacity。分情况总结:
newcapacity > oldcapacity:reserve函数扩容。(在vs2019中大概按照1.5倍扩容)
newcapacity < oldcapacity:<1>字符串有效长度 <= 15,将容量缩小为15。<2>字符串有效长度 > 15,容量保持不变。
有没有发现这几条性质都要围绕15这个数字展开,就是因为之前说过的,string类对象内部存在一个定长char类型数组,可以存放15个有效字符。当字符串要扩容,就要去堆上申请空间。当空间要缩小时,很多时候并不能成功,原因就是,申请空间不容易,编译器为了防止缩小后又扩容浪费时间,所以一般情况下就不会缩小。
上面的reserve只是用来管理容量,现在的resize是专门用来管理有效字符长度的。
它有两种使用方法:1.resize(size_t n) 将有效元素字符修改成n个,多出的空间用'\0'填充。2.resize(size_t n,char c)将有效字符修改为n个,多处的空间用字符c填充。
代码六:
- //代码六
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s1("123");
- s1.resize(10,'d');
- cout << s1.size() << endl;// 10
-
- s1.resize(2);
- cout << s1.size() << endl;// 2
- }
-
- int main() {
- Test();
- }
在STL初级阶段,我们就把迭代器当作指针来使用即可。
函数名称 | 功能说明 |
begin() | 返回string对象首字符的迭代器 |
end() | 返回string对象最后一个字符下一个位置的迭代器 |
rbegin() | 返回string对象最后一个字符的迭代器 |
rend() | 返回string对象首字符前一个位置的迭代器 |
下图是正反迭代器的示意图:
代码七:既然把迭代器当作指针使用,自然是可以解引用的。
- //代码七
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s("abcde");
- auto it1 = s.begin();
- auto it2 = s.end();
- auto it3 = s.rbegin();
- auto it4 = s.end();
- while (it1 != it2) {
- cout << *it1 << " ";
- it1++;
- }
- }
-
- int main() {
- Test();//输出 a b c d e
- }
函数名称 | 功能说明 |
operatoe[size_t pos] | 返回pos位置的字符,const string类对象调用 |
at(szie_t pos) | 返回pos位置的字符 |
范围for | 和数组中的范围for使用方法相同 |
代码八:注意,当string对象用const修饰时,不可以使用 s[0]='k' 及类似的方式修改string对象。因为const对象是不可以修改的。
- //代码八
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s("abcde");
- cout << s[0] << endl;//输出 a
- cout << s.at(0) << endl;//输出 a
- for (auto& e : s) {
- cout << e;
- }//输出 abcde
- s[0] = 'k';
- }
-
- int main() {
- Test();
- }
-
函数说明 | 功能说明 |
push_back(c) | 在string类对象后尾插字符c |
append(const char* s) | 在string类对象后追加字符串s |
append(size_t n,char c) | 在string类对象后追加n个字符c |
operator += c 或 str 或 string类对象 | 在string类对象后追加字符c 或 字符串str 或 string类对象 |
insert(iterator it,char c) | 在迭代器it的位置插入字符c |
insert(size_t pos,const char* s) | 在下标为pos的位置插入字符串s |
substr(size_t pos,size_t n) | 从string类对象的pos位置开始向后复制n个元素 |
earse(size_t pos,size_t n) | 在string类对象从pos位置开始向后删除n个元素 |
erase(iterator firest,iterator last) | 在string类对象中删除 [first,last) 之间的元素,注意区间是左闭右开,last位置不能删除。 |
代码九:
- //代码九
- #include "iostream"
- #include "string"
- using namespace std;
-
- //插入
- void Test1() {
- string s1("123");
- string s2("89");
- const char* str = "56";
-
- s1.push_back('4');
-
- s1.append(str);
- s1.append(1, '7');
-
- s1 += s2;
- s1 += '0';
-
- cout << s1 << endl;//输出 1234567890
-
- const char* st = "bc";
- s1.insert(s1.begin(), 'a');
- s1.insert(1, st);
- cout << s1 << endl;//输出 abc1234567890
- }
-
- //复制子串与删除
- void Test2() {
- string s1("1234567");
- string s2 = s1.substr(0, 7);
- cout << s2 << endl;//输出 1234567
-
- s1.erase(0, 5);
- cout << s1 << endl;//输出 67
- s2.erase(s2.begin(), s2.end() - 3);
- cout << s2 << endl;//输出 567
- }
-
- int main() {
- Test1();
- Test2();
- }
函数说明 | 功能说明 |
find(char c,size_t pos) 和 npos | 在string类对象从pos位置开始向后查找字符c,遇见第一个c返回下标,若没找到返回npos 如果pos没有传递,默认从0下标开始查找 |
find(const string& s,size_t pos) | 在string对象中从pos位置开始向后查找子串s出现的首个位置的下标,找不到返回npos |
rfind(char c,size_t pos) | 在string类对象从pos位置开始向前查找字符c,遇见第一个c返回下标,若没找到返回npos 若pos没有传递,默认从尾元素开始 |
c_str() | 返回一个指向正规C字符串的指针常量, 内容与本string串相同。(为了兼容C语言) |
atoi(const char* s) | 将字符串s转换为对应的整型变量 |
sort(iterator firest,iterator last) | 把string类对象 [first,last) 之间的元素按大小排序 |
代码十:在使用函数时,一定要特别注意参数究竟是普通类型还是const类型。
- //代码十
- #define _CRT_SECURE_NO_WARNINGS
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s1("1234");
- cout << s1.find('2', 0) << endl;//输出 1
-
- const string s2("23");
- cout << s1.find(s2, 0) << endl;//输出1
-
- //rfind与find区别仅是方向的区别,故不做演示
-
- const char* str = s1.c_str();
- int num1 = atoi(str);
- cout << num1 << endl;//输出 1234
-
- //也可以利用strcpy函数
- char ss[100];
- strcpy(ss, s1.c_str());
- int num2 = atoi(str);
- cout << num2 << endl;//输出 1234
- }
-
- int main() {
- Test();
- }
代码十一:使用cin接收时,遇到空格等空白字符和回车后停止接收。可以使用cout直接打印。
- //代码十一
- #include "iostream"
- #include "string"
- using namespace std;
-
- void Test() {
- string s;
- cin >> s;
- cout << s;
- }
-
- int main() {
- Test();
- }
代码十二:在刷oj题中经常会遇到需要循环输入的题目,这时候就需要掌握相应的使用方法。
- //代码十二
- #include "iostream"
- #include "string"
- using namespace std;
-
- //循环接收单个单词并打印
- void Test1() {
- string word;
- while (cin >> word) {
- cout << word <
- }
- //截止输入 ctrl + z 回车
- }
-
- //循环接收带有空格的字符串并打印
- void Test2() {
- string words;
- while (getline(cin,words)) {
- cout << words << endl;
- }
- //截止输入 ctrl + z 回车
- }
-
- int main() {
- Test1();
- Test2();
- }