302 lines
9.9 KiB
Markdown
302 lines
9.9 KiB
Markdown
# 第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课学过,头文件是 `<cctype>`。
|
||
|
||
### 伪代码
|
||
```
|
||
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 <iostream>
|
||
#include <string>
|
||
#include <cctype> // 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 <iostream>
|
||
#include <string>
|
||
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 <iostream>
|
||
#include <string>
|
||
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 <iostream>
|
||
#include <string>
|
||
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 类 |
|
||
|------|-------------------|-----------|
|
||
| 头文件 | 不需要(或 `<cstring>`) | `<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. 输入一个字符串,将其中的数字提取出来组成新字符串并输出
|