# 第8课:字符数组与string ## 教学目标 - 掌握 string 类的基本操作(长度、访问、截取、查找) - 理解字符数组与 string 的区别 - 能处理字符串遍历、逆序、回文判断等常见问题 - 会用 `getline` 读取含空格的字符串 ## 核心考点:字符串输入和逐字符处理 ### 考什么 常见题型包括读取一句话、统计各类字符、查找或替换子串、反转字符串和判断回文。首先要正确读入完整字符串,然后通过下标逐个处理字符。 ### 必须理解 `cin >> s` 遇到空格就停止,只适合读取一个不含空格的单词;`getline(cin, s)` 会读取整行。如果前面刚用 `cin >>` 读取数字,输入缓冲区可能残留换行符,应先用 `cin.ignore()` 清掉,否则 `getline` 会读到空行。字符串长度为 `s.length()`,合法下标仍是 `0~length-1`。逐字符处理时,每个 `s[i]` 都是 `char`,可配合 `isdigit`、`isalpha` 等函数分类。 ### 核心写法 ```cpp int n; cin >> n; cin.ignore(); string s; getline(cin, s); for (int i = 0; i < s.length(); i++) { char c = s[i]; // 对 c 进行判断、统计或修改 } ``` 输入方式由数据格式决定;遍历上界由字符串的实际长度决定。 ### 常见错误 - 用 `cin >> s` 读取句子,只得到第一个单词。 - 混用 `cin` 和 `getline` 时忘记处理残留换行。 - 写成 `i <= s.length()`,访问字符串末尾之外的位置。 - 把字符下标与子串位置混淆,或未检查 `find` 是否找到。 ### 判断是否掌握 能根据输入是否含空格选择 `cin` 或 `getline`,能解释空行问题,并能无越界地遍历字符串完成字符统计或修改。 --- ## 题目一:字符串输入输出与字符统计 ### 核心代码(一句话) > 字符串处理的核心:**`getline(cin, s)` 读整行**,**`s.length()` 取长度**,**`s[i]` 访问每个字符**,配合字符判断函数做统计。 ### 自然语言思路 输入一句话,统计里面的大写字母、小写字母、数字、其他字符各有几个。 第一步,用 `getline(cin, s)` 读入整行(不能用 `cin >> s`,因为碰到空格就停了)。 第二步,用 `s.length()` 或 `s.size()` 拿到字符串长度。 第三步,用 `for` 循环遍历每个字符 `s[i]`。 第四步,对每个字符用判断函数分类:`isupper()` 判断大写、`islower()` 判断小写、`isdigit()` 判断数字,剩下的就是"其他"。 第五步,四个计数器分别累加,最后输出。 > 提示:`isupper`、`islower`、`isdigit` 这些函数在第1课学过,头文件是 ``。 ### 伪代码 ``` 1. 输入一行字符串 s(用 getline) 2. upper = 0, lower = 0, digit = 0, other = 0 3. for i = 0 到 s.length() - 1: 4. if isupper(s[i]): upper++ 5. else if islower(s[i]): lower++ 6. else if isdigit(s[i]): digit++ 7. else: other++ 8. 输出 upper, lower, digit, other ``` ### 真实代码 ```cpp #include #include #include // isupper, islower, isdigit using namespace std; int main() { string s; getline(cin, s); // 读整行,含空格 int upper = 0, lower = 0, digit = 0, other = 0; for (int i = 0; i < s.length(); i++) { if (isupper(s[i])) { upper++; } else if (islower(s[i])) { lower++; } else if (isdigit(s[i])) { digit++; } else { other++; } } cout << "大写:" << upper << ",小写:" << lower << ",数字:" << digit << ",其他:" << other << endl; return 0; } ``` ### 注意点 1. **`cin >> s` vs `getline(cin, s)`**:`cin` 遇空格换行就停,`getline` 读整行(含空格)。如果前面用过 `cin`,需要用 `cin.ignore()` 吃掉残留的换行符。 2. **`s.length()` vs `s.size()`**:两个完全一样,都可以用,没有区别。 3. **访问越界**:下标范围 `0` 到 `s.length() - 1`,访问 `s[s.length()]` 会越界。 4. **空格算"其他"**:上面的代码中空格既不是大写/小写/数字,会归入 other。如果想单独统计空格,加一个 `else if (s[i] == ' ')`。 --- ## 题目二:字符串查找与替换 ### 核心代码(一句话) > 查找用 **`s.find("目标")`**,找不到返回 **`string::npos`**;替换用 **`s.replace(起始位置, 长度, "新内容")`**;截取用 **`s.substr(起始位置, 长度)`**。 ### 自然语言思路 在一句话里找到某个词的位置,然后把它替换成另一个词。 第一步,用 `s.find("目标词")` 查找,它会返回目标词第一次出现的位置(下标)。 第二步,如果找到了(返回值 `!= string::npos`),用 `s.replace(位置, 长度, "新词")` 替换。 第三步,如果找不到,`find` 返回 `string::npos`(一个很大的特殊值),直接提示"没找到"。 第四步,`substr` 用来截取子串,比如 `s.substr(0, 5)` 表示从下标 0 开始取 5 个字符。 ### 伪代码 ``` 1. 输入字符串 s 2. 输入要查找的词 target 3. pos = s.find(target) 4. if pos != string::npos: 5. 输入替换词 replacement 6. s.replace(pos, target.length(), replacement) 7. 输出 s 8. else: 9. 输出 "未找到" ``` ### 真实代码 ```cpp #include #include using namespace std; int main() { string s; getline(cin, s); string target; cin >> target; int pos = s.find(target); if (pos != string::npos) { string replacement; cin >> replacement; s.replace(pos, target.length(), replacement); cout << s << endl; } else { cout << "未找到" << endl; } return 0; } ``` ### 注意点 1. **`string::npos` 是什么**:`npos` = "no position",是一个特殊常量,表示"没找到"。不要用 `-1` 或 `0` 来判断。 2. **`replace` 会修改原字符串**:`s.replace(...)` 直接改了 `s`,如果还需要原来的值,先备份。 3. **`substr` 参数**:`s.substr(开始下标, 长度)`。如果省略第二个参数,默认截取到末尾。 4. **find 只找第一次出现**:如果想找所有出现的位置,需要循环 + 更新起始位置。 --- ## 题目三:字符串逆序与回文判断 ### 核心代码(一句话) > 字符串逆序的核心:**从最后一个字符 `s.length()-1` 遍历到第一个字符 `0`**;回文判断的核心:**首尾字符两两比较,`s[i] != s[n-1-i]` 就不是回文**。 ### 自然语言思路 **字符串逆序输出**: 第一步,拿到最后一个字符的下标:`s.length() - 1`。 第二步,从后往前遍历:`for (i = s.length()-1; i >= 0; i--)`。 第三步,逐个输出 `s[i]`,就得到了逆序字符串。 **判断回文**: 第一步,回文就是正着读和反着读一样,比如 "abcba"、"level"。 第二步,只需要比较前一半和后一半:`s[0]` 比 `s[n-1]`,`s[1]` 比 `s[n-2]`……直到中间。 第三步,用 `bool` 变量记录结果,默认 `true`。一旦发现不相等,改成 `false` 并 `break`。 第四步,别忘了字符串可能有空格,但 GESP 题目一般用 `cin` 读单个词即可。 ### 伪代码 **字符串逆序**: ``` 1. 输入 s 2. for i = s.length()-1 递减到 0: 3. 输出 s[i] 4. 输出换行 ``` **回文判断**: ``` 1. 输入 s 2. n = s.length() 3. isPalindrome = true 4. for i = 0 到 n/2 - 1: 5. if s[i] != s[n-1-i]: 6. isPalindrome = false 7. break 8. if isPalindrome: 9. 输出 "是回文" 10. else: 11. 输出 "不是回文" ``` ### 真实代码 **字符串逆序**: ```cpp #include #include using namespace std; int main() { string s; cin >> s; for (int i = s.length() - 1; i >= 0; i--) { cout << s[i]; } cout << endl; return 0; } ``` **回文判断**: ```cpp #include #include using namespace std; int main() { string s; cin >> s; int n = s.length(); bool isPalindrome = true; for (int i = 0; i < n / 2; i++) { if (s[i] != s[n - 1 - i]) { isPalindrome = false; break; } } if (isPalindrome) { cout << "是回文" << endl; } else { cout << "不是回文" << endl; } return 0; } ``` ### 注意点 1. **逆序遍历注意边界**:`for (i = s.length()-1; i >= 0; i--)`,不能忘了减 1,否则越界。 2. **回文只需比一半**:循环 `n/2` 次就够了。如果 n=5,比下标 (0,4) 和 (1,3),中间的下标 2 不用比。 3. **大小写问题**:严格判断回文时 "Aba" 不是回文(A ≠ a)。如果要忽略大小写,可以先用 `tolower`/`toupper` 统一转换。 4. **string 可以直接用 `==` 比较**:`s1 == s2` 判断两个字符串是否完全相等,比字符数组方便得多。 --- ## 字符数组 vs string 速查 | 特性 | 字符数组 `char[]` | string 类 | |------|-------------------|-----------| | 头文件 | 不需要(或 ``) | `` | | 定义 | `char str[100];` | `string s;` | | 输入 | `cin >> str;` | `cin >> s;` | | 读整行 | `cin.getline(str, 100);` | `getline(cin, s);` | | 长度 | `strlen(str)` | `s.length()` 或 `s.size()` | | 拼接 | `strcat(str1, str2);` | `s1 + s2` | | 比较 | `strcmp(str1, str2)` | `s1 == s2` | | 结尾标志 | `'\0'` | 不需要关心 | > **建议**:GESP 考试中优先用 `string`,操作更简单,不容易出错。 --- ## 本课打油诗 > string 类真好用,拼接查找样样通。 > getline 读整行,length 长度记心中。 > 下标访问像数组,逆序只需倒着走。 > 回文首尾比一半,find 定位快如风。 --- ## 课后作业 1. 输入一个字符串,统计其中数字、字母、其他字符的个数 2. 输入一个字符串,删除其中所有的空格后输出 3. 输入两个字符串 A 和 B,判断 B 是否是 A 的子串 4. 输入一个字符串,将其中的数字提取出来组成新字符串并输出