Skip to content

第 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 当整数用,做字符加减和大小写转换
  • 熟练使用 isdigitisalphatouppertolower 等字符处理函数
  • 理解二进制的基本概念和十进制↔二进制的转换方法
  • 知道 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

三大规律(背下来,受益终生):

  1. 数字字符连续:'0'~'9' 的 ASCII 码是 48~57(连续递增)
  2. 大写字母连续:'A'~'Z' 的 ASCII 码是 65~90(连续递增)
  3. 小写字母连续:'a'~'z' 的 ASCII 码是 97~122(连续递增)

🧠 核心记忆点:小写字母比对应的大写字母大 32。'a' - 'A' = 97 - 65 = 32

21.4 把 char 当整数玩

在 C++ 里,char 本质就是一个整数(ASCII 值)。你可以直接用整数给 char 赋值,也可以拿 char 做算术——这就是为什么 touppertolower 能工作的底层原理。

经典踩坑——把字符 '5' 当数字 5:

cpp
char ch = '5';
int x = ch;          // ❌ 以为 x = 5?错!x = 53('5' 的 ASCII 码)
cout << x << endl;   // 输出 53,不是 5!

正确——字符数字转真数字:

cpp
char ch = '5';
int x = ch - '0';    // ✅ 53 - 48 = 5
// 或者:int x = ch - 48;

🧠 为什么 ch - '0' 能行?因为 '0'~'9' 连续排列,任何数字字符减 '0' 就等于它代表的数值:'5' - '0' = 53 - 48 = 5

char 做算术的神奇操作:

cpp
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;否则不变
cpp
#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 两个数字。

十进制二进制解释
00
11
2101+1=2,逢二进一,写 10
31110+1
410011+1=100
5101
6110
7111
81000

十进制转二进制——除 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 值:

cpp
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:写一段程序,统计用户输入的一行文字中有多少个字母、多少个数字、多少个其他符号。用 isalphaisdigit 来做判断。


🦶 你踩过这些坑吗?

  • [ ] 坑 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 取余时顺序写反——应该从下往上读(倒序),不是从上往下
  • [ ] 坑 5toupper(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. 分析题

cpp
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练双向转换。。★核心(课堂必做) ◆拓展(课后练习)

级别题号链接覆盖知识点
★ 核心J0087https://hydro.ac/d/srqc/p/J0087isalpha、桶计数、ASCII
◆ 拓展J0097https://hydro.ac/d/srqc/p/J0097字符→数字映射、模运算
◆ 拓展J0017https://hydro.ac/d/srqc/p/J0017大小写转换、ASCII偏移
◆ 拓展luogu-B2038https://hydro.ac/p/luogu-B2038奇偶ASCII、字符判断
◆ 拓展luogu-P5733https://hydro.ac/p/luogu-P5733toupper/tolower、库函数

💡 练习建议:先完成 1 道★核心题,确保掌握本章基本方法;再完成 4 道◆拓展题,覆盖不同变式和细节。


配套练习

共5题。ASCII重点是"字符和整数是同一枚硬币的两面"——J0097和J0017练双向转换。★核心(课堂必做) · ◆拓展(课后练习)

级别题号链接覆盖知识点
★ 核心J0087https://hydro.ac/d/srqc/p/J0087isalpha、桶计数、ASCII
◆ 拓展J0097https://hydro.ac/d/srqc/p/J0097字符→数字映射、模运算
◆ 拓展J0017https://hydro.ac/d/srqc/p/J0017大小写转换、ASCII偏移
◆ 拓展luogu-B2038https://hydro.ac/p/luogu-B2038奇偶ASCII、字符判断
◆ 拓展luogu-P5733https://hydro.ac/p/luogu-P5733toupper/tolower、库函数

练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 4 道◆拓展题,覆盖不同变式和细节。

自查清单

  • [ ] 我能说出 bit、Byte、KB、MB、GB 的关系
  • [ ] 我会查 ASCII 表,记住三大规律(数字 48~、大写 65~、小写 97~)
  • [ ] 我理解 char 的本质是整数,能用 char 做算术('A'+1ch-'0'
  • [ ] 我能手动做十进制和二进制互转(除 2 取余,倒序排列)
  • [ ] 我能正确使用 isdigitisalphatouppertolower
  • [ ] 我知道 'a' - 'A' = 32,理解大小写转换的原理
  • [ ] 我知道为什么 int x = '5' 得到 53 而不是 5

🚀 下章预告

M3 模块走到最后一站了。你手里有三件武器:一维数组(存一排数字)、二维数组(存表格)、string(存文字)。但练武的时候,拳法、腿法、剑术都是一招一招分开练的——上了擂台,敌人可不会等你按套路出牌。真实的问题从来不会告诉你"请用一维数组解我"。

下一章,M3 终极挑战——三合一综合实战!我们把数组、二维数组和 string 全部揉在一起,解决真正有挑战的综合题:成绩排名(姓名 + 多科成绩)、螺旋填数、单词统计、矩阵旋转……跨过这道坎,你就从一个"练动作"的新手,变成能"打组合拳"的战士!