字符数组与字符串处理 知识点讲解

信息学竞赛(CSP-J/S、NOIP、GESP)高频考点精讲:从基础规则到推演与易错点,配真题例题。学完可一键去 OI伴学在线刷题。

去 OI伴学在线练习 →

本讲学习目标

概念

在信息学竞赛里,字符串处理是出现频率最高的考点之一。别被“字符串”三个字吓到,它本质上就是一串字符的集合。为了让你直观感受,我们用一个生活场景切入:

想象你在玩文字冒险游戏,屏幕上一行行显示着剧情文本。如果你想知道主角名字里有多少个字母,或者要把所有感叹号替换成问号,你不可能把整段话一次性“吞”进去再处理,而是必须一个字一个字地看过去,遇到目标就动手,遇到无关的就跳过。这种“逐字扫描”的能力,就是字符串处理的核心。

再看一个极小的暴力例子:假设有一个只包含 a 和 b 的字符串 "abba",题目要求统计 a 和 b 各出现几次。最笨的办法(也是计算机最初级的办法)就是:从第一个字符开始,如果是 a 就 a 计数 +1;如果是 b 就 b 计数 +1;然后移到下一个字符,重复这个过程,直到字符串结束。虽然笨,但逻辑清晰,不会出错。

在 C++ 中,这种“逐字扫描”通常通过循环来实现。因为字符串在内存里是连续存放的,我们可以用下标 s[i] 访问第 i 个字符,或者用范围 for 循环遍历。

触发清单(看到这些信号第一时间想到本知识点)

题干信号优先考虑什么对应思路
“统计每个字母出现次数”、“找出出现最多的字符”遍历 + 计数开数组 cnt[256] 做桶计数
“把字符串里的 * 换成 -"、“凯撒密码移位”遍历 + 修改循环里 if 判断字符类型并替换
“判断是不是回文”、“正反读一样吗”对称比较双指针从两头往中间扫
“解析 ISBN 号码”、“展开字符串”、“校验格式”分段处理状态机:识别数字段/字母段/符号段
“输入一行包含空格的话”读入方式用 getline 或 fgets,别用 cin >>

定义

C++ 里的字符串,本质是一个装着字符的数组,末尾额外放一个 '\0' 当“到此结束”的标记。

char s[10] = "hello";
// 内存里实际是: h e l l o \0 ? ? ? ?
// ↑ s[0] ↑ s[5] 是结束标记

所以有个必须记住的结论:存 n 个字符,数组至少要开 n + 1,多出来的那一位是留给 '\0' 的。

️ 为什么很多人“看得懂但不会用”:教材上讲的是 strlen / strcpy 这些函数,但编程题里几乎不会让你直接调它们——题目要的是“逐字符处理”“统计”“转换”“解析”。也就是说,你真正需要的能力不是“会背函数表”,而是把字符串当成一串可以按位操作的字符来处理。这一节就讲这个。

三条基础事实(先记牢):

1. 字符就是数字(ASCII 码)。'0' 是 48,'A' 是 65,'a' 是 97。所以 s[i] - '0' 能直接把字符数字变成整数值。

2. 大小写只差 32(第 5 位):'a' - 'A' == 32。所以 s[i] ^ 32 可以切换大小写。

3. '\0' 是结束标记,值为 0。所以 for (int i = 0; s[i]; i++) 就是“一直遍历到结尾”——这个布尔判断的写法要熟练到不用想。

常用函数速查(#include <cstring>):

函数作用坑
strlen(s)求长度(不含 '\0')O(n),别放在循环条件里
strcpy(d, s)复制目标空间要够
strcat(d, s)追加目标空间要够
strcmp(a, b)比较:<0 / 0 / >0不是返回 true/false
strchr(s, c)查字符第一次出现返回指针
strstr(s, t)查子串返回指针

输入方式对比(选错就丢数据):

写法读到空格/换行时
scanf("%s", s) / cin >> s停(读到空格就结束,空格后面的内容留在缓冲区)
fgets(s, sizeof(s), stdin)继续读,但会把 '\n' 一起存进去
cin.getline(s, n) / getline(cin, str)继续读,不含 '\n' ← 读整行推荐这个

️ 知识用法地图(先记这张表,后面每节都会回到它)

知识点(对应小节)它解决什么典型写法什么时候想到它
逐字符扫描统计 / 转换每个字符for(char c:s) + 桶 cnt[c-'a']出现「每个字符 / 字母出现几次」
字符↔数字数位和、格式输出c-'0'、'0'+d数位统计、ISBN / 多项式输出
回文 / 匹配对称判断、子串比较双指针 s[i] vs s[n-1-i]出现「回文 / 对称 / 反转」
状态机解析按规则逐段翻译枚举状态 state + switch出现「按规则展开 / 翻译 / 校验」

原讲义配图

例题

例题 1:凯撒密码(字符移位)

① 题目描述

给定一个只包含小写字母的字符串 s 和一个整数 k(0 <= k < 26)。将字符串中的每个字母向后移动 k 位(z 之后回到 a),输出变换后的字符串。

例如:输入 s = "abc", k = 2,输出 "cde";输入 s = "xyz", k = 2,输出 "zab"。

② 审题分析

③ 解题思路

1. 定义字符数组 s 存储输入字符串。

2. 使用 for 循环,从 i = 0 遍历到字符串末尾(利用 s[i] 为 '\0' 时跳出)。

3. 在循环内部,判断 s[i] 是否为小写字母 'a' 到 'z'。

4. 如果是,计算新字符:先将 s[i] 减去 'a' 归零到 0~25,加上 k,再对 26 取模,最后加上 'a' 还原为字符。

5. 如果不是小写字母(虽然题目限定只有小写,但写个 else 更健壮),直接跳过。

④ C++ 完整参考代码

#include <iostream>
#include <cstring> // 用于 strlen

using namespace std;

int main() {
 // 1. 定义字符数组,假设最大长度 100
 char s[100];
 int k;

 // 2. 输入字符串和移位量
 cin >> s >> k;

 // 3. 遍历字符串,直到遇到结束符 '\0'
 for (int i = 0; s[i]; i++) {
 // 4. 判断当前字符是否为小写字母
 if (s[i] >= 'a' && s[i] <= 'z') {
 // 5. 计算新字符:归零 -> 加 k -> 取模 -> 加回 'a'
 // (s[i] - 'a' + k) % 26 确保结果在 0~25 之间
 s[i] = 'a' + (s[i] - 'a' + k) % 26;
 }
 // 6. 非小写字母直接跳过(本题无需处理)
 }

 // 7. 输出结果
 cout << s << endl;

 return 0;
}

⑤ 代码解读(含真实运行输出)

这段代码的核心在于循环条件 s[i]。因为 char 类型在 C++ 中可以直接参与布尔判断,当 s[i] 是 '\0'(值为 0)时,条件为假,循环自动终止,无需手动判断 s[i] != '\0'。

运行示例:

例题 2:ISBN 号码校验(字符转数字)

① 题目描述

给定一个长度为 10 的字符串,表示 ISBN 号码(格式为 9 位数字 + 1 位校验码)。前 9 位数字分别乘以 10, 9, 8, ..., 2,求这 9 个乘积之和,再对 11 取模。如果余数为 10,则校验码为 X;否则校验码为余数对应的数字字符。

例如:输入 0306406152,前 9 位乘积和为 1595,1595 % 11 = 10,所以校验码是 X,输出 0306406152X。

② 审题分析

③ 解题思路

1. 定义字符数组 s 存储 ISBN。

2. 初始化 sum = 0。

3. 遍历前 9 个字符(i 从 0 到 8)。

4. 在循环内,用 s[i] - '0' 将字符转为数字。

5. 乘以对应的权重 (9 - i) 并累加到 sum。

6. 计算 sum % 11。

7. 根据余数输出校验码:10 输出 X,否则输出数字字符。

④ C++ 完整参考代码

#include <iostream>
#include <cstring>

using namespace std;

int main() {
 // 1. 定义字符数组,ISBN 固定 10 位
 char s[11]; // 10 位数字 + 1 个 '\0'
 int sum = 0;

 // 2. 输入 ISBN 字符串
 cin >> s;

 // 3. 遍历前 9 位数字
 for (int i = 0; i < 9; i++) {
 // 4. 字符 '2' 减去 '0' 得到整数 2
 int digit = s[i] - '0';
 // 5. 权重从 10 递减到 2
 sum += digit * (9 - i);
 }

 // 6. 计算余数
 int remainder = sum % 11;

 // 7. 输出结果
 cout << s;
 if (remainder == 10) {
 cout << "X";
 } else {
 // 8. 余数对应的数字字符:'0' + remainder
 cout << (char)('0' + remainder);
 }
 cout << endl;

 return 0;
}

⑤ 代码解读(含真实运行输出)

关键技巧是 s[i] - '0'。因为 ASCII 码中 '0' 是 48,'1' 是 49,...,'9' 是 57,它们连续排列。减去 '0' 相当于把字符映射到 0~9 的整数区间。

运行示例:

- i=0: '0'->0, 0*9=0

- i=1: '3'->3, 3*8=24

- i=2: '0'->0, 0*7=0

- i=3: '6'->6, 6*6=36

- i=4: '4'->4, 4*5=20

- i=5: '0'->0, 0*4=0

- i=6: '6'->6, 6*3=18

- i=7: '1'->1, 1*2=2

- i=8: '5'->5, 5*1=5

- sum = 0+24+0+36+20+0+18+2+5 = 105

- 105 % 11 = 6

例题复盘总结

共性特征:

各自技巧:

审题模板(固定思考流程):

1. 看输入:是字符串还是数字?有没有空格?长度固定吗?

2. 看要求:是统计、转换、还是校验?

3. 定策略:需要逐字符扫描吗?需要字符转数字吗?

4. 写循环:for (int i = 0; s[i]; i++) 还是固定长度?

5. 写转换:s[i] - '0' 还是 s[i] - 'a'?

错误清单只列上文没讲过的特有错误:

写法总表

技法分层解决什么典型写法
逐字符扫描统计 / 转换每个字符for(int i=0;s[i];i++) + 桶 cnt[c-'a']
字符↔数字数位和、格式输出c-'0'、'0'+d
回文 / 匹配对称判断、子串比较双指针 s[i] vs s[n-1-i]
状态机解析按规则逐段翻译枚举状态 state + switch

分工说明:触发清单告诉你“什么时候想到它”,速查表告诉你“具体怎么写”,总表则按粒度从细到粗归纳了所有技法。

常见错误

1. 数组开小了。 存 n 个字符要开 n + 1,忘了 '\0' 是最经典的越界原因。

2. strlen 放进循环条件。

```cpp

for (int i = 0; i < strlen(s); i++) // 每次都重算,O(n²)

int len = strlen(s); // 先存下来

for (int i = 0; i < len; i++)

```

3. scanf("%s") / cin >> s 读不到空格后面的内容。 要读整行请用 getline(C++)或 fgets。注意 fgets 会把 '\n' 也读进来,记得处理掉。

4. strcmp 当成布尔用。 if (strcmp(a, b)) 判断的是"不相等"(因为相等返回 0)。要写 if (strcmp(a, b) == 0) 才是相等。

5. char 是有符号的。 某些平台上 char 取值范围是 -128~127,对非 ASCII 字符做下标会越界。用 unsigned char 或 int 接一下。

6. 忘了 '\0' 在中间的情况。 手工改字符串时(比如删字符),别忘了补 s[newLen] = '\0',否则 strlen 会读到垃圾。

7. 两个字符串相加用 +。 C 风格 char 数组不能用 +(那是 std::string 才支持的)。C 风格要用 strcat。

自测

1. 存 "12345" 需要 char 数组至少多大?为什么?

2. for (int i = 0; i < strlen(s); i++) 的时间复杂度是多少?怎么改成 O(n)?

3. 写出把 char c 转成大写字母的两种写法(用函数 / 不用函数)。

4. 给定一个字符串,怎么判断它能不能通过重排变成一个回文串?

5. if (strcmp(a, b)) 这个判断什么时候为真?

6. scanf("%s", s) 和 getline(cin, s) 在读 "abc def" 时分别得到什么?

举一反三 · 审题流水线

拿到一道字符串题,别急着敲代码,先让脑子跑一遍这个流水线:

1. 先看输入格式 —— 有没有空格?是不是整行?先确定用 cin >> 还是 getline(选错直接丢数据);

2. 看题目要“处理字符”还是“处理数字” —— 处理字符就逐位扫;处理数字先想“转不转成字符串做”;

3. 找关键词判断套路 —— “次数”→桶计数;“回文/对称”→双指针;“格式/合法”→逐段解析;

4. 想清楚字符串有多长 —— 开 char 数组要 n + 1;长度到 1e6 就别用 strlen 反复算;

5. 把边界情况列出来 —— 空串、长度为 1、开头结尾是特殊字符、有前导零。字符串题的失分几乎全在边界;

6. 提交前用“边界样例”自测,别靠眼睛看代码。

练手题

题目 A:字母求和

给定一个只包含小写字母的字符串 s,求其中所有字母的 ASCII 码之和。

输入:abc

输出:969('a' 是 97,'b' 是 98,'c' 是 99,97+98+99=294,等等,这里只是示意,实际按 ASCII 算)。

题目 B:数字替换

给定一个字符串 s 和一个整数 k。将字符串中所有数字字符替换为 (数字 + k) % 10 对应的字符,其他字符保持不变。

输入:12345,k = 5

输出:67890

学完这个考点,用历年真题检验一下

OI伴学 把「字符数组与字符串处理」在 CCF-GESP / CSP / NOIP 真题里出现过的题目汇总到一页,可按考点横向刷;每题都带答案、解题思维和常见错误。

看 字符数组与字符串处理 历年真题汇总 → 去平台刷题 →