第 21 章 字符与编码——ASCII 的世界
🏗️ 前情回顾:上一章你学会了
string的查找、截取、删除、替换——文字编辑功夫全拿下。你还用了toupper('a')把小写变大写,用了isdigit(c)判断字符是不是数字。但等一下——'a'怎么就能变成'A'?计算机凭什么知道'5'是数字?凭什么'A' + 1居然等于'B'?这些"字符魔术"背后,藏着一个秘密。💡 本章要解决的问题:字符 'a' 和数字 97 之间到底是什么关系?为什么
char能当int用?计算机里只有 0 和 1,是怎么存下字母和汉字的? 这一章,我们揭开 ASCII 编码的神秘面纱。
🎯 本章目标
学完这一章,你能:
- 说出 bit、Byte、KB、MB、GB 之间的关系
- 看懂 ASCII 表,记住常用字符的编码
- 把
char当整数用,做字符加减和大小写转换 - 熟练使用
isdigit、isalpha、toupper、tolower等字符处理函数 - 理解二进制的基本概念和十进制↔二进制的转换方法
- 知道
char的本质就是一个 0~127 的整数
📖 故事引入
间谍传密信
1942 年,上海。地下党员老周拿到一封"普通家书":
"母亲身体安好,勿念。家中米面各余三斗,盐两斤,油五两。"
这封信看起来只是报平安。但中共地下电台的译电员小刘扫了一眼,立刻摊开一本小册子。他先在信里找数字——"三斗"的 3、"两斤"的 2、"五两"的 5——然后翻到手册第 3 页、第 2 行、第 5 个字:"撤"。
这本小册子,就是一个编码本——每个位置对应一个字,数字变成了文字。
计算机的"编码本"比这简单得多。它只有 128 个"位置"(叫 ASCII 码),编号 0 到 127,每个编号对应一个英文字符。比如:
- 编号 65 = 'A'
- 编号 97 = 'a'
- 编号 48 = '0'
计算机内部只认 0 和 1,但当它看到 01000001(二进制的 65),它就去编码本里查第 65 号——哦,是字母 'A'!
你看到的每一个字母,在计算机心里都是一个数字。 这一章,我们就来翻开这本"编码本"。
🧱 知识讲解
21.1 计算机的最小单位:bit
计算机内部有亿万个小开关(晶体管),每个开关只有两种状态:开或关。
用数字表示:开 = 1,关 = 0。
这一个 0 或 1,就是计算机存储信息的最小单位,叫 bit(比特,也称"位")。
1 bit = 一个 0 或一个 1。
21.2 从 bit 到 GB:存储单位家族
1 个 bit 只能表示两种状态(0 和 1),太少了。于是把 8 个 bit 捆在一起,成为一个新单位:Byte(字节)。
1 Byte(字节)= 8 bit
1 KB(千字节)= 1024 Byte
1 MB(兆字节)= 1024 KB
1 GB(千兆字节)= 1024 MB为什么是 1024 而不是 1000?因为 $1024 = 2^{10}$,在二进制世界里算起来方便。计算机的世界里,1024 才是"整数"。
一个英文字母占 1 个字节,一个汉字通常占 2~4 个字节(取决于编码方式)。
21.3 ASCII 码:字母的数字身份证
既然计算机只认数字,那字母 'A' 是怎么存的?
答案是——给每个字符分配一个数字编号。这个编号规范叫 ASCII 码(American Standard Code for Information Interchange,美国信息交换标准代码)。
常用 ASCII 码速查表:
| 字符 | ASCII 值 | 字符 | ASCII 值 | 字符 | ASCII 值 |
|---|---|---|---|---|---|
| '0' | 48 | 'A' | 65 | 'a' | 97 |
| '1' | 49 | 'B' | 66 | 'b' | 98 |
| '2' | 50 | 'C' | 67 | 'c' | 99 |
| '8' | 56 | 'Y' | 89 | 'y' | 121 |
| '9' | 57 | 'Z' | 90 | 'z' | 122 |
三大规律(背下来,受益终生):
- 数字字符连续:'0'~'9' 的 ASCII 码是 48~57(连续递增)
- 大写字母连续:'A'~'Z' 的 ASCII 码是 65~90(连续递增)
- 小写字母连续:'a'~'z' 的 ASCII 码是 97~122(连续递增)
🧠 核心记忆点:小写字母比对应的大写字母大 32。
'a' - 'A' = 97 - 65 = 32。
21.4 把 char 当整数玩
在 C++ 里,char 本质就是一个整数(ASCII 值)。你可以直接用整数给 char 赋值,也可以拿 char 做算术——这就是为什么 toupper 和 tolower 能工作的底层原理。
❌ 经典踩坑——把字符 '5' 当数字 5:
cppchar ch = '5'; int x = ch; // ❌ 以为 x = 5?错!x = 53('5' 的 ASCII 码) cout << x << endl; // 输出 53,不是 5!✅ 正确——字符数字转真数字:
cppchar ch = '5'; int x = ch - '0'; // ✅ 53 - 48 = 5 // 或者:int x = ch - 48;🧠 为什么
ch - '0'能行?因为 '0'~'9' 连续排列,任何数字字符减 '0' 就等于它代表的数值:'5' - '0' = 53 - 48 = 5。
char 做算术的神奇操作:
char ch = 65; // 65 是 'A' 的 ASCII 码
cout << ch << endl; // 输出:A
char letter = 'B';
cout << (int)letter << endl; // 输出:66 —— 把 char 转成整数看
// 字符魔术①:A → B
char c = 'A';
c = c + 1; // 65 + 1 = 66 → 'B'
cout << c << endl; // 输出:B
// 字符魔术②:小写转大写(手动版)
char lower = 'g';
char upper = lower - 32; // 103 - 32 = 71 → 'G'
cout << upper << endl; // 输出:G
// 字符魔术③:大写转小写(手动版)
char upper2 = 'M';
char lower2 = upper2 + 32; // 77 + 32 = 109 → 'm'
cout << lower2 << endl; // 输出:m
(int)letter叫强制类型转换——"把 letter 当成 int 来看"。后面章节会专门讲。
21.5 字符处理函数(标准化版)
上一章我们用了 toupper/tolower/isdigit/isalpha,现在知道它们背后的原理了:这些函数本质上就是在查 ASCII 表。
需要 #include <cctype>。
| 函数 | 作用 | 底层逻辑 |
|---|---|---|
isdigit(c) | c 是数字吗? | ASCII 码在 48~57 之间 |
isalpha(c) | c 是字母吗? | 大写 65~90 或小写 97~122 |
isupper(c) | c 是大写吗? | ASCII 码在 65~90 之间 |
islower(c) | c 是小写吗? | ASCII 码在 97~122 之间 |
isspace(c) | c 是空白吗? | 空格、换行、制表符等 |
toupper(c) | 转大写 | 如果是小写,减 32;否则不变 |
tolower(c) | 转小写 | 如果是大写,加 32;否则不变 |
#include <cctype>
#include <iostream>
using namespace std;
int main() {
char c = 'b';
cout << isalpha(c) << endl; // 非零(真)—— 'b' 是字母
cout << isdigit(c) << endl; // 0(假)—— 'b' 不是数字
cout << islower(c) << endl; // 非零 —— 'b' 是小写
char upper = toupper(c); // 'B'
cout << upper << endl;
return 0;
}💡 现在你明白了——
toupper('a')做的事就是'a' - 32 = 97 - 32 = 65 = 'A'。函数帮你做了查表和计算,你不用自己记数字。
21.6 二进制入门——计算机的母语
十进制是我们从小用的——逢十进一。有 0~9 十个数字。
二进制是计算机的母语——逢二进一。只有 0 和 1 两个数字。
| 十进制 | 二进制 | 解释 |
|---|---|---|
| 0 | 0 | — |
| 1 | 1 | — |
| 2 | 10 | 1+1=2,逢二进一,写 10 |
| 3 | 11 | 10+1 |
| 4 | 100 | 11+1=100 |
| 5 | 101 | — |
| 6 | 110 | — |
| 7 | 111 | — |
| 8 | 1000 | — |
十进制转二进制——除 2 取余,倒序排列。
举个例子:把 13 转成二进制。
13 ÷ 2 = 6 … 余 1 ↑
6 ÷ 2 = 3 … 余 0 ↑
3 ÷ 2 = 1 … 余 1 ↑
1 ÷ 2 = 0 … 余 1 ↑ 从下往上读:1101所以:13₁₀ = 1101₂
❌ 常见踩坑——除 2 取余顺序写反:
除 2 取余时从上往下读 → 得到 1011(错误!)
✅ 正确:从下往上读(倒序)→ 13₁₀ = 1101₂
二进制转十进制——按位展开求和:
1101₂ = 1×2³ + 1×2² + 0×2¹ + 1×2⁰
= 8 + 4 + 0 + 1
= 13₁₀🧠 现在回看 ASCII:'A' = 65 = 01000001₂。你每次敲键盘上的 A,计算机存的就是这串 8 位的 0 和 1。
21.7 拓展视野:一个字节能存多少?
既然 1 Byte = 8 bit,那 1 个字节能表示多少个不同的值?
8 位二进制,最小 00000000(0),最大 11111111(255)。
所以 1 Byte 能表示 0~255,共 256 种状态。
这也解释了为什么 ASCII 码只要 0~127(128 个够了)——英文字母、数字、标点符号加起来才 100 个左右。后来扩展到了 0~255(扩展 ASCII),多出来的 128 个位置用来存其他语言的字符(如法语 é、德语 ü)。
✋ 动手试试
试试 1:运行下面的代码,观察每个大写字母对应的 ASCII 值:
for (char c = 'A'; c <= 'Z'; c++) {
cout << c << "=" << (int)c << " ";
}再改循环条件为 'a' 到 'z',观察小写字母的 ASCII 值。验证大小写之间确实差 32。
试试 2:输入一个小写字母,用两种方式输出它的大写——手动减 32 和用 toupper 函数。比较两种方式的结果是否一样。
试试 3:把十进制数 100 用"除 2 取余法"手动转成二进制,再用程序验证:输出 (int)'d'('d' 的 ASCII 码),看它的 ASCII 码是多少,验证这个数字的二进制和你想的一不一样。
试试 4:写一段程序,统计用户输入的一行文字中有多少个字母、多少个数字、多少个其他符号。用 isalpha 和 isdigit 来做判断。
🦶 你踩过这些坑吗?
- [ ] 坑 1:把 1024 记成 1000——1KB = 1024B,不是 1000B(那是硬盘厂商的营销数字)
- [ ] 坑 2:以为
int x = '5'得到数字 5——实际得到 ASCII 码 53;应该写int x = '5' - '0' - [ ] 坑 3:大小写转换算错差值——
'a' - 'A' = 32,不是 30 或 26 - [ ] 坑 4:除 2 取余时顺序写反——应该从下往上读(倒序),不是从上往下
- [ ] 坑 5:
toupper(c)直接用在cout里输出没问题,但要用它修改 c 时必须赋值回去 - [ ] 坑 6:用了
isdigit/isalpha但忘记#include <cctype>
📝 练习
基础题
1. 选择题
(1)1 Byte 等于多少 bit? A. 2 B. 4 C. 8 D. 16
(2)字符 'B' 的 ASCII 码是多少? A. 64 B. 65 C. 66 D. 98
(3)二进制数 1010 对应的十进制是多少? A. 8 B. 9 C. 10 D. 12
(4)1GB 等于多少 MB? A. 100 B. 512 C. 1000 D. 1024
2. 填空题
(1)计算机存储的最小单位是 ____,8 个这样的单位组成 1 个 ____。
(2)字符 '0' 的 ASCII 码是 ____,字符 '9' 的 ASCII 码是 ____。
(3)二进制是逢 ___ 进一,只有 ___ 和 ___ 两个数字。
(4)'a' - 'A' 的结果是 ____,这意味着小写字母比大写字母的 ASCII 码大 ____。
提高题
3. 分析题
char ch = 'd';
cout << (char)(ch - 32) << endl; // 输出什么?
cout << (int)ch << endl; // 输出什么?
cout << ch - 'a' << endl; // 输出什么?先推算,再上机验证。
4. 转换题
将十进制数 42 转为二进制(手算,写出步骤)。然后验证 42 的二进制的十进制值是否等于 42。
5. 编程题 — 大小写自动转换
写一个程序:输入一个大写字母,输出它的小写字母。输入一个小写字母,输出它的大写字母。输入数字或其他字符,输出"请输入字母"。
挑战题
6. 编程题 — ASCII 对照表
输出 32~126 号的 ASCII 码对照表,每行 10 个,格式如下:
32:( ) 33:(!) 34:(") 35:(#) ...
...
65:(A) 66:(B) 67:(C) ...(提示:用循环从 32 到 126,(char)i 把整数转成字符输出。)
7. 编程题 — 凯撒密码
凯撒密码是最古老的加密方法之一:把每个字母按字母表往后移动固定位数(比如 +3),'A'→'D','X'→'A'(循环)。写一个程序,用户输入一个只含大写字母的字符串和偏移量 k,输出加密后的字符串。
示例输入:HELLO k=3 → 输出 KHOOR
(提示:c = 'A' + (c - 'A' + k) % 26。)
🧠 本章小结
存储单位:
bit → Byte → KB → MB → GB
(×8) (×1024)(×1024)(×1024)
ASCII 码(背!):
'0'~'9' → 48~57
'A'~'Z' → 65~90
'a'~'z' → 97~122
大小写差:32 → 'a' - 'A' = 32
char 的本质:
char 就是 0~127 的整数
可以加减运算:'A'+1 → 'B'
字符数字转真数字:ch - '0'
字符处理函数(需 <cctype>):
isdigit/isalpha/isupper/islower/isspace
toupper/tolower
二进制入门:
逢二进一,只有 0 和 1
十进制 → 二进制:除 2 取余,倒序排列
二进制 → 十进制:按位展开求和(×2ⁿ)
1 Byte = 8 bit → 可表示 0~255📝 配套练习
共5题。ASCII重点是"字符和整数是同一枚硬币的两面"——J0097和J0017练双向转换。。★核心(课堂必做) ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0087 | https://hydro.ac/d/srqc/p/J0087 | isalpha、桶计数、ASCII |
| ◆ 拓展 | J0097 | https://hydro.ac/d/srqc/p/J0097 | 字符→数字映射、模运算 |
| ◆ 拓展 | J0017 | https://hydro.ac/d/srqc/p/J0017 | 大小写转换、ASCII偏移 |
| ◆ 拓展 | luogu-B2038 | https://hydro.ac/p/luogu-B2038 | 奇偶ASCII、字符判断 |
| ◆ 拓展 | luogu-P5733 | https://hydro.ac/p/luogu-P5733 | toupper/tolower、库函数 |
💡 练习建议:先完成 1 道★核心题,确保掌握本章基本方法;再完成 4 道◆拓展题,覆盖不同变式和细节。
配套练习
共5题。ASCII重点是"字符和整数是同一枚硬币的两面"——J0097和J0017练双向转换。★核心(课堂必做) · ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0087 | https://hydro.ac/d/srqc/p/J0087 | isalpha、桶计数、ASCII |
| ◆ 拓展 | J0097 | https://hydro.ac/d/srqc/p/J0097 | 字符→数字映射、模运算 |
| ◆ 拓展 | J0017 | https://hydro.ac/d/srqc/p/J0017 | 大小写转换、ASCII偏移 |
| ◆ 拓展 | luogu-B2038 | https://hydro.ac/p/luogu-B2038 | 奇偶ASCII、字符判断 |
| ◆ 拓展 | luogu-P5733 | https://hydro.ac/p/luogu-P5733 | toupper/tolower、库函数 |
练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 4 道◆拓展题,覆盖不同变式和细节。
自查清单:
- [ ] 我能说出 bit、Byte、KB、MB、GB 的关系
- [ ] 我会查 ASCII 表,记住三大规律(数字 48~、大写 65~、小写 97~)
- [ ] 我理解
char的本质是整数,能用char做算术('A'+1、ch-'0') - [ ] 我能手动做十进制和二进制互转(除 2 取余,倒序排列)
- [ ] 我能正确使用
isdigit、isalpha、toupper、tolower - [ ] 我知道
'a' - 'A' = 32,理解大小写转换的原理 - [ ] 我知道为什么
int x = '5'得到 53 而不是 5
🚀 下章预告
M3 模块走到最后一站了。你手里有三件武器:一维数组(存一排数字)、二维数组(存表格)、string(存文字)。但练武的时候,拳法、腿法、剑术都是一招一招分开练的——上了擂台,敌人可不会等你按套路出牌。真实的问题从来不会告诉你"请用一维数组解我"。
下一章,M3 终极挑战——三合一综合实战!我们把数组、二维数组和 string 全部揉在一起,解决真正有挑战的综合题:成绩排名(姓名 + 多科成绩)、螺旋填数、单词统计、矩阵旋转……跨过这道坎,你就从一个"练动作"的新手,变成能"打组合拳"的战士!