1.string类简介
现在用法是将string类当成STL用,但实际上string类与STL并没有关系。事实上C语言的标准库配套了str系列的字符串函数,可惜这些函数跟字符串本身是分离开的,那么就不符合面向对象的思想。
所以在C++中,直接把字符串归成了一个类来管理,也就是说string类是属于C++的标准库的。那么string类设计了诸多有用的公用接口以及各类的函数重载,如果真要从头数到尾估计有一两百个吧(我没数过,瞎猜的)。string类是一个模板的实例,这样的设计呢是因为编码的存在。
编码就是类似于ASCLL码的这样的一套用来表示字符的规则,计算机是美国人发明的,那么理所应当编码只能表示数字和英文。但随着计算机的发展,人对于计算机的要求越来越高,那么编码只支持英文就不太合适了,所以就诞生出了诸多编码,而我们使用string类的原生模板的实例化类型是char,因为它使用的编码是utf-8,简单来说,这套编码不仅支持数字和英文,还支持汉字。
因为string是专门管理字符串的,所以很多的设计思维不能同其他容器那样,就比如我们要统计字符串的大小时通常指的是有效字符的长度,而其他数据却考虑的不是长度,而是这些数据所占据的空间。所以为了设计上的统一,绝大部分的接口设计与其他容器的接口相同。
因为string类是标准库里面的东西,所用使用string类的时候要添加头文件 <string>,并且还要放开命名空间。
2.实用容量接口
我们知道描述字符串的大小通常是以长度来描述的,所以在早期的string中是有一个专门的函数用来返回字符串长度的公用接口函数的:
string s1 = "Nice to meet you.";
int len = s1.length();//返回字符串长度的函数
cout << len << endl;
但是后来由于STL的引入,就造成了string的接口与其他容器格格不入,所以添加了 size() 这个功能与 length() 完全一致的函数,其目的就是为了向其他容器统一:
string s1 = "Nice to meet you.";
//int len = s1.length();//返回字符串长度的函数
int len = s1.size();
cout << len << endl;
string类还提供了可以改变有效字符的个数的函数,这样的函数有两个,分别为 clear()和 resize()。注意:这两个函数是改变有效字符个数的函数,而不是改变其容量大小的函数。
事实上我更推荐大家使用resize(),因为resize()需要我们手动添加一个参数,将这个参数置0与clear()的效果是一样的。并且resize()不仅能够减小有效字符的个数,还能自定义添加有效字符的个数:
string s2 = "hello world";
s2.resize(6);//s2的有效字符个数为6
int sz = s2.size();
s2.resize(9, 'c');//增加有效字符个数
//对比之前多出来的几个有效字符,我们可以指定它
cout << s2 << endl;
因为有效字符个数的改变,那么其必定会影响容量大小。影响的行为为:当resize()添加有效字符个数时,如果超出了容量的范围,那么容量就会进行扩容。
string s3 = "1234";
int sz = s3.size();
cout << "当前size为:" << sz << " " << "初始容量为:" << s3.capacity() << endl;
while (s3.capacity() < 1000)
{
int tmp = s3.capacity();
sz += 20;
s3.resize(sz);
if (tmp != s3.capacity())
{
cout << "当前size为:"<<sz<<" " << "容量改变为:" << s3.capacity() << endl;
}
}
}
Visua Studio 2022 环境下,容量呈1.5倍增长。
但是当有效字符个数减少时,容量是不会改变的:
string s3 = "1234";
int sz = s3.size();
sz += 1000;
s3.resize(sz);
while (sz > 0)
{
sz -= 50;
cout << "当前size为:" << sz << " " << "容量为:" << s3.capacity() << endl;
}
3.实用遍历方式
对于string来说,正常的遍历有三种:
string s4 = "12345";
//遍历方式1:使用 [] 运算符重载
for (int i = 0; i < s4.size(); i++)
{
cout << s4[i] << " ";
}
cout << endl;
//遍历方式2:使用范围for
for (auto& tmp : s4)
{
cout << tmp << " ";
}
cout << endl;
//遍历方式3:使用迭代器
for (auto i = s4.begin(); i < s4.end(); i++)
{
cout << *i << " ";//迭代器的行为可以是指针
//使用的时候当成指针用就可以
}
cout << endl;
事实上我更推荐用迭代器遍历,因为这样可以与其他容器匹配。因为对于list容器,如果我么使用 [] 运算符重载的方式遍历,这种感觉就很怪。但是在实际使用的时候用的最多的还是[]运算符重载。
如果我们想要倒序输出的话就可以使用反向迭代器了:
for (auto i = s4.rbegin(); i < s4.rend(); i++)
{
cout << *i << " ";
}
cout << endl;
4.实用修改接口
我们模拟实现过顺序表,尾插的话会实用PushBack()函数,stirng也有这个函数:
string s5 = "hello ";
s5.push_back('w');
s5.push_back('o');
//……
但是这样只能每次只能尾插一个字符,这就很累赘了。所以我们用的最多的便是 +=运算符重载。
string s5 = "hello ";
s5 += "world";
cout << s5 << endl;
我个人觉得最实用的还得是find()接口。其作用是寻找对应的字符,找到了就返回对应的位置,找不到就返回npos。我们实用简单的代码来描述这个接口的功能:
string s6 = "Nice to meet you.";
int prev_pos = 0;
int after_pos = 0;
while ((after_pos = s6.find(" ", after_pos)) != string::npos)
{
/*for (auto i = s6.begin() + prev_pos; i < s6.begin() + after_pos; i++)
{
cout << *i;
}
cout << endl;*/
cout << s6.substr(prev_pos, after_pos - prev_pos) << endl;
prev_pos = ++after_pos;
}
cout << s6.substr(prev_pos, after_pos - prev_pos) << endl;
有这样的工具那就来做一道题:反转字符串中的单词
class Solution {
public:
string reverseWords(string s) {
int prev_pos=0;
int after_pos=0;
while((after_pos=s.find(" ",after_pos))!=string::npos)
{
reverse(s.begin()+prev_pos,s.begin()+after_pos);
prev_pos=++after_pos;
}
reverse(s.begin()+prev_pos,s.end());
return s;
}
};