GESP2026年9月认证C++三级( 第三部分编程题(2、分割字符串))精讲



🌟 编程题2:分割字符串

它表面上是在考"字符串",但实际上把几个非常重要的 C++ 知识点串在了一起:

  • string

  • getline()

  • find()

  • 字符串下标

  • for 循环

  • break

  • 连续空格的处理

  • "每次处理完,再处理剩下的字符串"


这道题最重要的思想不是某一行代码,而是:

像切蛋糕一样,一刀一刀切;每切一次,下一刀需要的连续空格就增加1个。

题目规定:第一次遇到 1个连续空格 就分割;第二次需要 2个连续空格 ;第三次需要 3个连续空格,依次增加,直到无法继续分割。每次输出这次分割的左半部分,最后再输出最后剩下的字符串。


一、先讲一个"小明切字符串"的故事

假设小明得到一条字符串:

复制代码
sdfd 55 55   99312

注意!

这里的空格不是装饰品,它们可是这道题的"刀"。

我们先给空格编号:

复制代码
sdfd 55 55   99312
    ↑
   1个空格

第一次要求:

找到第一个 1个连续空格

所以第一次从这里切:

复制代码
sdfd |55 55   99312

输出左边:

复制代码
sdfd

剩下右边:

复制代码
55 55   99312

二、第二刀更加严格

第一次切完以后,第二次要求:

必须找到 连续2个空格

剩下:

复制代码
55 55   99312

注意前面的:

复制代码
55 55

中间只有:

1个空格

不够!

但是:

复制代码
55 55···99312
     ↑↑↑
     3个空格

这里有3个连续空格。

既然:

3个连续空格里面当然包含2个连续空格。

所以第二次可以切:

复制代码
55 55 |  99312

注意第二次切掉的是:

复制代码
两个空格

于是:

复制代码
左边 = 55 55
右边 =  99312

这里右边还会留下 1个空格

这正是题目特别提醒的地方:第二次只需要两个空格,而原来这里有三个,所以会留下一个空格。


三、为什么第三次不能继续?

第三次要求:

必须找到 连续3个空格

现在剩下:

复制代码
 99312

最前面只有:

复制代码
1个空格

没有3个连续空格。

所以:

❌ 第三次无法分割。

最后输出剩下的:

复制代码
 99312

注意这个字符串前面还有一个空格!


四、所以整个过程就像这样

复制代码
原字符串:

sdfd 55 55   99312
     ↑
     第1次:找1个空格

↓

sdfd | 55 55   99312

输出:

sdfd

↓

剩下:

55 55   99312
     ↑↑
     第2次:找2个连续空格

↓

55 55 |   99312

输出:

55 55

↓

剩下:

 99312

↓

第3次需要3个连续空格

❌ 找不到

↓

最后输出:

 99312

因此输出就是:

复制代码
sdfd
55 55
 99312

其中最后一行前面实际上有一个空格。题目样例解释明确说明了这个细节。


五、这道题最难的地方:空格也是字符串的一部分

很多刚学 C++ 的小朋友看到:

复制代码
sdfd 55 55   99312

会觉得:

"不就是几个空格吗?"

错!

对于字符串来说:

复制代码
"abc"

和:

复制代码
"abc "

不是同一个字符串。

前者长度:

复制代码
3

后者长度:

复制代码
4

最后那个空格也是一个字符。

所以这道题里面:

⭐ 空格不是"看不见的东西",空格也是字符!


六、为什么不能用 cin >> s

这是本题第一个非常重要的陷阱。

如果写:

复制代码
string s;
cin >> s;

输入:

复制代码
sdfd 55 55   99312

得到的可能只有:

复制代码
sdfd

因为:

复制代码
cin >> s;

遇到空格就认为:

"一个单词结束了!"


七、这道题必须用 getline

所以参考程序写:

复制代码
string s = "";
getline(cin, s);

getline() 的特点就是:

一整行全部读进来,包括中间的空格。

例如输入:

复制代码
Hello World C++

使用:

复制代码
getline(cin, s);

得到:

复制代码
Hello World C++

中间的空格全部保留。

所以记住:

🧠 cin >> s

读一个"单词"。

🧠 getline(cin, s)

读"一整行"。


八、接下来是本题真正的魔法:find()

官方参考程序里面有:

复制代码
int pos = s.find(spaces);

这是本题最值得掌握的一个函数。

find() 的意思就是:

🔍 在字符串中寻找另一个字符串第一次出现的位置。

比如:

复制代码
string s = "hello world";

执行:

复制代码
s.find("world")

就会找到:

复制代码
hello world
     ↑

返回 6

为什么是6?

因为字符串下标从0开始:

复制代码
字符: h e l l o   w o r l d
下标: 0 1 2 3 4 5 6 7 8 9 10

所以:

复制代码
world
  ↑
位置6

九、这道题有一个特别聪明的办法

题目要求:

第一次找:

复制代码
一个空格

第二次找:

复制代码
两个空格

第三次找:

复制代码
三个空格

那么我们可以让程序自己制造一个字符串:

复制代码
string spaces = "";

第一次:

复制代码
" "

第二次:

复制代码
"  "

第三次:

复制代码
"   "

第四次:

复制代码
"    "

......

这就是参考程序中的:

复制代码
for (int k = 1;; ++k) {
    string spaces = "";

    for (int i = 0; i < k; ++i) {
        spaces += " ";
    }

十、这里的 k 是什么意思?

复制代码
for (int k = 1;; ++k)

可以把 k 想象成:

🗡️ 第几把越来越长的刀

第一次:

复制代码
k = 1

需要:

复制代码
" "

一个空格。

第二次:

复制代码
k = 2

需要:

复制代码
"  "

两个空格。

第三次:

复制代码
k = 3

需要:

复制代码
"   "

三个空格。

所以:

复制代码
k
↓
需要的连续空格数量

1 → " "
2 → "  "
3 → "   "
4 → "    "
......

十一、这个 for 为什么没有条件?

你可能注意到了:

复制代码
for (int k = 1;; ++k)

正常我们见过:

复制代码
for (int i = 0; i < n; i++)

但是这里:

复制代码
for (int k = 1;; ++k)

中间没有条件!

这意味着:

for 无限循环

相当于:

复制代码
while (true)

也就是说:

复制代码
k = 1
↓
k = 2
↓
k = 3
↓
k = 4
↓
k = 5
↓
......

什么时候停?

不是靠 for 的条件停,而是程序里面遇到:

复制代码
break;

才停。


十二、制造"空格钥匙"

看这段:

复制代码
string spaces = "";

for (int i = 0; i < k; ++i) {
    spaces += " ";
}

假如:

复制代码
k = 3

一开始:

复制代码
spaces = ""

第一次:

复制代码
spaces = " "

第二次:

复制代码
spaces = "  "

第三次:

复制代码
spaces = "   "

最终:

复制代码
spaces = "   "

于是:

复制代码
s.find(spaces)

就相当于:

复制代码
s.find("   ")

也就是:

🔍 "帮我找三个连续空格!"


十三、找到以后,pos 是什么?

程序:

复制代码
int pos = s.find(spaces);

假设:

复制代码
s = "sdfd 55 55   99312"

第一次:

复制代码
spaces = " "

那么:

复制代码
pos = s.find(" ");

找到:

复制代码
sdfd·55·55···99312
    ↑

所以 pos 就是这个空格的位置。

我们可以把:

复制代码
pos

理解成:

📍 "刀子应该切在哪里"


十四、然后输出左边

参考程序:

复制代码
for (int i = 0; i < pos; ++i) {
    cout << s[i];
}
cout << endl;

这是什么意思?

假设:

复制代码
pos = 4

那么:

复制代码
for (int i = 0; i < 4; ++i)

依次输出:

复制代码
s[0]
s[1]
s[2]
s[3]

也就是:

复制代码
s d f d

输出:

复制代码
sdfd

十五、为什么不输出 s[pos]

因为:

复制代码
s[pos]

就是那个用于切割的空格。

题目要求:

用于分割的空格要舍弃。

所以:

复制代码
i < pos

刚好只输出空格前面的内容。


十六、最有意思的一步:把已经切掉的左边扔掉

程序:

复制代码
string rest = "";

for (int i = pos + k; i < s.length(); ++i) {
    rest += s[i];
}

s = rest;

这段代码非常重要。

我们刚才找到的是:

复制代码
sdfd······55 55   99312
↑←左边→↑↑↑←右边→

如果:

复制代码
pos = 4
k = 1

那么:

复制代码
pos + k

就是:

复制代码
4 + 1 = 5

也就是说:

从第5个字符开始,重新建立一个字符串。


十七、为什么是 pos + k

因为:

复制代码
pos

是连续空格的起始位置。

而:

复制代码
k

是这次要消耗掉的空格数量。

所以:

复制代码
pos + k

就是:

跳过左边部分 + 跳过本次使用的空格之后,真正的右半部分起点。

例如:

复制代码
sdfd 55 55   99312
    ↑
   pos

第一次:

复制代码
k = 1

所以:

复制代码
pos + k

刚好跳过这个空格。

剩下:

复制代码
55 55   99312

十八、第二次更加有意思

现在:

复制代码
s = "55 55   99312"

此时:

复制代码
k = 2

程序制造:

复制代码
spaces = "  "

然后:

复制代码
pos = s.find(spaces);

找到:

复制代码
55 55···99312
     ↑

注意:

这里实际有三个空格。

但是:

复制代码
k = 2

只吃掉两个:

复制代码
55 55··99312
     ↑↑

所以剩下:

复制代码
 99312

也就是:

复制代码
一个空格 + 99312

十九、这就是题目的"小陷阱"

很多孩子会想:

"找到三个空格以后,不是应该把三个都删掉吗?"

❌ 不对!

程序找的是:

复制代码
"  "

也就是:

复制代码
两个连续空格

所以只消耗:

复制代码
2个

剩下:

复制代码
1个

这就是题目样例解释中特别强调的地方。


二十、什么时候 break

程序:

复制代码
if (pos < 0 || pos >= s.size())
    break;

这里是:

"如果找不到需要的空格,就停止切割。"

为什么:

复制代码
pos < 0

代表找不到?

因为 find() 找不到目标时,会返回一个特殊值 string::npos

在这个程序里把它放到 int 中,所以参考程序用:

复制代码
pos < 0

来判断。

对于初学者,可以先记住:

🔍 find() 找不到 → 没找到

于是:

复制代码
if (pos < 0)
    break;

就是:

找不到刀口了,停止切!


二十一、为什么还有 pos >= s.size()

参考程序写的是:

复制代码
if (pos < 0 || pos >= s.size()) break;

这是一个保险判断。

正常情况下,如果找到了一个有效位置:

复制代码
0 <= pos < s.size()

所以:

复制代码
pos >= s.size()

也意味着这个位置不能作为有效的字符串位置。

对于这道题的主要逻辑,孩子最应该记住的是:

找不到分隔符,就 break


二十二、最后为什么还要 cout << s

这是本题另一个很重要的细节。

循环里面输出的是:

每一次成功切割的左边。

但是最后还有一个:

最后一次切割之后留下来的右边部分。

所以循环结束后:

复制代码
cout << s << endl;

就是把最后剩下的字符串输出。


二十三、把完整程序连起来

参考程序的完整思路可以写成:

复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "";

    // 整行读入,空格也保留下来
    getline(cin, s);

    // k 表示这一次需要几个连续空格
    for (int k = 1;; ++k) {

        // 制造 k 个连续空格
        string spaces = "";

        for (int i = 0; i < k; ++i) {
            spaces += " ";
        }

        // 寻找这 k 个连续空格
        int pos = s.find(spaces);

        // 找不到,就不能继续分割
        if (pos < 0 || pos >= s.size())
            break;

        // 输出本次分割的左边部分
        for (int i = 0; i < pos; ++i) {
            cout << s[i];
        }
        cout << endl;

        // 保存本次分割之后的右边部分
        string rest = "";

        for (int i = pos + k; i < s.length(); ++i) {
            rest += s[i];
        }

        // 以后只处理右边剩下的字符串
        s = rest;
    }

    // 最后剩下的字符串
    cout << s << endl;

    return 0;
}

参考程序的核心逻辑:使用 getline 保留空格,逐步构造 1、2、3...... 个连续空格,用 find 查找,输出左侧,再把右侧保存回 s,直到找不到对应数量的连续空格。


二十四、我们用"程序运行表"完整模拟一次

输入:

复制代码
sdfd 55 55   99312

第1轮

复制代码
k = 1
spaces = " "

原字符串:

复制代码
sdfd 55 55   99312
    ↑

找到位置:

复制代码
pos = 4

输出:

复制代码
sdfd

剩下:

复制代码
55 55   99312

第2轮

复制代码
k = 2
spaces = "  "

现在:

复制代码
55 55   99312
     ↑↑↑

找到连续两个空格。

输出左边:

复制代码
55 55

消耗两个空格以后:

复制代码
 99312

第3轮

复制代码
k = 3
spaces = "   "

现在:

复制代码
 99312

只有一个空格。

找不到:

复制代码
"   "

所以:

复制代码
break;

最后

复制代码
cout << s << endl;

输出:

复制代码
 99312

二十五、再看第二个样例,更容易发现题目的"坑"

第二个样例类似:

复制代码
AbCd   999

中间有:

复制代码
三个连续空格

第一次

需要:

复制代码
1个空格

所以:

复制代码
AbCd |  999

第一次输出:

复制代码
AbCd

但是注意:

原来有三个空格。

第一次只吃掉一个。

所以还剩:

复制代码
  999

也就是:

复制代码
两个空格 + 999

第二次

现在需要:

复制代码
2个空格

刚好找到了:

复制代码
  999
↑↑

所以第二次分割:

复制代码
|999

左边是什么?

是:

复制代码
空字符串

因此必须输出一个:

空行

这也是题目特别强调的地方:第二次分割可以得到空字符串,所以输出中必须保留这一行,不能偷偷省略。


二十六、这个"空字符串"非常值得孩子理解

什么叫:

复制代码
""

就是:

里面一个字符都没有的字符串。

例如:

复制代码
string s = "";

那么:

复制代码
s里面什么都没有

长度:

复制代码
s.length() == 0

但是:

复制代码
"没有字符"

和:

复制代码
"不输出这一行"

不是一回事。

如果题目要求输出空字符串这一行:

复制代码
cout << endl;

仍然要执行。

也就是:

复制代码
第一行:AbCd
第二行:        ← 空行
第三行:999

二十七、这道题其实是一个"递归式思维",

我们可以把它理解成:

复制代码
第一次:
处理整个字符串
        ↓
切掉左边
        ↓
留下右边

第二次:
只处理右边
        ↓
再切掉左边
        ↓
留下新的右边

第三次:
只处理新的右边
        ↓
......

也就是说:

⭐ "每一次只关心剩下的右边。"

这句话非常重要。

程序中的:

复制代码
s = rest;

就是在做这个事情。


二十八、为什么不需要保存所有字符串?

有的小朋友可能会想:

复制代码
第一次的左边
第二次的左边
第三次的左边
......

是不是需要很多数组?

其实不需要。

因为题目规定:

输出一次,就可以把这一部分处理掉。

所以程序只需要一直保存:

复制代码
当前还没有处理的右边

也就是:

复制代码
s

因此:

复制代码
原来的s
    ↓
切割
    ↓
新的s
    ↓
再切割
    ↓
新的s
    ↓
......

这是一种非常实用的:

"不断缩小问题规模"的思想。


二十九、这道题考的知识点可以整理成一张图

复制代码
             分割字符串
                 │
       ┌─────────┼─────────┐
       ↓         ↓         ↓
    getline     find      string
       │         │         │
   保留空格    找位置     存文字
       │         │         │
       └─────────┼─────────┘
                 ↓
              for循环
                 ↓
          第1次需要1个空格
          第2次需要2个空格
          第3次需要3个空格
                 ↓
             找不到?
              ↙    ↘
            是      否
            ↓       ↓
          break    分割
                    ↓
                 s = rest
                    ↓
                  继续

三十、初学者最应该记住的5个知识点

🥇 第一:getline

复制代码
getline(cin, s);

记忆:

想把整行,包括空格,一口气读进来,就用 getline


🥈 第二:find

复制代码
int pos = s.find("abc");

记忆:

🔍 find = 找东西

找到:

复制代码
返回第一次出现的位置

找不到:

复制代码
返回特殊的"没找到"

🥉 第三:string

字符串可以保存:

复制代码
字母
数字
空格

例如:

复制代码
string s = "A B C";

其中空格也是字符。


🏅 第四:s = rest

这是本题最重要的算法思想:

处理完左边以后,只留下右边,下一轮继续处理。


🏅 第五:break

复制代码
if (找不到)
    break;

意思:

🛑 "没有刀口了,停止切割!"


三十一、最后给大家一个"分割字符串口诀"

把这道题记成:

🗡️ 第一刀找一个,第二刀找两个,第三刀找三个;

🔍 find 找刀口;

✂️ 输出左边;

🧹 扔掉已经切掉的部分;

👉 留下右边继续切;

🛑 找不到就停止;

📢 最后把剩下的右边输出。

程序思想就是:

复制代码
getline
   ↓
k = 1
   ↓
制造 k 个空格
   ↓
find 找位置
   ↓
找不到 → break
   ↓
输出左边
   ↓
留下右边
   ↓
k++
   ↓
继续

🌟 三十二、这道题我们学到了什么?

如果只记住:

复制代码
s.find(...)

其实还不够。

这道题真正值得学的是一个非常重要的编程思想:

一个复杂的问题,不一定要一次全部解决,可以先解决一小部分,然后把问题变小,再解决剩下的部分。

就像爬楼梯:

复制代码
整栋楼
 ↓
先上一层
 ↓
剩下的楼梯
 ↓
再上一层
 ↓
剩下的楼梯
 ↓
......
 ↓
到达终点

这就是算法中非常重要的:

🧠 "不断处理、不断缩小问题"

而这道题恰好用字符串,把这个思想演示得非常清楚。

如果孩子把这道题真正吃透,那么将来学习双指针、模拟、递归,甚至一些更复杂的算法时,都会发现:很多题其实都是在想办法让"当前的问题越来越小"。


相关推荐
估值探索者1 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
天若有情6731 小时前
C++花式魔改!手写头文件复刻Java语法,彻底打破编码思维定式
java·c++·语言做语言·jac++
qq_199886872 小时前
第7板块·第3节:CUTLASS 的 GEMM 实现与优化策略
c++·人工智能·gpu算力·cuda
hetao17338372 小时前
2026-09-17 hetao1733837 的刷题记录
c++·算法
shada3 小时前
Boost.Process v2 在旧内核上报 `assign: Bad file descriptor`
c++
别动我齐刘海3 小时前
ROS2 Jazzy + C++ 实战路线——基础学习2
c++·人工智能·vscode·python·学习·机器学习·机器人
朝朝辞暮i4 小时前
C++第 2 课
c++
代码中介商4 小时前
C++ 预约系统实战(三):服务端实现——libevent 事件驱动与业务路由
c++·json·c/s
布莱克6055 小时前
C++中的内存分区详解
c++