一、基本介绍
1.string类是STL库里面比较常见的一个数据结构,string是表示字符串的字符串类 ,该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
2.string在底层实际是:basic_string模板类的别名,typedef basic_string string;
3.不能操作多字节或者变长字符的序列。
4.在使用string类时,必须包含#include头文件以及using namespace std;
二、string类的常见接口
1.string类常见的构造
函数名称 | 功能说明 | 例子 |
string() | 构造空的string类对象,即空字符串 | string s1; |
string(const char*s) | 用常量字符串类构造string类对象 | string s2("hello world"); |
string(size_t , char c) | string类对象中包含n个字符c | string s3(5,'*'); |
string(const string&s) | 拷贝构造函数 | string s4(s2); |
2.string类对象的容量操作
函数名称 | 功能说明 |
size() | 返回字符串有效字符长度 |
capacity() | 返回空间总大小 |
empty() | 检测字符串是否为空串,是返回true,否则返回false |
clear() | 清空有效字符 |
reserve(size_t) | 可用于提前开辟空间,减少扩容,提高效率,在进行字符串修改操作时,如果能够预先知道要使用多大的空间,建议提前开好空间,避免扩容 |
resize(size_t n , const char c) | 将有效字符数改成n个,多出来的空间由字符c填充,不给c则默认为0 |
注意:
1.clear()只是将string中有效字符清空,不改变底层空间大小。
2.size决定有效长度,而不是‘\0’决定,capacity决定实际开辟空间的大小
3.关于resize的使用,resize改变的是有效长度,若改变后大于原本长度,可能会发生扩容,也可能不会,看size是否大于capacity,容是小于原本长度,有效长度缩减,但实际开辟的空间大小capacity不会缩减。在扩大有效长度后,多出来的空间,若是给了第二个参数,则根据第二个参数所给的字符进行初始化,若没给则是默认用'\0'初始化,缩小有效长度后,有效长度以外的同样按以上原则初始化
4.reserve改变的是实际大小,而不改变有效长度,若是给的参数小于原先的capacity,则不发生任何改变,当大于capacity时,则按照给定值进行扩容(至少会等于给定值,也有可能扩容后会大于给定值,根据编译器不同所不同)。
3. string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] | 返回pos位置的字符,const string类对象调用 |
begin + end | begin获取第一个字符的迭代器+end获取最后一个字符下一个位置的迭代器,迭代器对应的类型有const_iterator 和 iterator |
rbegin + rend | 反向迭代器,可以实现从字符串最后一个遍历到第一个 对应的类型是 reverse_iterator 和 const_reverse_iterator 为了方便,可以使用auto(自动识别类型) |
范围for | C++11支持更加简洁的范围for的遍历auto |
例子:用迭代器遍历字符串(begin和end)
例子:用范围for去遍历字符串
for(auto ch: s1)
{
cout << ch << '/';
}
例子:下标访问
for(int i=0;i<s1.size;i++)
{
cout << s1[i] << '/';
}
4. string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后面尾插一个字符 |
append | 在字符串后面尾插一个字符串 |
operator+= | 可以用来直接代替上面两个的功能,推荐使用+=去实现尾插 |
c_str ( ) | 返回c格式的字符串,此时有效长度按“\0”决定 |
find ( str,pos) | 第一个参数str是要查找的字符或者字符串,pos是查找的起始位置(默认从第一个开开始找),返回目标字符串第一次出现的下标,若是没找到则返回(size_t)string::npos |
rfind | 从字符串最后一个字符往前开始查找,找到返回下标,没找到返回string::npos |
substr(pos,nub) | 从pos位置开始截取nub个字符,若是不给nub,则默认截取到字符串结束,返回一个截取后的新字符串 |
例子:截取test.cpp的后缀
int main()
{
string s1("test.cpp");//用substr和find去取出
string suffix = s1.substr(s1.find('.'));
cout << suffix << endl;
system("pause");
return 0;
}
5. string类非成员函数
函数名称 | 功能说明 |
operator+ | 尽量少用,因为存在拷贝构造,传值返回效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串,由于cin会将空格当作两段数据的分割,在一段字符串中有空格时,就需要用getline去获取字符串,例如:getline(cin, str); |
relational operator | 比较类型的运算符重载 |
6.string类中find接口的归纳
函数名称 | 功能介绍 |
find | 第一个参数给所要查找的字符串或者字符,第二个参数给查找的起点下标(默认从0开始),返回第一次找到时的下标位置,找不到则返回string::npos |
rfind | 倒着往前找,找到返回起始下标,找不到返回string::npos |
find_first_of | 第一个参数给一个字符串,当出现字符串当中的字符时,会返回对应位置的下标,第二个参数可以设置查找的起点,找不到返回string::npos |
find_last_of | 从后往前找,出现第一个参数给的字符串中的字符时,返回对应位置的下标 |
例子:截取网址的域名
int main()
{
// 取出url中的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
int begin = url.find("://")+3;//找到域名开始的下标
int end = url.find("/",begin);//域名结束的下标
string address = url.substr(begin,end - begin);//用substr截取
cout << address << endl;
system("pause");
return 0;
}
例子:指定替换
int main()
{
//将其中所有a,b,c都替换成*
string str ("Please, replace the vowels in this sentence by asterisks.");
size_t found = str.find_first_of("abcs");
cout << str << endl;
while(found != string::npos)
{
str[found] = '*';
found = str.find_first_of("abcs",found+1);
}
cout << str << endl;
system("pause");
return 0;
}
三、与string类相关的OJ练习
1.仅仅反转字母
题目链接:
917. 仅仅反转字母 - 力扣(LeetCode)
题目描述:
给定一串字符串,将其中的字母字符进行反转,其他字符相对位置不变
解题思路:
与快速排序的思路相似,用一个索引begin指向开头,一个end指向末尾,begin往后找字母,找到后停下,end往前找字母,找到后停下,然后进行交换,重复操作直到遍历完整个字符串
代码参考:
class Solution
{
public:
bool if_alphabet(char c)
{
if((c>='a' && c<='z') || (c>='A' && c<='Z'))
return true;
else
return false;
}
string reverseOnlyLetters(string s)
{
int begin = 0;
int end = s.size()-1;
while(begin<end)
{
while(!if_alphabet(s[begin]) && begin<end)
{
begin++;
}
while(!if_alphabet(s[end]) && begin<end )
{
end--;
}
swap(s[begin],s[end]);
begin++;
end--;
}
return s;
}
};
2.找到第一个只出现过一次的字母字符
题目链接:
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
题目描述:
给定一个字符串,找到字符串中第一个字出现过一次的字母字符,返回对应位置的下标,如果没有只出现一次的字母,则返回-1。
解题思路:
用计数排序的思路,先将字符串遍历一遍,统计出每个字符出现的次数,得到该表格(数组),然后在遍历一次字符串,逐一查表得到每个字符出现的次数,第一个出现次数为1的字符时,返回下标,遍历结束后都没有返回则说明没找到,则返回-1。
参考代码:
class Solution
{
public:
int firstUniqChar(string s)
{
//先进行计数排序,得到每个出现过的字母的一个出现次数表,再遍历一遍字符串,逐一查表即可得到第一个只出现一次的字符
int CountSort[26] ={0};
for(int i=0;i < s.size();i++)
{
CountSort[s[i]-'a']++;
}
for(int i=0;i < s.size();i++)
{
if(CountSort[s[i]-'a'] == 1)
return i;
}
return -1;
}
};
3.返回最后一个单词的长度
题目链接:
字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)
题目描述:
给一串字符,单词间会用空格隔开,打印最后一个单词的长度
解题思路:
运用rfind找到空格位置即可算到最后一个单词的长度,需要注意的是,这里字符串的输入要用getline,而不能用cin。
参考代码:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
getline(cin,str);
size_t pos = str.rfind(" ");
if(pos != string::npos)
{
cout << str.size() - pos - 1;
}
else
{
cout << str.size();
}
return 0;
}
4.字符串相加
题目链接:
415. 字符串相加 - 力扣(LeetCode)
题目描述:
给两个数字相关的字符串,要求将两个字符串中的数字相加,其结果存到字符串中返回,该题目是为了解决一些特别大的数字相加时,int类型会溢出,用字符进行相加,自行设计规则,实现字符串相加
解题思路:
逐一将最后字符最后一个取出来进行相加,结果判断是否需要进位,从个位开始,一位位往前加,每次结果记录到字符串中当两个数字都从个位到最大位遍历结束后跳出循环,剩下的就是对一些细节和特殊情况进行特殊处理以及优化
特殊情况/需要注意的细节:
1.每次需要将进位进行更新
2.字符取出后需要减上字符0,而录入到字符串的结果需要加上字符0(类型转换)
3.可能存在其中一个数字已经遍历结束的情况,这个时候需要将其置成0,而不能直接结束循环,另一个数可能存在进位以及多次进位的情况。
4.可能存在两个数字同时结束遍历,但进位还没完成的情况,需要在循环外进行特殊处理
C++中若是使用string类解题,存在优化:
1.但凡使用string类都需要考虑下,是否可以提前开劈还足够的空间,避免扩容(扩容存在较大消耗)
2.根据题目要求以及设计思路,每次结果都会存入字符串中,每次存入一位,因此是头插操作,但头插消耗过大,可以采用尾插的方式,最终结果再进行逆置
代码参考:
class Solution
{
public:
string addStrings(string num1, string num2)
{
int end1 = num1.size() -1;
int end2 = num2.size() -1;
int up = 0; //进位
string str;
//优化,提前开好空间,避免扩容消耗
str.reserve(num1.size() > num2.size() ? num1.size()+1 : num2.size()+1);
while(end1>=0 || end2>=0)//只要num1或者num2没结束就继续
{
int n1 = end1>=0? num1[end1]-'0' : 0;//取最后一位
int n2 = end2>=0? num2[end2]-'0' : 0;
int ret = n1+n2+up;
up = ret/10;//重置进位
ret = ret%10;
str += (ret+'0');
end1--;
end2--;
}
if(up == 1)//避免两个同时走完,还没进位的情况
str+='1';
reverse(str.begin(),str.end());//优化,这里采用先存再逆置的方法,避免头插消耗过大
return str;
}
};
5.反转字符串2
题目链接:
541. 反转字符串 II - 力扣(LeetCode)
题目描述:
给一个字符串,每走2k长都需要反转前k个字符的字符串,长度不够k则全反转
解题思路:
估计题目画图分析发现,定义一个begin和end,每次往后走2k步,就是需要反转的区间,在判断下结束条件和末尾的各种情况即可。
代码参考:
class Solution {
public:
void s_reverse(string& s,int begin,int end)
{
if(end > s.size()-1)
{
end = s.size() - 1;
}
while(begin < end)
{
swap(s[begin],s[end]);
begin++;
end--;
}
}
string reverseStr(string s, int k)
{
int begin = 0;
int end = begin + k - 1;
while(begin < s.size())
{
s_reverse(s,begin,end);
begin+=2*k;
end+=2*k;
}
return s;
}
};
6.反转字符串中的单词
题目链接:
557. 反转字符串中的单词 III - 力扣(LeetCode)
题目描述:
在一段字符串中,找到单词部分进行反转,其余相对位置不变
解题思路:
用空格判断单词之间的间隔,用一个cur去遍历字符串,begin和end去进行逐段反转,最后是结束时的判断要注意
参考代码:
class Solution
{
public:
string reverseWords(string s)
{
int cur = 0;
int begin = 0;
int end = 0;
while(cur < s.size())
{
begin = cur;
while(cur < s.size() && s[cur++]!=' ');
if(cur == s.size())
{
end = cur - 1;
}
else
{
end = cur - 2;
}
while(begin < end)
{
swap(s[begin],s[end]);
begin++;
end--;
}
}
return s;
}
};
总结
本篇内容整理了关于string类的常用接口以及介绍,还整理了部分简单的Oj练习题可以用来巩固练习。