• 【C++】string的接口从生涩到灵活使用——这篇文章就够了


    目录

    第一类题目:反转字符串类型 

    1.反转字母(初级)

    正向迭代器  

    题目讲解  

     2.反转字母(中级)

    reverse和size

    题目讲解  

    3.反转字母(高级) 

    find和resize  

    题目讲解  

    第二类题目:一个字符串的子字符串

    1.模拟实现strStr()

    KMP算法

    理论讲解  

    代码实现  

    使用next数组模拟实现strStr()

    2.重复的子字符串

    erase 和 operator+  

    题目讲解  

    第三类题目:一些值得注意的细节

    字符串最后一个单词的长度

    getline 和 cin  

    题目讲解  

     


            本篇文章其实是以笔者在LeetCode的刷题经历为基础而诞生的。string的接口有100多个,实在有些繁杂,哪些应该熟练掌握,哪些应该简单了解,自己摸索其实成本其实还是挺高的,所以我想这一篇文章能够带给你帮助。内容有些长,只要你耐心看完一定会有很大的收货。各位刚接触string的同道中人也可以先刷刷这些题试试水。        


    第一类题目:反转字符串类型 

    反转字母(初级)

    反转字母(初级)

    正向迭代器  

    在此之前,我们先来介绍关于正向迭代器的两个接口:

      先来看如何使用: 

    1. void test_string1()
    2. {
    3. string s = "abcdefg";
    4. /*迭代器的使用:iterator 是一种在string类域中被重新命名的类型,所以string::iterator
    5. 表示类型*/
    6. string::iterator it_begin = s.begin();
    7. string::iterator it_end = s.end();
    8. /*string迭代器行为类似于指针,begin()接口获得字符串的起始位置
    9. end()接口获得字符串最终的位置(也就是'\0'的位置)*/
    10. cout << *it_begin << ' ' << *(it_end - 1) << endl;
    11. //我们可以像指针一样操作string迭代器:
    12. while (it_begin <= it_end - 1)
    13. {
    14. (*it_begin) -= 32; //全部转成大写字母
    15. cout << *it_begin << ' ';
    16. ++it_begin;
    17. }
    18. cout << endl;
    19. } //运行结果如下图:

    题目讲解  

     

    这道题思路很简单,使用前后指针,往中间移动的过程中,将字母交换即可,这道题就来使用一下迭代器,直接附上代码实现:

    1. class Solution {
    2. public:
    3. string reverseOnlyLetters(string& s) {
    4. //定义前后指针
    5. string::iterator it_begin = s.begin(), it_end = s.end() - 1;
    6. while(it_begin < it_end)
    7. {
    8. /*这里如果忘记怎么用isalpha函数判断是否是字母,也可以用*it_begin > 'A'
    9. && *it_begin < 'Z'...... 的逻辑*/
    10. while(it_begin < it_end && !isalpha(*it_begin))
    11. {
    12. ++it_begin;
    13. }
    14. while(it_begin < it_end && !isalpha(*it_end))
    15. {
    16. --it_end;
    17. }
    18. if(it_begin < it_end)
    19. {
    20. swap(*it_begin, *it_end);
    21. ++it_begin;
    22. --it_end;
    23. }
    24. }
    25. return s;
    26. }
    27. };

     2.反转字母(中级)

    反转字母(中级)

    在此之前我们来介绍两个接口:  

    reverse和size

    先来看如何使用:

    1. void test_string2()
    2. {
    3. //reverse和size的使用
    4. string s = "hello";
    5. //size获得字符串的长度,且不包含'\0'
    6. size_t sz = s.size();
    7. cout << sz << endl;
    8. string::iterator it_begin = s.begin(), it_end = s.end();
    9. /*reverse在std的命名空间中,也就是说使用std命名空间后可以直接调用该接口
    10. 该函数的两个参数的类型必须是迭代器,且区间是左闭右开
    11. 该函数效果是:实现字符串的翻转,与上一题类似*/
    12. reverse(it_begin, it_end);
    13. cout << s << endl;
    14. }//效果如下图所示:

    题目讲解  

    思路上也不算难,只不过比第一题多加了一些限制条件而已,我们以每2k个为一组进行遍历,将前k个进行翻转;对于最后不满2k个进行特殊判断即可,附上代码实现:

    1. class Solution {
    2. public:
    3. string reverseStr(string& s, int k) {
    4. for(size_t i = 0; i < s.size(); i += 2 * k)
    5. {
    6. //i + k <= s.size()确保迭代器不会越界
    7. if(i + k <= s.size())
    8. {
    9. //对前k个进行翻转
    10. reverse(s.begin() + i, s.begin() + i + k);
    11. }
    12. else
    13. {
    14. //剩余的不满k个全部翻转
    15. reverse(s.begin() + i, s.end());
    16. }
    17. }
    18. return s;
    19. }
    20. };

    3.反转字母(高级) 

    3​​反转字母(高级)

    在此之前我们先来介绍两个接口:

    find和resize  

    先来看看如何使用:

    1. void test_string3()
    2. {
    3. //find的介绍:
    4. string s = "hello world hello programmer";
    5. //1.常见用法:直接传入你想找的某个字符,返回第一个出现该字符的下标(类型:size_t)
    6. //比如这里我想找第一个空格出现的位置:
    7. size_t pos1 = s.find(' ');
    8. cout << pos1 << endl;
    9. //2.当然如果没有找到,语法规定返回string::npos,这里来验证一下:
    10. //我们找一个字符'z',但是字符串中并不存在
    11. size_t pos2 = s.find('z');
    12. if (pos2 == string::npos)
    13. cout << "yes" << endl;
    14. else
    15. cout << "no" << endl;
    16. /*3.小应用:查找文件后缀名
    17. 现在有一个文件名为:test.cpp,我想获得后缀如何操作?*/
    18. string file = "test.cpp";
    19. size_t pos3 = file.find('.');
    20. //这里介绍一个接口:substr,给定范围后可以返回子子字符串
    21. string suffixName = file.substr(pos3, file.size()-pos3);
    22. cout << suffixName << endl;
    23. //4.另一个常见用法:查找模式串是否存在,比如:
    24. string haystack = "hello world hello everyone";
    25. string needle = "everyone";
    26. if (haystack.find(needle) != string::npos)
    27. cout << "find!" << '\n';
    28. else
    29. cout << "none!" << '\n';
    30. /*当然你也可以这样用:haystack.find("everyone");
    31. 这里的原理就是:这个传入的字符串会隐式类型转化为string类型,然后再进行查找
    32. 当find只传入需要查找的对象作为参数时,默认从0位置开始查找,如果想从特定某个位置
    33. 开始查找,需要传入具体的开始位置,例如:*/
    34. haystack.find(needle, 5);
    35. }//运行结果如下图所示:

    1. void test_string4()
    2. {
    3. //resize的使用:
    4. string s1 = " hello world ";
    5. //使用快慢指针法:
    6. size_t fast = 0, slow = 0;
    7. while (fast < s1.size())
    8. {
    9. if(s1[fast] != ' ')
    10. {
    11. if (slow != 0)
    12. {
    13. s1[slow] = ' ';
    14. ++slow;
    15. }
    16. while (fast < s1.size() && s1[fast] != ' ')
    17. {
    18. s1[slow++] = s1[fast++];
    19. }
    20. }
    21. ++fast;
    22. }
    23. //resize:重新改变s1的空间大小,传入的参数(类型size_t)是你想让空间最终变成你指定的大小
    24. s1.resize(slow);
    25. cout << s1 << endl;
    26. }//运行结果如下图所示:

    题目讲解  

    对整个字符串进行翻转的操作不难:  

    1. //只需使用一次reverse即可:
    2. string s = "the sky is blue";
    3. reverse(s.begin(), s.end());

    接下来如何对每个单词进行翻转?

    笔者一开始是想使用find来找到每个空格的位置,以此来控制每两个空格之间的单词进行翻转,这种操作有点冗杂,还需要特别处理一些头尾单词,得到如下代码:

    1. string reverseSeriesWords(string& s)
    2. {
    3. size_t pos = s.find(' '); //找到第一个空格位置
    4. size_t index = 0;
    5. reverse(s.begin() + index, s.begin() + pos); //单独控制第一个单词
    6. while (s.find(' ', pos + 1) != string::npos)//进入循环进行遍历,每两个空格使用一次 reverse
    7. {
    8. index = pos;
    9. pos = s.find(' ', index + 1);
    10. reverse(s.begin() + index + 1, s.begin() + pos);
    11. }
    12. reverse(s.begin() + pos + 1, s.end()); //最后单独控制最后一个单词
    13. return s;
    14. }

    后来,经过反思与借鉴得到更好的代码如下:  

    1. void reverse(string& s, int start, int end)
    2. { //翻转,区间写法:左闭又闭 []
    3. for (int i = start, j = end; i < j; i++, j--)
    4. {
    5. swap(s[i], s[j]); //调用库函数swap
    6. }
    7. }
    8. void reverseSeriesWords(string& s)
    9. {
    10. int start = 0;
    11. for (int i = 0; i <= s.size(); ++i)
    12. {
    13. if (i == s.size() || s[i] == ' ')
    14. { //到达空格或者串尾,说明一个单词结束,进行翻转。
    15. reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
    16. start = i + 1; //更新下一个单词的开始下标start
    17. }
    18. }
    19. }

    最终得到完整的代码如下:  

    1. class Solution {
    2. public:
    3. void reverse(string& s, int start, int end)
    4. { //翻转,区间写法:左闭又闭 []
    5. for (int i = start, j = end; i < j; i++, j--)
    6. {
    7. swap(s[i], s[j]);
    8. }
    9. }
    10. void removeSpaces(string& s) //移除空格我们在将rsize接口的时候已经实现过了
    11. {
    12. size_t fast = 0, slow = 0;
    13. while (fast < s1.size())
    14. {
    15. if(s1[fast] != ' ')
    16. {
    17. if (slow != 0)
    18. {
    19. s1[slow] = ' ';
    20. ++slow;
    21. }
    22. while (fast < s1.size() && s1[fast] != ' ')
    23. {
    24. s1[slow++] = s1[fast++];
    25. }
    26. }
    27. ++fast;
    28. }
    29. //resize:根据你的指定,重新改变s1的空间大小
    30. s1.resize(slow);
    31. }
    32. string reverseSeriesWords(string& s)
    33. {
    34. removeSpaces(s); //去除多余空格
    35. reverse(s, 0, s.size() - 1);
    36. int start = 0;
    37. for (int i = 0; i <= s.size(); ++i)
    38. {
    39. if (i == s.size() || s[i] == ' ')
    40. { //到达空格或者串尾,说明一个单词结束,进行翻转。
    41. reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
    42. start = i + 1; //更新下一个单词的开始下标start
    43. }
    44. }
    45. return s;
    46. }
    47. };

    第二类题目:一个字符串的子字符串

    1.模拟实现strStr()

    模拟实现strStr()

    做这道题之前,先想一个问题:

    给定文本串:a a b a a b a a f (长度假设为N)

    给定模式串:a a b a a f (长度假设为M)

    如何在前文本符串中查找模式串是否存在?

    我第一个想到的是暴力求解,直接走两层循环来匹配,但是时间复杂度很高:O(M * N),那么有没有更优化得分方法?这里介绍KMP算法  

    KMP算法

    理论讲解  

     KMP主要应用在字符串匹配上,KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。

    代码实现  

    而前缀表就是存放在next数组里面,下面我们介绍如何通过代码实现——传入一个模式串能够得到其next数组的函数

    1.首先我们对next数组进行初始化

    1. size_t i, j = 0; //定义两个指针,之后用来遍历
    2. next[0] = j //将next数组第一个位置初始化为0

    2.处理前后缀相同和不同的情况

    首先得知道一个点:

    1. //给出代码:
    2. void getNext(int* next, const string& s) {
    3. int j = -1;
    4. next[0] = j;
    5. for(int i = 1; i < s.size(); i++) //注意i从1开始
    6. {
    7. //当前后缀不同:
    8. while (j >= 0 && s[i] != s[j + 1])
    9. {
    10. j = next[j]; // 向前回退
    11. }
    12. //当前后缀相同:
    13. if (s[i] == s[j + 1])
    14. {
    15. j++;
    16. }
    17. next[i] = j; // 将j(前缀的长度)赋给next[i]
    18. }
    19. }//运行结果如下:

    至此我们next数组构建的函数已经完成  

    使用next数组模拟实现strStr()

    思路讲解:首先我们先得到needle的next数组  

    1. int next[needle.size()];
    2. getNext(next, needle);

    接着我们开始遍历haystack与needle  

    1. size_t j = 0;
    2. for(size_t i = 0; i < haystack.size(); ++i)
    3. {
    4. //出现冲突时回到next数组对应下标处
    5. while(j > 0 && haystack[i] != needle[j])
    6. {
    7. j = next[j - 1];
    8. }
    9. //没出现冲突时一直往后匹配即可
    10. if(haystack[i] == needle[j])
    11. {
    12. ++j;
    13. }
    14. //当j遍历到最后等于模式串的长度时仍没有出现冲突即证明存在子串
    15. //这时我们返回下标即可
    16. if(j == needle.size())
    17. {
    18. return (i - needle.size() + 1);
    19. }
    20. }

    如下附上完整代码:

    1. class Solution {
    2. public:
    3. void getNext(int* next, string& s)
    4. {
    5. //初始化
    6. size_t j = 0;
    7. next[0] = j;
    8. //得到next数组
    9. for(size_t i = 1; i < s.size(); ++i)
    10. {
    11. while(j > 0)
    12. {
    13. if(s[i] != s[j])
    14. {
    15. j = next[j - 1];
    16. }
    17. else
    18. break;
    19. }
    20. if(s[i] == s[j])
    21. ++j;
    22. next[i] = j;
    23. }
    24. }
    25. int strStr(string haystack, string needle) {
    26. if(needle.size() == 0)
    27. {
    28. return 0;
    29. }
    30. int next[needle.size()];
    31. getNext(next, needle);
    32. size_t j = 0;
    33. for(size_t i = 0; i < haystack.size(); ++i)
    34. {
    35. while(j > 0 && haystack[i] != needle[j])
    36. {
    37. j = next[j - 1];
    38. }
    39. if(haystack[i] == needle[j])
    40. {
    41. ++j;
    42. }
    43. if(j == needle.size())
    44. {
    45. return (i - needle.size() + 1);
    46. }
    47. }
    48. return -1;
    49. }
    50. };

    2.重复的子字符串

    重复的子字符串

    再次之间我们先来介绍两个接口:

    erase 和 operator+  

    1. void test_string7()
    2. {
    3. //operator+ 用法:
    4. string s = "hello ";
    5. string tmp = "world";
    6. //创建一个新变量来接收
    7. //可以+ 一个string对象:
    8. string newstr = s + tmp;
    9. cout << newstr << endl;
    10. //也可以单独加一个字符:
    11. string newstr2 = newstr + '!';
    12. cout << newstr2 << '\n';
    13. //operator+= 用法:
    14. s += tmp;
    15. s += '!';
    16. cout << s << endl;
    17. //不过不建议多用erase接口,尾部删除还好,如果中间删除还要挪动数组,时间复杂度就为O(N)了
    18. }//运行结果如下图:

    1. void test_string8()
    2. {
    3. //erase的用法:
    4. string s = "hello world";
    5. //1.删除指定位置字符,参数为迭代器:
    6. s.erase(s.begin());
    7. cout << s << endl;
    8. //2.删除指定范围字符串,参数为迭代器,区间为左闭右开:
    9. string s2 = "hello world";
    10. s2.erase(s2.begin(), s2.begin() + 5);
    11. cout << s2 << endl;
    12. //3.删除指定范围字符,参数为size_t,区间为左闭右开:
    13. string s3 = "hello world";
    14. s3.erase(0, 6);
    15. cout << s3 << endl;
    16. }//代码演示如下图:

    题目讲解  

    这里介绍两种解题方法:

    1.移动匹配:

    接下来附上代码:  

    1. class Solution {
    2. public:
    3. bool repeatedSubstringPattern(string s) {
    4. string newstr = s + s;
    5. string::iterator it_begin = newstr.begin(), it_end = newstr.end();
    6. newstr.erase(it_end - 1); //删除尾
    7. newstr.erase(it_begin); //删除头
    8. if(newstr.find(s) != string::npos)
    9. {
    10. return true;
    11. }
    12. else
    13. return false;
    14. }
    15. };

    2.KMP算法逻辑解决  

    接下来我们以字符串"abababab"为例,得到next数组  

    接下来附上代码实现:  

    1. class Solution {
    2. public:
    3. void getNext(int* next, string& s)
    4. {
    5. size_t j = 0;
    6. next[0] = j;
    7. for(size_t i = 1; i < s.size(); ++i)
    8. {
    9. while(j > 0 && s[i] != s[j])
    10. {
    11. j = next[j - 1];
    12. }
    13. if(s[i] == s[j])
    14. ++j;
    15. next[i] = j;
    16. }
    17. }
    18. bool repeatedSubstringPattern (string& s) {
    19. if (s.size() == 0) {
    20. return false;
    21. }
    22. int next[s.size()];
    23. getNext(next, s);
    24. int len = s.size();
    25. if (next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0) {
    26. return true;
    27. }
    28. return false;
    29. }
    30. };

    第三类题目:一些值得注意的细节

    字符串最后一个单词的长度

    字符串最后一个单词的长度

    在这之前先来介绍两个接口:  

    getline 和 cin  

    1. void test_string9()
    2. {
    3. string s1, s2;
    4. //现在想对s1输入"hello"
    5. cin >> s1;
    6. cout << s1 << endl;
    7. //如果现在想对s2输入"hello world"呢?
    8. cin >> s2;
    9. cout << s2 << endl;
    10. } //代码运行结果如下图所示:

    为什么输入了“hello world”最后打印却只有“hello”?

    因为对于cin来说,空格就表示对当前对象输入完成了,而在空格后面继续输入,数据保存在缓存中,但是没有另外的对象接收,在程序运行完就自动丢弃了。所以cin无法对一个对象输入带着空格的英文词组,或者句子。

    这个时候只能用getline了:

    题目讲解  

    这题本身没有什么难度,只是想区别一些getline和cin的用法,避免混淆,这里直接附上代码:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string line;
    7. while(getline(cin, line))
    8. {
    9. size_t pos = line.rfind(' '); //rfind和find使用方式一样,只不过是find是从前往后找, refind是从后往前找
    10. cout<size()-pos-1<
    11. }
    12. return 0;
    13. }

    好了,文章到这里结束了,本篇文章介绍了常见的string接口的使用,但是并不完全,实际上还需要读者在平时的时候查查文档,加深印象。

    最后附上一个大佬对于string类的认识:

    STL_string类到底怎么啦? ​​​​​​​ 

  • 相关阅读:
    【滤波跟踪】基于不变扩展卡尔曼滤波器对装有惯性导航系统和全球定位系统IMU+GPS进行滤波跟踪附matlab代码
    Unity接入北斗探针SDK(基于AndroidJavaProxy)丨安卓交互的最佳方式
    SAP KO22内部订单预算BAPI与BDC
    家庭网络中的组网方式
    通过WSL2 Ubuntu18.04搭建CANN算子开发环境
    Databend 开源周报第 111 期
    Day08_DM层建设实战,本地视频+md,review第1遍,220625,
    【个人总结】动态路由实现方案
    Spring原理学习(六)Scope
    向NXP官网Linux内核添加ALPHA开发板
  • 原文地址:https://blog.csdn.net/Lin_zhiyouyu/article/details/127694744