目录
5.查找相应字符串(find + npos)/查找子串(substr)
一.序言
string类是表示字符串的字符串类
该类的接口与常规容器的接口基本相同,不过专门添加了一些用来操作string的常规操作
我们可以去cplusplus.com网站查看有关string类更为详细的官方介绍.
PS:在使用string类时,必须包含#include头文件以及using namespace std;
二.String类的常用接口说明
1.String类对象的常见构造
1.构造空的String类对象
2.用C-string来构造string类对象
由于常量字符串默认已经以‘\0’结尾,所以可以直接构造.
3.String类对象包含n个字符c
如果第二个是数字,默认会将其按照Ascal码对应的字符进行看待.
4.拷贝构造
同样的,通过赋值来构造一个string类对象也是支持的.
5.一些补充(不常用)
可以通过字符串的子串进行构造String类对象,如果给定的字符串长度过大,或者给了npos长度,则直接取到末尾.
npos是一个size_t类型的数,给定的值是-1,但是size_t,也就是相当于unsigned int类型,所以npos相当于一个非常大的正数.
迭代器构造
注意是左闭右开区间,String类的迭代器可以理解为指针,其底层实现也是指针进行实现的.
2.string类对象的容量操作
1.返回String对象的大小(有效字符串)
两者实现的功能都是一样的,即返回String对象的大小.
为什么要构造出两个呢?String类先于STL库诞生,一开始用length来实现,但后面树,图等等的
数据结构,用length来表示就不太合理,所以为了统一,与其他容器的接口保持一致,给String类
也补充了size操作,实际使用size更多.
2.返回String对象的空间容量
注意:不是字符串的有效长度,两者一般并不相等,对象的空间可以是100个字节,而实际上只用了5个字节.
下面的例子,空间中最后存储了'\0',所以空间容量是15,而不是14.
3.字符串清空
clear()只是将string中有效字符清空,不改变底层空间大小
4.改变字符串的大小
假如调整的大小比原字符串小,则保留n个字符,其余全部删除,但底层空间总大小不变
假如比原字符串大,则resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间,底层空间大小可能改变也可能不改变.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s0("Initial string");
cout << s0.size() << endl;
cout << s0.capacity() << endl;
cout << ".................." << endl;
s0.resize(7);
cout << s0.size() << endl;
cout << s0.capacity() << endl;
cout << ".................." << endl;
s0.resize(14);
cout << s0.size() << endl;
cout << s0.capacity() << endl;
cout << ".................." << endl;
//有效字符size调整为20,多的用字符a来填充
s0.resize(20,'a');
cout << s0 << endl;
cout << s0.size() << endl;
cout << s0.capacity() << endl;
return 0;
}
5.为字符串预留空间
reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于
string的底层空间总大小时,reserver不会改变容量大小
字符串扩容虽然操作上对比C十分方便,但效率上依旧不理想,如果提前已知字符串的大小,提前
开空间,就可以减少空间开辟的消耗.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
//输出初始容量值
cout << sz << endl;
for (int i = 0; i < 100; i++)
{
s.push_back('c');
//检测到字符串扩容时,及时将当前扩容的容量打印出来
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
return 0;
}
从上面这段程序,就可以看出字符串扩容,也是有时间消耗的,第一次2倍扩后,后面都是按接近
1.5倍进行扩容.
如果提前开好空间,就没有这样的消耗.
#include <string>
#include <iostream>
using namespace std;
int main()
{
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
return 0;
}
6.检测字符串是否为空
如果为空,返回true;否则,返回false
不会改变字符串的值
7.一些补充
1.与resize不同,shrink_to_fit可以改变容量大小,但不常用,并且可以想象它的消耗是巨大的.
2.max_size返回字符串可以储存的最大长度,但实际上并没有那么大.
3.string类对象的访问及遍历操作
1.迭代器访问与遍历
迭代器在string类里面可以看作是指针,也就是说字符串s.begin()获取的是指向字符串首元素的指
针,而s.end()获取的是字符串最后一个元素的下一个指针.
因此可以通过这两个迭代器实现遍历字符串.
当然,我们接触过auto,和范围for操作,因此上面代码,可以进一步化简.
虽然实际上两段代码本质上是一样的.
2.反向迭代器的访问与遍历
反向迭代器rbegin,和rend两者结合起来使用,可以达到反向输出字符串的结果.
PS:rbegin++的话,是往前移动.
3.运算符[ ]访问字符串
String类支持运算符[ ]重载,平时最常使用访问字符串的方式也是这种.
和[ ]运算符功能相似,at也支持访问字符串中的元素,但是假如范围超过字符串长度,程序不会崩
溃,而是抛异常解决.
4.string类对象的修改与操作
1.字符串的拼接(push_back,append,+=)
push_back可以给字符串末尾追加一个字符
append 可以给字符串追加一个字符串
+=则既可以加一个字符,又可以加一个字符串
2.字符串插入
string类里面的insert操作支持函数重载,有着多种形式.
使用的时候,自行去查表即可.
下面给出官网的标准代码,函数与上图顺序一一对应.
#include <string>
#include <iostream>
using namespace std;
int main()
{
string str = "to be question";
string str2 = "the ";
string str3 = "or not to be";
string::iterator it;
// used in the same order as described above:
str.insert(6, str2); // to be (the )question
str.insert(6, str3, 3, 4); // to be (not )the question
str.insert(10, "that is cool", 8); // to be not (that is )the question
str.insert(10, "to be "); // to be not (to be )that is the question
str.insert(15, 1, ':'); // to be not to be(:) that is the question
it = str.insert(str.begin() + 5, ','); // to be(,) not to be: that is the question
str.insert(str.end(), 3, '.'); // to be, not to be: that is the question(...)
str.insert(it + 2, str3.begin(), str3.begin() + 3); // (or )
cout << str << '\n';
return 0;
}
3.字符串消除和末尾删除元素
erase操作支持删除指定位置的n个字符,如果没有指定,则默认npos个,也就是指定位置之后的字符串全部删除.
指定位置和数组一样,第一个字符对应下标为0,即可以实现头删
pop_back弹出字符串的最后一个字符,并且会改变字符的大小,也就是size的大小,但容量不发生
改变.
4.返回C格式字符串
C++兼容C,因此肯定会设计一些接口,使其兼容c的格式.
c_str就是这样一个接口,结合C++cout函数能够自动识别类型,它能帮助我们实现C格式打印字符
串,类似我们使用printf的时候,传常量字符串指针,默认会自动打印字符串.
5.查找相应字符串(find + npos)/查找子串(substr)
find:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
如果没有找到,则返回npos
#include <string>
#include <iostream>
using namespace std;
int main()
{
string str("There are two needles in this haystack with needles.");
string str2("needle");
size_t found = str.find(str2);
if (found != string::npos)
cout << "first 'needle' found at: " << found << '\n';
//查找字符串中第二个needle的位置,匹配字符个数为6
found = str.find("needles", found + 1, 6);
if (found != string::npos)
cout << "second 'needle' found at: " << found << '\n';
return 0;
}
substr:找子串,从pos位置开始,截取n个字符,然后将其返回
substr和find两者可以结合起来,起到意想不到的效果
比如,我想取出一个网址的域名,就可以这样操作,找到相应位置,取出对应子串
#include <string>
#include <iostream>
using namespace std;
int main()
{
// 取出url中的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t start = url.find("://");
if (start == string::npos)
{
cout << "invalid url" << endl;
return 0;
}
//跳过://
start += 3;
size_t finish = url.find('/', start);
string address = url.substr(start, finish - start);
cout << address << endl;
return 0;
}
5.string类非成员函数
relational operators其实就是我们之前日期类实现的大小比较,和C一样也是逐个字符进行比较.
#include <string>
#include <iostream>
using namespace std;
int main()
{
string foo = "alpha";
string bar = "beta";
if (foo == bar) cout << "foo and bar are equal\n";
if (foo != bar) cout << "foo and bar are not equal\n";
if (foo < bar) cout << "foo is less than bar\n";
if (foo > bar) cout << "foo is greater than bar\n";
if (foo <= bar) cout << "foo is less than or equal to bar\n";
if (foo >= bar) cout << "foo is greater than or equal to bar\n";
return 0;
}
这里再简单补充一下getline即可,它可以帮我们获取一行数据,用于用户输入,并转成字符串类
型,具有重要的意义.
三.有关String类的题目
1.翻转字符串
class Solution {
public:
bool isLetter(char ch)
{
if (ch >= 'a' && ch <= 'z')
return true;
if (ch >= 'A' && ch <= 'Z')
return true;
return false;
}
string reverseOnlyLetters(string s) {
//如果字符为空,直接返回s
if (s.empty())
return s;
//定义begin指向第一个字符,end指向最后一个字符
size_t begin = 0,end = s.size() - 1;
while (begin < end)
{
//从左往右找字母字符
while (begin < end && !isLetter(s[begin]))
begin++;
//从右往左找字母字符
while (begin < end && !isLetter(s[end]))
end--;
swap(s[begin],s[end]);
begin++;
end--;
}
return s;
}
};
2.找字符串第一个只出现一次的字符
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
class Solution {
public:
int firstUniqChar(string s) {
//统计字符串中出现的次数
int Count[256] = {0};
int sz = s.size();
for (int i = 0;i < sz;i++)
{
Count[s[i]]++;
}
//遍历字符串,如果为1,则返回对应位置
for (int j = 0;j < sz;j++)
{
if (Count[s[j]] == 1)
return j;
}
return -1;
}
};
3.字符串最后一个单词的长度
字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)
PS:不能用cin来录入数据,因为遇到空格就会停止录入,导致结果错误.
#include <iostream>
#include <string>
using namespace std;
int main() {
string line;
getline(cin,line);
size_t pos = line.rfind(' ');
cout << line.size() - pos - 1 << endl;
}
4.检验是否是回文
class Solution {
public:
//判断是字母数字字符
bool ischar(char ch)
{
if (ch >= '0' && ch <= '9')
return true;
if (ch >= 'a' && ch <= 'z')
return true;
if (ch >= 'A' && ch <= 'Z')
return true;
return false;
}
bool isPalindrome(string s) {
//引用范围for遍历
for (auto&ch:s)
{
//将所有大写字母转成小写字母
if (ch >= 'A' && ch <= 'Z')
ch+=32;
}
//判断是否是回文
int begin = 0,end = s.size() - 1;
while (begin < end)
{
//从左往右找字母字符
while (begin < end && !ischar(s[begin]))
begin++;
//从右往左找字母字符
while (begin < end && !ischar(s[end]))
end--;
if (s[begin] != s[end])
{
return false;
}
else
{
begin++;
end--;
}
}
//假如只有空格字符串
return true;
}
};
5.字符串相加
class Solution {
public:
string addStrings(string num1, string num2) {
//从后往前计算字符大小
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int value1 = 0,value2 = 0,carry = 0;
string addret;
//只要有一个字符串没有到达头部,就继续循环
while (end1 >= 0 || end2 >= 0)
{
//计算字符时,自动移动end1
if (end1 >= 0)
value1 = num1[end1--] - '0';
else
value1 = 0;
//计算字符时,自动移动end2
if (end2 >= 0)
value2 = num2[end2--] - '0';
else
value2 = 0;
//该位数字等于两数相加,和上一位进位相加
int valueret = value1 + value2 + carry;
//保留进位,获取对应该位数字
carry = valueret/10;
valueret%=10;
addret += (valueret+'0');
}
//最后一位可能是进位
if(carry == 1)
addret += '1';
//采用reverse比insert直接头插效率更高
reverse(addret.begin(),addret.end());
return addret;
}
};
6.翻转字符串(二)
class Solution {
public:
string reverseStr(string s, int k) {
//步长一次移动2k步
for (size_t i = 0;i < s.size();i+= (2*k))
{
//如果该字符串长度大于k,小于2k
if (i + k <= s.size())
reverse(s.begin() + i,s.begin() + i + k);
else//如果剩余字符小于k个
reverse(s.begin() + i,s.end());
}
return s;
}
};
7.翻转字符串(三)
557. 反转字符串中的单词 III - 力扣(LeetCode)
class Solution {
public:
string reverseWords(string s) {
int i = 0,pos = s.find(' ');
while (pos!= string::npos)
{
reverse(s.begin() + i,s.begin() + pos);
i = pos+1;
pos = s.find(' ',pos+1);
}
//翻转最后一个单词
reverse(s.begin() + i,s.end());
return s;
}
};
8.字符串相乘
整体思路借鉴字符串相加,如果字符串相加可以理解,那乘法是一样的道理.
num2从低位开始,每一位都和num1中的数字相乘,得到的数字,再赋用我们已经实现过的字符串
相加,一起全部相加.
class Solution {
public:
string addStrings(string num1, string num2) {
//从后往前计算字符大小
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int value1 = 0,value2 = 0,carry = 0;
string addret;
//只要有一个字符串没有到达头部,就继续循环
while (end1 >= 0 || end2 >= 0)
{
//计算字符时,自动移动end1
if (end1 >= 0)
value1 = num1[end1--] - '0';
else
value1 = 0;
//计算字符时,自动移动end2
if (end2 >= 0)
value2 = num2[end2--] - '0';
else
value2 = 0;
//该位数字等于两数相加,和上一位进位相加
int valueret = value1 + value2 + carry;
//保留进位,获取对应该位数字
carry = valueret/10;
valueret%=10;
addret += (valueret+'0');
}
//最后一位可能是进位
if(carry == 1)
addret += '1';
//采用reverse比insert直接头插效率更高
reverse(addret.begin(),addret.end());
return addret;
}
string multiply(string num1, string num2) {
//如果num1或者num2有一个等于0,则直接返回结果为0
if(num1 == "0" || num2 == "0")
return "0";
string ret("0");
//从num2的最后一位开始遍历
for (int n2 = num2.size() - 1;n2 >=0;n2--)
{
int value2 = num2[n2] - '0';
int carry = 0;
string temp;
//补0
for (size_t i = 0;i < num2.size() - 1 - n2;i++)
{
temp.push_back('0');
}
//计算,value2和num1的每一位相乘
for (int n1 = num1.size() - 1;n1 >=0 ;n1--)
{
int value1 = num1[n1] - '0';
//该位数字等于两数相乘,和上一位进位相加
int valueret = value1 * value2 + carry;
carry = valueret/10;
valueret %= 10;
temp += (valueret+'0');
}
//最后一位可能是进位
if (carry != 0)
temp += (carry + '0');
//翻转
reverse(temp.begin(),temp.end());
//每一次得到的结果相加
ret = addStrings(ret,temp);
}
return ret;
}
};