B 班 第 5 课:字符串与字符处理

适用对象: 已完成第 4 课(数组)的同学 使用方式: 自学讲义。本课延续第 2 课起的厚度——4 个知识模块 + 8 题练习 + 加餐,做完大约需要 1.5 小时 学完本课,你应该能:

  1. string 存一段文字,会取长度、拼接、按下标取字符
  2. 分清 cin >> s(读一个词)和 getline(cin, s)(读一整行)
  3. 理解 C++ 独有的 char 类型和单引号,会用 s[i] - '0'c - 'a' + 'A' 这类字符运算

0. 热身:数组三部曲回顾(10 分钟)

先把上节课的"读入-处理-输出"三部曲跑一遍热热身——读入 n 个成绩,统计及格(≥60)的人数:

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    int a[1005];
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        if (a[i] >= 60) cnt++;
    }
    cout << cnt << endl;
    return 0;
}
输入:
5
78 45 92 60 30

输出:
3

上节课加餐里我提过一句:"字符串其实也是字符的数组,s[i] 能取出单个字符。" 今天就把这句话彻底展开。


1. string 基础:和 Python str 的对照(20 分钟)

Python 你这样写:

s = "hello"
t = s + " world"      # 拼接
print(len(s))         # 5
print(t)              # hello world
print(s[0], s[4])     # h o

C++ 改成这样:

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s = "hello";
    string t = s + " world";          // 拼接,和 Python 一样用 +
    cout << s.length() << endl;       // 5
    cout << t << endl;                // hello world
    cout << s[0] << " " << s[4] << endl;   // h o
    return 0;
}

输出:

5
hello world
h o

对照下来只有一处真正不同:求长度。

Python C++
len(s) s.length()s.size()(两个完全一样,随便用哪个)

📝 注意这个反差:上节课说过 C++ 数组没有 len,必须自己另开变量 n 记账;但 string 是有长度的——它比数组"聪明",自己记得住装了多少个字符。所以处理字符串时不需要额外传一个长度变量。

遍历字符串——就是上节课的数组循环,把 a[i] 换成 s[i]

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s = "abc";
    for (int i = 0; i < s.length(); i++) {
        cout << s[i] << " ";
    }
    cout << endl;
    return 0;
}

输出:a b c

📝 有的编译器会对 i < s.length() 提示一句 comparison between signed and unsigned integer expressions 警告——这只是警告不是错误,程序照跑不误,原因以后学,现在直接忽略。

动手检查点 1(5 分钟)

新建 check1.cpp:定义 string s = "python";,输出它的长度,再用循环把每个字符倒着输出(提示:从 s.length() - 1 倒数到 0,就是上节课的数组逆序)。


2. 读入:cin >> sgetline 的区别(15 分钟)

Python 的 input() 读一整行,怎么读都不会出错。C++ 这里有个Python 学生百分之百会踩的坑

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin >> s;
    cout << s << endl;
    return 0;
}

输入 hello world,输出却只有:

hello

⚠️ cin >> s 遇到空格就停下来,只能读到一个"单词"。这和 cin >> n 读整数是同一套规则——空格是分隔符。

要读一整行(包含空格),必须用 getline

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin, s);          // 读一整行,直到回车
    cout << s << endl;
    return 0;
}

输入 hello world,输出:

hello world
Python C++ 读到什么
input() getline(cin, s); 一整行(含空格)
input().split()[0] cin >> s; 一个词,遇空格停

⚠️ 混用大坑:cin >> n 之后紧接着 getline,会读到一个空行。 因为 cin >> n 读完数字后,把行末的那个回车留在了输入里,getline 一上来就撞上它,以为这一行读完了。解决办法是中间加一句 cin.ignore();

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    cin.ignore();             // 把 cin >> n 剩下的那个回车扔掉
    string s;
    getline(cin, s);
    cout << n << " " << s << endl;
    return 0;
}
输入:
3
hello world

输出:
3 hello world

📝 先照抄 cin.ignore(); 这一行,记住"cin >> 后面要接 getline,中间加一句 ignore"这条规矩就够了,它的完整原理以后学。


3. char:C++ 独有的"单个字符"类型(20 分钟,本课重点)

这是Python 里根本没有的概念。Python 中 "a"'a' 完全一样,都是长度为 1 的字符串;C++ 里这两个是两种不同的类型

写法 类型 含义
'a' 单引号 char 一个字符
"a" 双引号 string 一个字符串(哪怕只有一个字符)

s[i] 取出来的东西,类型就是 char

关键认知:char 本质上就是一个数字。 每个字符都有一个编号(叫 ASCII 码),'a' 是 97,'b' 是 98,'A' 是 65,'0' 是 48……正因为是数字,字符才能加减和比大小:

#include <bits/stdc++.h>
using namespace std;

int main() {
    char c = 'a';
    cout << c << endl;              // a
    cout << (int)c << endl;         // 97 —— 强行当数字看,就是它的编号

    char d = c - 'a' + 'A';         // 小写转大写
    cout << d << endl;              // A

    char num = '7';
    cout << num - '0' << endl;      // 7 —— 数字字符转成真正的数字
    return 0;
}

输出:

a
97
A
7

两个必须背下来的套路:

目的 写法 原理
数字字符 → 数值 s[i] - '0' '7' 是 55,'0' 是 48,相减得 7
小写 → 大写 c - 'a' + 'A' 先算出它是第几个字母,再从 'A' 数过去

三个必须背下来的判断:

c >= 'a' && c <= 'z'      // 是小写字母
c >= 'A' && c <= 'Z'      // 是大写字母
c >= '0' && c <= '9'      // 是数字字符

⚠️ c >= '0' && c <= '9' 判断的是"字符 '7'",不是"数字 7"。 别把 s[i] == 7s[i] == '7' 搞混了——前者在比编号 7(那是个看不见的控制字符),后者才是你想要的。

把上节课的"计数套路"搬到字符串上——统计元音字母的个数:

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s = "programming";
    int cnt = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s[i];
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') cnt++;
    }
    cout << cnt << endl;
    return 0;
}

输出:3oai 三个)

📝 看出来了吗?这就是上节课数组计数套路的原样搬运——只是把 a[i] 换成了 s[i],把"数偶数"换成了"数元音"。学过的东西没白学。


4. 读错误:3 个字符串专属的坑(10 分钟)

错误 1:单引号双引号用混了

char c = "a";        ← 双引号是字符串,不能塞进 char 里

编译器报错大致长这样:

error: invalid conversion from 'const char*' to 'char'

看到 invalid conversion(无效的类型转换)+ char* 这些词,第一反应就是去检查引号。反过来 string s = 'a'; 也是错的。

📝 char 配单引号,string 配双引号,一一对应,不要混。

错误 2:cin >> s 读不到空格后面的内容

string s;
cin >> s;               // 输入 hello world
cout << s << endl;      // 只输出 hello,world 不见了

不是程序崩了,是 cin >> 本来就只读一个词。要整行就换 getline(cin, s)

错误 3:字符串下标越界

string s = "abc";
cout << s[3] << endl;   ← s 只有下标 0、1、2

和数组越界一模一样:s 的有效下标是 0s.length() - 1。循环条件写 i < s.length()千万别写成 i <= s.length()

📝 字符串的下标规则和数组完全一样,上节课那两个坑在这里原样成立。


5. 本课练习(8 题,35 分钟起步,做不完当课后作业)

⭐ 基础题 1:字符串长度(文件名 t1.cpp

输入一个不含空格的字符串,输出它的长度。

输入样例:
hello

输出样例:
5
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin >> s;
    cout << s.length() << endl;
    return 0;
}

⭐ 基础题 2:逐字符输出(文件名 t2.cpp

输入一个不含空格的字符串,把每个字符单独输出一行。

输入样例:
abc

输出样例:
a
b
c
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin >> s;
    for (int i = 0; i < s.length(); i++) {
        cout << s[i] << endl;
    }
    return 0;
}

⭐⭐ 实战题 3:统计元音(文件名 t3.cpp

输入一个不含空格的小写字符串,输出其中元音字母(a e i o u)的个数。

输入样例:
programming

输出样例:
3
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin >> s;
    int cnt = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s[i];
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') cnt++;
    }
    cout << cnt << endl;
    return 0;
}

⭐⭐ 实战题 4:全部转大写(文件名 t4.cpp

输入一整行文字(可能含空格和标点),把其中的小写字母全部转成大写,其余字符原样保留,输出结果。

输入样例:
Hello, World!

输出样例:
HELLO, WORLD!
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin, s);
    for (int i = 0; i < s.length(); i++) {
        if (s[i] >= 'a' && s[i] <= 'z') {
            s[i] = s[i] - 'a' + 'A';       // 直接改回原字符串
        }
    }
    cout << s << endl;
    return 0;
}

因为有空格和标点,必须用 getline。注意 s[i] = ... ——字符串的下标和数组一样,既能读也能写


⭐⭐ 实战题 5:字符分类统计(文件名 t5.cpp

输入一整行文字,统计其中字母、数字、其他字符各有多少个,用空格分隔输出三个数。

输入样例:
abc 123 !!

输出样例:
3 3 4

(3 个字母、3 个数字,其余 2 个空格 + 2 个感叹号共 4 个。)

参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin, s);
    int letter = 0, digit = 0, other = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s[i];
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) letter++;
        else if (c >= '0' && c <= '9') digit++;
        else other++;
    }
    cout << letter << " " << digit << " " << other << endl;
    return 0;
}

三个计数变量各管一类,if / else if / else 保证每个字符只被数一次——这是第 2 课的 else if 链在字符串上的应用。


⭐⭐⭐ 冲刺题 6:回文判断(文件名 t6.cpp

输入一个不含空格的字符串,判断它是不是回文(正着读和倒着读一样)。是输出 是回文,不是输出 不是回文

输入样例1:      输入样例2:
level           hello

输出样例1:      输出样例2:
是回文          不是回文
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin >> s;
    int len = s.length();
    bool ok = true;
    for (int i = 0; i < len / 2; i++) {
        if (s[i] != s[len - 1 - i]) ok = false;     // 第 i 个 和 倒数第 i 个 比
    }
    cout << (ok ? "是回文" : "不是回文") << endl;
    return 0;
}

s[i]s[len-1-i] 是"对称的一对":i=0 配最后一个,i=1 配倒数第二个……只需要比到中间(i < len / 2)就够了,再往后就是重复比一遍。


⭐⭐⭐ 冲刺题 7:各位数字之和(文件名 t7.cpp

输入一个只由数字字符组成的字符串(可能很长,比 int 装得下的还长),求各位数字之和。

输入样例:
12345

输出样例:
15
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin >> s;
    int sum = 0;
    for (int i = 0; i < s.length(); i++) {
        sum += s[i] - '0';        // 数字字符 → 数值
    }
    cout << sum << endl;
    return 0;
}

📝 这题演示了字符串的一个重要用途:装一个超大的数。 int 最多装到 21 亿左右,但用字符串可以装 100 位甚至更长的数字,再一位一位处理。高精度计算就是这么做的,以后学


⭐⭐⭐ 冲刺题 8:查找字符(文件名 t8.cpp,选做)

第一行输入一个不含空格的字符串,第二行输入一个字符。输出该字符出现的次数,第二行输出它出现的所有下标(从 0 开始,空格分隔)。保证至少出现一次。

输入样例:
banana
a

输出样例:
3
1 3 5
参考答案
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    char c;
    cin >> s >> c;

    int cnt = 0;
    for (int i = 0; i < s.length(); i++) {
        if (s[i] == c) cnt++;
    }
    cout << cnt << endl;

    for (int i = 0; i < s.length(); i++) {
        if (s[i] == c) cout << i << " ";
    }
    cout << endl;
    return 0;
}

cin >> c 读一个 char,正好和 s[i] 类型一致,可以直接用 == 比较。C++ 其实有个现成的 s.find() 函数能找位置,以后学——现在自己用循环写一遍,才明白它在干什么。


6. 学有余力:取子串与字典序(加餐,可跳过)

取子串 s.substr(起始下标, 取几个)

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s = "programming";
    cout << s.substr(0, 3) << endl;      // pro
    cout << s.substr(3, 4) << endl;      // gram

    string a = "apple", b = "banana";
    if (a < b) cout << a << " 排在 " << b << " 前面" << endl;
    return 0;
}

输出:

pro
gram
apple 排在 banana 前面

s.find()(查子串位置)、reverse()(翻转)、stoi / to_string(字符串和数字互转)、二维数组、vector 一律以后学


本课要点速查

Python C++ 备注
s = "hello" string s = "hello"; 双引号
len(s) s.length()s.size() 数组没有,但 string 有
s + t s + t 拼接写法一样
s[i] s[i] 下标从 0 开始,可读可写
input() getline(cin, s); 读一整行(含空格)
(无对应) cin >> s; 只读一个词,遇空格停
'a'"a" 一样 'a'char"a"string C++ 独有的区分
ord(c) (int)c 字符本来就是数字,直接减也行
s[3:7] s.substr(3, 4) 参数是"起点 + 个数",不是"起点 + 终点"

字符运算三件套:

s[i] - '0'                     // 数字字符 → 数值
c - 'a' + 'A'                  // 小写 → 大写
c >= '0' && c <= '9'           // 判断是不是数字字符

三个字符串专属坑:

症状 原因
invalid conversion from 'const char*' char 用了双引号
输入 hello world 只读到 hello 该用 getline 却用了 cin >> s
cin >> ngetline 读到空行 中间少了 cin.ignore();

本课新符号: stringchar'单引号's.length()getline(cin, s)cin.ignore()(int)cs.substr(pos, len)


结束前的自我检查

  1. 不看讲义,说出 cin >> sgetline(cin, s) 的区别,各举一个该用哪个的场景
  2. 向别人解释:'a'"a" 在 C++ 里为什么是两个不同的东西?Python 里为什么没这个麻烦?
  3. 练习 8 题至少完成到题 7,每题样例都亲手验证过
  4. 合上讲义,把"遍历字符串统计某类字符个数"的模板一次性写对