目录
1.构造
2.容量
(1)reserve使用及性质验证
[1]扩容机制验证
[2]扩容机制总结
(2)resize使用及性质验证
3.迭代器
4.元素访问
5.修改
6.特殊操作
7.string类的输入输出
(1)支持cin和cout
(2)在oj中的使用
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 <