从零开始给女朋友讲计算机 1 - 从比特、字节、补码到 ASCII、GB2312、Unicode
起因
在代码 review 的过程中,总是发现有人在数据类型转换(reinterpret_cast)、大小端等问题(什么情况下需要考虑大小端,什么情况下不需要考虑)上犯错误,究其原因是没有彻彻底底地搞懂数据的二进制表示。我想写篇文章,用通俗易懂的语言把这件事情说明白,通俗易懂到我的女朋友也能看懂。于是我就尝试着先做些铺垫,给她讲了些基础。发现效果出奇的好,于是赶紧把这一过程记录如下。
0 和 1 的世界
计算机的世界只有 0 和 1,所有的数据都由 0 和 1 的组合:数字、字母、汉字、图片、音乐、电影、游戏、网页等都可以由很多的 0 和 1 组成。
计算机如何知道一长串的 0 和 1 是什么含义呢?
比如 0100 0001
可能表示数字 65,可能表示大写字母A
,可能和更多的 0 和 1 共同组成一个汉字,也可能表示图片上某个点的颜色,其意义完全取决于人们约定的规则。
比特和字节
正着说:每一个 0 和 1 叫做一个比特(bit),8 个比特组成一个字节(Byte)。字节是计算机的基本单位,通常计算机一次最少处理一个字节。
例如:人们常说的一个 Word 文档 100 KB,一张图片 2 MB,一首歌 10 MB,一部电影 4 GB,内存 8 GB,硬盘 512 GB 等等。这里的大“B”就是 Bytes,字节。
比特(bit)最常见于宽带的宣传:例如 500M 宽带的完整单位是 500 Mbps(注意这里是小“b”,不是大“B”)。bps 即 bits per second,500Mbps 指的是每秒最大传输 500 兆比特(bit)。所以 500M 的宽带最快下载速度不是 500 MB/s,而是 500/8 = 62.5 MB/s。
反着再说一次:一个字节(byte)有 8 个比特(bit);每个比特只能是 0 或 1,8 个比特一共有 2^8 = 256 种组合,可以代表 256 种含义(具体含义完全取决于人们约定的规则)。
如何用 0 和 1 表示数字?
假设现在我们想用一个字节表示数字,于是我们可以约定,8 个 bit 低位到高位每个 bit 分别具有不同的权重,分别代表 1,2,4,8,16,32,64,128。于是通过一个字节 8 个 bit 的各种组合,就能表示出 0 到 255 之间所有的数字了。
高位 -> 低位 | bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
---|---|---|---|---|---|---|---|---|
权重 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
举例:0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
举例:1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
举例:35 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 |
举例:65 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
举例:128 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
举例:255 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
对于这种不考虑负数的情况,我们称之为无符号数。 |
那如何表示一个负数(有符号数)?
有很多种方法,只要约定好一个规则即可。比如我们可以约定,最高位 bit7 代表符号位,0 代表正数,1 代表负数。于是一个字节,8 个 bit 可以表示 -127 到 127 的数字。注意其中 0 有两种表示,+0 和 -0。
高位 -> 低位 | bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
---|---|---|---|---|---|---|---|---|
权重 | +/- | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
举例:+0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
举例:-0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
举例:35 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 |
举例:-65 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
举例:127 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
举例:-127 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
现实计算机世界的负数几乎都是补码表示。和无符号数的规则相比,差别仅在最高位的权重为负。于是一个字节,8 个 bit 可以表示 -128 到 127 的数字。其中 0 只有一种表示。
高位 -> 低位 | bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
---|---|---|---|---|---|---|---|---|
权重 | -128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
举例:0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
举例:35 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 |
举例:65 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
举例:-128 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
举例:127 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
举例:-127 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
举例:-1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
先停一下
看到这里,如果问你,1000 0000
代表一个什么数字,你要怎么回答?千万别急着回答,回答之前应该先问清楚,要按照什么规则去解析。比如这串 0/1 表示的是一个无符号数还是一个补码表示的有符号数。
如何表示更大的数?
用多个字节表示。一个字节不够就两个,两个不够就四个、八个。用 2 个字节就能够表示 0 到 65535 之间的无符号数,用 4 个字节就能表示 0 到 4294967295 的无符号数!
|高位 -> 低位| bit 15|bit 14 |bit 13 |bit 12 |bit 11 |bit 10 |bit 9| bit 8 | bit 7|bit 6|bit 5|bit 4|bit 3|bit 2|bit 1| bit 0|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|权重|32768|16384|8192|4096|2048|1024|512|256|128|64|32|16|8|4|2|1|
|举例:0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|
|举例:65|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|1|
|举例:255|0|0|0|0|0|0|0|0|1|1|1|1|1|1|1|1|
|举例:10000|0|0|1|0|0|1|1|1|0|0|0|1|0|0|0|0|
|举例:40256|1|0|0|1|1|1|0|1|0|1|0|0|0|0|0|0|
|举例:60666|1|1|1|0|1|1|0|0|1|1|1|1|1|0|1|0|
有符号数(补码)也是类似的,只不过最高位的权重为负。用 2 个字节就能够表示 -32768 到 32767 之间的有符号数,用 4 个字节就能表示 -2147483648 到 2147483647 的有符号数!
直接使用上面的表格(二进制表示的 bit 15 到 bit 0 和上面一模一样),但是现在按照补码的规则进行解析(即最高位权重为负),于是得到的结果就不一样了。
|高位 -> 低位| bit 15|bit 14 |bit 13 |bit 12 |bit 11 |bit 10 |bit 9| bit 8 | bit 7|bit 6|bit 5|bit 4|bit 3|bit 2|bit 1| bit 0|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|权重|-32768|16384|8192|4096|2048|1024|512|256|128|64|32|16|8|4|2|1|
|举例:0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|
|举例:65|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|1|
|举例:255|0|0|0|0|0|0|0|0|1|1|1|1|1|1|1|1|
|举例:-25280|1|0|0|1|1|1|0|1|0|1|0|0|0|0|0|0|
|举例:-4870|1|1|1|0|1|1|0|0|1|1|1|1|1|0|1|0|
十六进制:二进制的简化表示法
二进制要用 8 个 0/1 表示一个 byte,太不方便,为简化表示,十六进制用分别用一个 0-F 表示一个字节的前 4 位和后 4 位。一般还会加上前缀0x
,以提醒读者后面是 16 进制表示法。
如何表示带小数点的浮点数?
还是一样,只要约定好一个规则就行。计算机界流行的浮点数规则是 IEEE 定义单精度浮点(4 字节表示)和双精度浮点(8 字节表示)。浮点数的规则要稍微复杂一些,但也没什么特别难理解的。只是本文针对计算机初学者,不会涉及所有细节,后面可能会单独写一篇文章介绍浮点数的表示。
如何表示字符?
假设现在我们要用一个字节表示一个字符,我们可以约定,0000 0001 代表 a,0000 0002 代表 b,以此类推。从 0000 0000 到 1111 1111 的 256 种组合中表示 a-z、A-Z,加上各种标点符号也是绰绰有余。现实计算机世界几乎都心照不宣地采用 ASCII 规则来表示常见的英文字符、标点以及一些不显示的控制字符等。ASCII 只用了 7 个 bit,其中字符 a-z、A-Z、0-9 的表示在数值上都是连续的。选取部分例子如下:
进制 | 十进制 | 十六进制 | 字符/缩写 | 解释 |
---|---|---|---|---|
00000000 | 0 | 0x00 | NUL (NULL) | 空字符 |
00001010 | 10 | 0x0A | LF/NL(Line Feed/New Line) | 换行键 |
00001101 | 13 | 0x0D | CR (Carriage Return) | 回车键 |
00100000 | 32 | 0x20 | (Space) | 空格 |
00100001 | 33 | 0x21 | ! | |
00101100 | 44 | 0x2C | , | |
00101110 | 46 | 0x2E | . | |
00110000 | 48 | 0x30 | 0 | |
00110001 | 49 | 0x31 | 1 | |
00110010 | 50 | 0x32 | 2 | |
01000000 | 64 | 0x40 | @ | |
01000001 | 65 | 0x41 | A | |
01000010 | 66 | 0x42 | B | |
01000011 | 67 | 0x43 | C | |
01011000 | 88 | 0x58 | X | |
01011001 | 89 | 0x59 | Y | |
01011010 | 90 | 0x5A | Z | |
01100001 | 97 | 0x61 | a | |
01100010 | 98 | 0x62 | b | |
01100011 | 99 | 0x63 | c | |
01111000 | 120 | 0x78 | x | |
01111001 | 121 | 0x79 | y | |
01111010 | 122 | 0x7A | z | |
01111111 | 127 | 0x7F | DEL (Delete) | 删除 |
如何表示汉字?
一个字节一共就 256 种排列组合,就算每个组合代表一个汉字,也只能表示 256 个汉字,这显然是不够的。要想表示一个汉字,至少需要 2 个字节。这样就有 2^16 = 65536 种排列组合,可以表示 65536 个汉字了,应对常见的汉字已经不成问题。GB2312 编码就是用两个字节给汉字编码的。比如 0xB0A1 代表汉字“啊”,0xD7D3 代表汉字“子”。完整编码规则这里不详细展开。
如何表示韩文、日文、阿拉伯文等所有字符?
每个国家、地区都有自己的编码方式。比如同样的一串数字 1011 0000 1010 0001
即 0xB0A1
在 GB2312 编码下代表汉字“啊”,而在某种日文编码规则中则可能代表一个日文字符。如果一个日本程序员开发了一个软件,在日文编码的机器上可以正常显示日文,但是如果拿到中文编码的机器上就会显示乱码。为解决这一问题,推出了 Unicode 编码,为全世界的每一个字符都分配了一个独一无二的编码,甚至每个 emoji 表情都有自己的编码!现在只要所有的软件开发人员都统一采用 Unicode 编码方式,就再也不会出现乱码了。Unicode 采用 4 字节编码,可以表示 2^32 = 4294967295 个字符,足够容纳目前世界上所有已知的字符了。但是如果考虑到将这些字符信息保存下来,比如说保存到硬盘上,新的问题就来了:比如对于一个英文的纯文本文件,如何之前按 ASCII 编码,每个字符只要 1 个字节就够了,但是现在 Unicode 用 4 个字节编码,如果每个英文字符都用 4 个字节来存储(UTF-32),那么文件的大小将变成原来的 4 倍!为了解决这一问题,人们发明了 UTF-8。注意区分 UTF-8 和 Unicode:Unicode 是给每一个字符分配一个编号,从 0 到 4294967295;UTF-8 并不是给字符重新分配编号,每个字符的编号还是 Unicode 中定义、分配的编号,UTF-8 只是想方设法让 Unicode 在保存、传输的过程中减少所需的字节数的一种小技巧/规则,具体细节暂时不在这里展开,后期可能会专门写一篇文章介绍 UTF-8 是如何减少 Unicode 编码文件的体积。
总结
计算机的世界由 0/1 组成,数字、字母、图片等等所有信息都由一串串的 0/1 表示。8 个比特组成一个字节,字节是计算机的基本单位。一个字节可以表示 2^8 = 256 种含义,如何解析完全取决于人们约定的规则,要想知道字节的含义,必须要知道解析的规则(数据类型)。如果一个字节不足以表示所有的范围、可能性,就用多个字节表示。
本文作者:Zijian/TENG(微信公众号:好记性如烂笔头),转载请注明原文链接:https://www.cnblogs.com/tengzijian/p/14965165.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具