写代码这些年,我电脑里收藏夹里躺得最久的几张图,除了运维画的网络拓扑,就是各种版本的ASCII码对应表。别看它只是张密密麻麻的表格,串口通信、协议解析、字符编码转换、单片机调试,全都绕不开它。很多刚接触嵌入式和底层开发的朋友,第一次被十六进制字节折磨得头皮发麻,往往就是因为没把这张表在心里盘熟。这篇内容把我最常用、也最完整的那份ASCII码对应表整理出来,顺带把控制字符、可打印字符、常见编码坑都讲透,适合正在学C语言、搞单片机、写网络协议、做数据解析的开发者收藏,也适合所有想搞明白“字符在计算机里到底长什么样”的人。
1. 为什么ASCII码对应表值得你认真对待
1.1 字符与数字的第一座桥
计算机底层不认字母,不认标点,只认二进制。但人在读写数据的时候,总得用ABCD、用0到9来表达信息。ASCII(American Standard Code for Information Interchange,美国信息交换标准码)就是给每个常见字符分配一个固定编号的方案。一个字符用7位二进制表示,范围从0到127,这就是大家常说的128个字符。
你可以把它理解成一张字典:编号65对应大写字母A,编号97对应小写字母a。当你在串口助手里发送0x41,接收端如果按ASCII解释,显示出来就是字符A;如果按原始数值处理,它就是十进制的65。同样一串字节,解释方式不同,结果天差地别。所以搞通信、搞文件解析的人,必须随时能在“字符”和“数字”之间来回切换,这张对应表就是切换用的翻译词典。
1.2 谁需要这份表
说实话,工作这么多年,我认为这张表的使用频率比很多人想象中高得多。写串口调试工具、解析Modbus或自定义帧协议、处理HTTP报文、写编译器词法分析、做单片机按键扫描,都会频繁用到。甚至前端处理用户输入,把输入框里的数字字符串转成整数时,也脱不开字符与数值的转换逻辑。
新手最容易犯的错,是把字符‘5’和数字5混为一谈。在C语言里,‘5’被存储在内存中是十进制53,也就是0x35。你要是拿它直接参与数值计算,算出来的结果必然跑偏。这就是为什么我总跟刚入行的朋友说,先别急着背各种高级框架API,把ASCII表吃透,后面的坑能少踩一大半。
2. 完整ASCII码对照表(0-127)
2.1 控制字符区:0-31与127
先看0到31这32个字符,它们都是不可见控制符,再加上最后的127(DEL),英文简写、全称、含义都列在表里了。
| 十进制 | 十六进制 | 缩写 | 英文全称 | 含义说明 |
|---|---|---|---|---|
| 0 | 0x00 | NUL | Null | 空字符,早期用于填充,C语言字符串终止符就是它 |
| 1 | 0x01 | SOH | Start of Heading | 标题开始,电报/通信中表示报头开始 |
| 2 | 0x02 | STX | Start of Text | 正文开始 |
| 3 | 0x03 | ETX | End of Text | 正文结束 |
| 4 | 0x04 | EOT | End of Transmission | 传输结束 |
| 5 | 0x05 | ENQ | Enquiry | 请求应答 |
| 6 | 0x06 | ACK | Acknowledge | 确认应答 |
| 7 | 0x07 | BEL | Bell | 响铃,终端蜂鸣提示 |
| 8 | 0x08 | BS | Backspace | 退格,光标向左移动一格 |
| 9 | 0x09 | HT | Horizontal Tab | 水平制表符,跳到下一个制表位 |
| 10 | 0x0A | LF | Line Feed | 换行,Unix/Linux换行符 |
| 11 | 0x0B | VT | Vertical Tab | 垂直制表符 |
| 12 | 0x0C | FF | Form Feed | 换页,打印机送纸一页 |
| 13 | 0x0D | CR | Carriage Return | 回车,光标回到行首,Windows换行符的一部分 |
| 14 | 0x0E | SO | Shift Out | 切换字符集 |
| 15 | 0x0F | SI | Shift In | 恢复字符集 |
| 16 | 0x10 | DLE | Data Link Escape | 数据链路转义 |
| 17 | 0x11 | DC1 | Device Control 1 | 设备控制1,常作为XON软件流控信号 |
| 18 | 0x12 | DC2 | Device Control 2 | 设备控制2 |
| 19 | 0x13 | DC3 | Device Control 3 | 设备控制3,常作为XOFF软件流控信号 |
| 20 | 0x14 | DC4 | Device Control 4 | 设备控制4 |
| 21 | 0x15 | NAK | Negative Acknowledge | 否定应答 |
| 22 | 0x16 | SYN | Synchronous Idle | 同步空闲 |
| 23 | 0x17 | ETB | End of Transmission Block | 传输块结束 |
| 24 | 0x18 | CAN | Cancel | 取消之前数据 |
| 25 | 0x19 | EM | End of Medium | 介质结束 |
| 26 | 0x1A | SUB | Substitute | 替换字符,DOS下常用作Ctrl+Z结束输入 |
| 27 | 0x1B | ESC | Escape | 转义字符,ANSI终端控制序列由此开始 |
| 28 | 0x1C | FS | File Separator | 文件分隔符 |
| 29 | 0x1D | GS | Group Separator | 组分隔符 |
| 30 | 0x1E | RS | Record Separator | 记录分隔符 |
| 31 | 0x1F | US | Unit Separator | 单元分隔符 |
| 127 | 0x7F | DEL | Delete | 删除,来源于穿孔纸带“全部打孔”表示抹除 |
2.2 可打印字符区之一:32-63
从32开始就都是肉眼可见的字符了。32是空格,它也是字符,别忽略。
| 十进制 | 十六进制 | 字符 | 说明 |
|---|---|---|---|
| 32 | 0x20 | (空格) | 空格,最常用的分隔符 |
| 33 | 0x21 | ! | 叹号 |
| 34 | 0x22 | " | 双引号 |
| 35 | 0x23 | # | 井号,C语言预处理指令、十六进制前缀都用它 |
| 36 | 0x24 | $ | 美元符号,Shell脚本变量前缀 |
| 37 | 0x25 | % | 百分号,printf格式化占位符 |
| 38 | 0x26 | & | 和号,C语言取地址运算符 |
| 39 | 0x27 | ' | 单引号,C语言字符常量定界符 |
| 40 | 0x28 | ( | 左圆括号 |
| 41 | 0x29 | ) | 右圆括号 |
| 42 | 0x2A | * | 星号,乘法或指针解引用 |
| 43 | 0x2B | + | 加号 |
| 44 | 0x2C | , | 逗号 |
| 45 | 0x2D | - | 减号 / 连字符 |
| 46 | 0x2E | . | 句点,小数点 |
| 47 | 0x2F | / | 斜杠,路径分隔符、除法运算符 |
| 48 | 0x30 | 0 | 数字0 |
| 49 | 0x31 | 1 | 数字1 |
| 50 | 0x32 | 2 | 数字2 |
| 51 | 0x33 | 3 | 数字3 |
| 52 | 0x34 | 4 | 数字4 |
| 53 | 0x35 | 5 | 数字5 |
| 54 | 0x36 | 6 | 数字6 |
| 55 | 0x37 | 7 | 数字7 |
| 56 | 0x38 | 8 | 数字8 |
| 57 | 0x39 | 9 | 数字9 |
| 58 | 0x3A | : | 冒号 |
| 59 | 0x3B | ; | 分号 |
| 60 | 0x3C | < | 小于号 |
| 61 | 0x3D | = | 等号 |
| 62 | 0x3E | > | 大于号 |
| 63 | 0x3F | ? | 问号 |
2.3 可打印字符区之二:64-95
| 十进制 | 十六进制 | 字符 | 说明 |
|---|---|---|---|
| 64 | 0x40 | @ | 邮箱地址中分隔用户名与域名 |
| 65 | 0x41 | A | 大写字母A |
| 66 | 0x42 | B | 大写字母B |
| 67 | 0x43 | C | 大写字母C |
| 68 | 0x44 | D | 大写字母D |
| 69 | 0x45 | E | 大写字母E |
| 70 | 0x46 | F | 大写字母F |
| 71 | 0x47 | G | 大写字母G |
| 72 | 0x48 | H | 大写字母H |
| 73 | 0x49 | I | 大写字母I |
| 74 | 0x4A | J | 大写字母J |
| 75 | 0x4B | K | 大写字母K |
| 76 | 0x4C | L | 大写字母L |
| 77 | 0x4D | M | 大写字母M |
| 78 | 0x4E | N | 大写字母N |
| 79 | 0x4F | O | 大写字母O |
| 80 | 0x50 | P | 大写字母P |
| 81 | 0x51 | Q | 大写字母Q |
| 82 | 0x52 | R | 大写字母R |
| 83 | 0x53 | S | 大写字母S |
| 84 | 0x54 | T | 大写字母T |
| 85 | 0x55 | U | 大写字母U |
| 86 | 0x56 | V | 大写字母V |
| 87 | 0x57 | W | 大写字母W |
| 88 | 0x58 | X | 大写字母X |
| 89 | 0x59 | Y | 大写字母Y |
| 90 | 0x5A | Z | 大写字母Z |
| 91 | 0x5B | [ | 左方括号,数组下标运算符 |
| 92 | 0x5C | \ | 反斜杠,转义字符前缀、Windows路径分隔符 |
| 93 | 0x5D | ] | 右方括号 |
| 94 | 0x5E | ^ | 脱字符,C语言异或运算符 |
| 95 | 0x5F | _ | 下划线,命名字符的常用连接符 |
2.4 可打印字符区之三:96-126
| 十进制 | 十六进制 | 字符 | 说明 |
|---|---|---|---|
| 96 | 0x60 | ` | 反引号,Shell命令替换符 |
| 97 | 0x61 | a | 小写字母a |
| 98 | 0x62 | b | 小写字母b |
| 99 | 0x63 | c | 小写字母c |
| 100 | 0x64 | d | 小写字母d |
| 101 | 0x65 | e | 小写字母e |
| 102 | 0x66 | f | 小写字母f |
| 103 | 0x67 | g | 小写字母g |
| 104 | 0x68 | h | 小写字母h |
| 105 | 0x69 | i | 小写字母i |
| 106 | 0x6A | j | 小写字母j |
| 107 | 0x6B | k | 小写字母k |
| 108 | 0x6C | l | 小写字母l |
| 109 | 0x6D | m | 小写字母m |
| 110 | 0x6E | n | 小写字母n |
| 111 | 0x6F | o | 小写字母o |
| 112 | 0x70 | p | 小写字母p |
| 113 | 0x71 | q | 小写字母q |
| 114 | 0x72 | r | 小写字母r |
| 115 | 0x73 | s | 小写字母s |
| 116 | 0x74 | t | 小写字母t |
| 117 | 0x75 | u | 小写字母u |
| 118 | 0x76 | v | 小写字母v |
| 119 | 0x77 | w | 小写字母w |
| 120 | 0x78 | x | 小写字母x |
| 121 | 0x79 | y | 小写字母y |
| 122 | 0x7A | z | 小写字母z |
| 123 | 0x7B | { | 左花括号,代码块定界符 |
| 124 | 0x7C | | | 竖线,Shell管道符、逻辑或 |
| 125 | 0x7D | } | 右花括号 |
| 126 | 0x7E | ~ | 波浪号,按位取反运算符、用户主目录缩写 |
2.5 值得背下来的三个区段
完整表格参考价值大,但工作中真正需要即时反应的,其实就是三个区段加两个特殊值。
- 数字:48到57,十六进制0x30到0x39。取数字字符的值用
c - '0',本质上就是减去48。 - 大写字母:65到90,十六进制0x41到0x5A。
- 小写字母:97到122,十六进制0x61到0x7A。
- 空格:32,十六进制0x20。
- 回车/换行:13/10,十六进制0x0D/0x0A。
大写A是65,小写a是97,两者相差32。这句话我建议你刻在脑子里。因为ASCII这套编号在设计时非常讲究,大小写字母之间正好错开32,也就是二进制第5位翻转一下就能完成大小写转换。后头写代码时,ch |= 0x20转小写,ch &= ~0x20转大写,都是这么来的。
3. 控制字符没你想的那么简单
3.1 从电报与打孔纸带走来
很多人看到0到31这些控制字符,第一反应是“这都什么鬼”。实际上这些字符的来历和电报、打孔纸带、早期终端高度相关。比如BEL原来就是物理电铃的触发信号,终端收到它就会“叮”一声;DEL更直接,纸带上打满孔代表删除,后来计算机里就用来表示Delete。
搞清楚这一点,你就不会觉得SOH、STX、ETX是莫名其妙的存在。当时的通信是面向字节流的,一帧报文发出去,总得告诉对方“从哪开始、到哪结束”。SOH标识报头,STX标识正式数据开始,ETX标识数据结束,这就是最原始的帧协议设计。现在很多工业总线和自定义串口协议,表面上没用这些字符,但分帧、定界的思路一脉相承。
3.2 通信协议里的帧控制与流控
在我调试串口设备的经验里,控制字符里出现频率最高的几个,一个是0x0D/0x0A这对“回车换行”,另一个是0x1B转义符。很多触摸屏、扫码枪、打印机的串口指令都以CR或LF结尾,解析时先按行切割,再去掉行尾的\r和\n,剩下的才是有效数据。
DC1和DC3也就是Ctrl+Q和Ctrl+S,在老式的串口软流控里承担暂停/恢复发送的作用。如果在调试老设备时发现数据传着传着突然卡住,八成是远端发来一个0x13(XOFF),要你停下来等它处理完,然后收到0x11(XON)再继续。现在串口参数里默认关闭软流控,但遇到老协议还是要留个心眼。
3.3 换行字符的恩怨
CR、LF这对组合,堪称跨平台开发的头号刺客。
Unix/Linux习惯只用LF(0x0A)表示换行,Windows用CR LF(0x0D 0x0A)两个字符表示一行结束,老Mac甚至只用CR。你从Windows拷贝脚本到Linux,经常会看到结尾一堆^M,那就是回车符没被识别为换行,直接显示成字符了。处理方式也简单,sed -i 's/\r$//'或者dos2unix一把梭。反过来从Linux往Windows传文件,有些编辑器会显示成一行西瓜(所有换行消失),本质也是换行符不匹配。
这一类问题排查起来不算难,但特别费时间。我的习惯是一拿到陌生环境的日志文件,先看文件里0x0D出现的频率,如果一行结尾挨着0x0D 0x0A,立刻就要意识到换行风格和当前系统不一致。
3.4 转义符与终端操作
ESC(0x1B)在终端控制里简直就是“魔法开关”。ANSI转义序列就是ESC加方括号再加参数,例如ESC[2J清屏、ESC[31m设置前景色为红色。很多嵌入式设备的LCD菜单、联网模块的AT指令集,也会用ESC做多级菜单的层级跳转标志。
写串口屏驱动的时候,我踩过这么个坑:厂商协议把ESC定义成“进入命令模式”,但图画文本里头换行也是ESC加别的字符组成的序列。结果有一次数据显示里包含0x1B,屏幕就直接切到命令模式,显示全乱。后来实现在解析层做了严格状态机,只有特定前置条件下才把ESC解释成控制字符,数据区里的ESC一律按普通字节处理。这个思路也推荐给你,处理任何“元字符”时都不要无脑转义。
3.5 不可见控制符到底是什么意思
所谓“不可见控制符”,就是这33个字符在普通终端上不显示图形,只影响设备行为。它们不是没有意义,而是意义不在“长什么样”,而在“做什么事”。比如你在Windows记事本里看到一行结束,背后是两个字节0x0D 0x0A;按下退格键,终端收到的是0x08;按下Delete键,终端收到的是0x7F。这些行为肉眼不可见,但文件格式、协议语义全压在上面。
新人学到这里最绕的一个弯,是也把0x08和0x7F搞混。按Backspace和按Delete发送的ASCII码不一样,用串口工具调试终端时特别注意,很多设备对这两个码的响应策略截然不同。
4. 从ASCII走向扩展编码
4.1 128-255是怎么来的
ASCII原本只是7位编码,但计算机存储单元是8位,一个字节有256种组合,只用0到127未免浪费。于是各家厂商开始在128到255这段填充各自喜欢的字符,这就导致了“扩展ASCII”乱象。
最有名的是IBM PC时代的Code Page 437,里面塞进了制表符、希腊字母、甚至扑克牌花色。国内很多人记忆里的“ASCII里152是人民币符号”,其实是GB2312或者ANSI时代本地化字符集产生的错觉,并非标准ASCII内容。
如果从编程角度理解,就是同一个字节0x80,在不同的代码页下可能显示成完全不同的字符。这也是“外一字节就乱码”的根源之一:两个端点的代码页不一致。
4.2 ISO-8859-1与Latin-1的欧洲视角
ISO-8859-1又叫Latin-1,算是最常见的单字节扩展方案,覆盖了西欧主要语言。它保留0x00-0x7F与ASCII完全一致,又用0xA0-0xFF塞进了很多带重音的字母、货币符号、分数符号。
我对这个字符集印象深,是因为早年写网页的人都知道,在HTTP响应头里如果没指定charset,很多浏览器默认按ISO-8859-1解析。没有统一UTF-8的岁月里,西班牙语的ñ、法语的重音é、德国的ß、欧元的€,全都依赖这张扩展表才能正常显示。后来大家统一转向Unicode,才把这种混乱终结掉。
4.3 为什么ASCII至今没被淘汰
很多人会问:现在都用UTF-8了,还背ASCII表干什么?核心原因是ASCII的0x00到0x7F被Unicode原封不动继承下来,Unicode码点U+0000到U+007F和ASCII是一一对应的。UTF-8编码里,这个区段的字符也直接用单字节表示,和当年ASCII字节一模一样。
也就是说,你写的任何一段UTF-8编码的英文文本,本质上就是一段ASCII流。协议头、文件头、控制指令这些“结构性文字”为了保证通用性,几乎都会刻意选择ASCII字符。比如HTTP报文头、JSON键名、PNG文件头、ELF魔数,全都在ASCII区段内。所以解析任何文件格式,第一步仍然要对照ASCII码。
5. 实战:程序里怎么快速查表、怎么换算
5.1 命令行三件套
调试时不想开在线表格,最顺手的是这几个命令。
先看某个字符对应的ASCII码:
# 注意是单引号,shell会把'A改成字符而不是字符串 printf '%d\n' "'A" # 输出 65把一个字节按十六进制看:
echo -n "A" | od -An -tx1 # 输出 41如果看文件里有哪些不可见字符,cat -A非常直观:
echo -e "hello\r\nworld" | cat -A # 输出 # hello^M$ # world$其中^M就是回车符的显示形式。xxd也值得装,它能输出带偏移量和ASCII对照区的完整十六进制视图,我做协议分析时基本天天用。
5.2 用Python一键生成贯穿表
如果你像我一样经常要打印一份ASCII表贴显示器边上,用Python生成最省事:
for i in range(128): if i >= 32 and i <= 126: ch = chr(i) else: ch = ' ' print(f"{i:3d} 0x{i:02X} {ch}")这段代码会把0到127全部列出来,控制字符区字符位留空,可打印字符区直接显示字符。如果想要更完整的双向查询,可以写成字典:
char_to_code = {chr(i): i for i in range(128)} code_to_char = {i: chr(i) for i in range(128)} print(char_to_code['A']) # 65 print(ord('A')) # 65 print(chr(65)) # APython自带的ord和chr已经是天然的ASCII查询函数,不需要任何第三方库。
5.3 手工换算的速算技巧
现场没有工具时,把十六进制转十进制可以用一个口诀:高位乘16加低位。0x41就是4×16+1=65,0x61就是6×16+1=97。因为ASCII里字母区段很规整,你只需要记住A是0x41,a是0x61,然后往后一个个数就行。
数字字符更简单,0是0x30,9就是0x39。做过C语言字符串转数字的朋友应该都写过c - '0',本质上就是把ASCII值映射回数值本身。
我还常用一个技巧:大小写互转用异或32。大写A是65(0b1000001),小写a是97(0b1100001)。中间就差一位,也就是0x20这一位。ch ^= 0x20就能完成互换,在写简洁的协议处理代码时非常漂亮。
6. 常见问题与排查速查
6.1 问题速查表
| 症状 | 常见原因 | 处理思路 |
|---|---|---|
| 终端显示字符前出现^M | 文件或数据流用CRLF换行,当前系统只认LF | 去掉0x0D,或统一换行风格 |
| 串口发0x41,设备当成数字65处理而非字符A | 协议字段是二进制数值,不是文本 | 明确字段类型与编码规则 |
| 大小写转换后结果不对 | 用了加减32但方向搞反,或误对数字做转换 | 判断字符范围,再用异或0x20 |
| 把‘5’和5混淆,数值计算得到错误结果 | 未减‘0’就参与运算 | c - '0' |
| 解析文本时字符串提前终止 | 数据结构里出现0x00,被当成字符串结束符 | 使用带长度的缓冲区解析,别依赖strlen |
| 网页表单或日志出现大量菱形问号 | 扩展编码不一致,比如UTF-8被按Latin-1解码 | 统一用UTF-8,确认charset |
| Backspace按下去终端没反应或删错位置 | 收到0x08和0x7F的处理策略不同 | 查终端配置和驱动对两个码的响应 |
6.2 逐个排查与避坑
第一个常见坑是“用十六进制却写成了十进制”。协议文档写0x41,有人看表时盯着十进制65,然后写常量时直接写成十进制的41,数据彻底报废。我的习惯是:凡是和字节流打交道,常量一律写成0x开头,彻底消灭进制错觉。
第二个坑是“把数据当文本,把文本当数据”。串口收上来0x30,可能既是字符‘0’,也是数值48。是文本协议还是二进制协议,取决于双方约定。在协议结构体里,一个字段到底是uint8还是char,完全决定了后面的解析路径。不要因为正好打印出可打印字符,就默认它是字符串。
第三个坑是隐藏的半角全角问题。比如同一个数字字符,全角‘5’在Unicode里根本不是ASCII的0x35,而是U+FF15。你在代码里判断c >= '0' && c <= '9'只能过滤半角ASCII数字,遇到全角就会放进来。做输入校验时,先确认输入字符编码范围。
我调试时还习惯性地在打印日志里保留十六进制视图。不要只打%s看可读文本,因为不可见字符一旦混进去,肉眼完全看不出来。最稳妥的日志格式是“十六进制+ASCII对照”,这样既能看到真实字节,又能快速识别可读内容。
7. 最后说两句
做底层和通信越久,我越觉得ASCII表不是背完就完事的东西,它更像一把钥匙,打开的是“字符如何编码、字节如何解释”这扇门。我自己电脑桌面背景就有一张ASCII码对应表,平时调试时瞟一眼就够,比翻网页省几秒钟,但积累下来省下的时间真不少。另外一个小建议:把常用字符的十六进制写法练成肌肉记忆,例如空格0x20、LF是0x0A、CR是0x0D、ESC是0x1B,这几个字符在我处理过的协议里出场率最高。遇到复杂的协议和数据格式,能随口报出这些码值,很多问题在刚露苗头时就能直接掐掉,不至于在日志海洋里翻半天才回过神来。