计算机本质的理解
数字编码 🔗︎
1. 计算机的本质 🔗︎
计算机本质是 0/1 的世界,如果要用 0/1 表达这个世界,结论是:需要通过 编码。
2. 数字的表达方式 🔗︎
为什么会有补码? 🔗︎
目的: 补码是为了方便计算机进行运算,尤其是加法。
举例:
计算5 + (-3)
通过补码(定义:正数不变,负数取反并且+1)计算:- 步骤 1:
0101
(5) - 步骤 2:
0011
(3) -> 取反1100
-> 加一1101
- 步骤 3:
0101 + 1101 = 100010
截断后为0010
(结果为 2)
运算的结果符合人类的预期结果,而原码(首位为符号位)和反码(除了符号位,所有位取反)计算出来的结果都不满足人类预期。
- 步骤 1:
为什么表示范围是 [-128, 127]
? 🔗︎
- 解释:
以 4 位二进制补码为例:范围是0000(-0) 0000(0) 1111(-1) 0001(1) 1110 (-2) 0010(2) ... 1011(-5) 0101(5) 1010(-6) 0110(6) 1001(-7) 0111(7)
[-7, 7]
,但1000
没有被表示,刚好可以用来表示-8
(-8
的补码表示:1000
(原) -> 取反0111
-> 加一1000
)。
因此,最终范围是[-8, 7]
。同理,[-128, 127]
也解释得通。
为什么小数会有精度问题? 🔗︎
- 原因: 在一些语言中,
0.1 + 0.2
永远不会等于0.3
。因为二进制的小数只能用固定的数字组合,例如0.2
无法用二进制精确表示。
文本编码 🔗︎
1. 编码的概念 🔗︎
- 定义: 数字编码后,文本也需要通过编码来表示。
- 实现方式: 编码主要通过 码表 实现,即字符和数字的对应关系。
- 码表(字符集): 字符和数字的对应关系(接口)。
- 字符编码: 具体实现。
- 码点: 表中的每个数字(可能由一个或多个码元组成)。
2. 发展轨迹 🔗︎
ASCII: 英文和特殊字符,使用一个字节中的 7 位。
EASCII 字符集: ISO 8859-1。
GB2312: 早期汉字编码标准。
GBK: GBK 编码(汉字),扩展了 GB2312。
GB18030: 更全面的汉字编码标准。
Unicode 字符集:
- 一般 Unicode 编码指 UTF-16 编码,变长编码、2 字节码元、有字节序问题。
- UTF-8: 变长编码,单字节码元,无字节序问题。
发展过程: 初始化 -> 本地化(GB 表示国标) -> 国际化 -> 效率化。
多媒体编码 🔗︎
1. 音频 🔗︎
- 原理: 一定时间内采集有限的样本表示。
- 采样率: 与模拟信号频率有关,例如每秒 40000 个样本。
- 编码方式: AAC。
2. 图像 🔗︎
- 光栅图:
- 解析度: 单位面积像素数。
- 色彩深度: 像素位数量。
- 矢量图: 通过数学公式计算像素分布。
3. 视频 🔗︎
- 帧: 由图片构成,多帧构成视频流。
- 编码方式: MPEG-1, MPEG-2, MPEG-4, H.264, H.265。
运算的本质 🔗︎
1. 概念 🔗︎
有了编码后,计算机如何实现计算?为什么内存条、芯片会很贵?
逻辑运算 🔗︎
- 基本操作: 与、或、非、异或。
- 实现方式: 通过逻辑门电路实现。输入是开关,输出一般是 LED 灯(有颜色的灯)。芯片一般是实现逻辑运算,例如 7486(异或门)芯片,会暴露一些引脚出来,比如一个接电源,一个接地,其他接输入,表示输出等。
2. 半加器 🔗︎
- 定义: 不考虑进位的加法器。
- 公式:
Cout = X * Y
S = X ⊕ Y
3. 全加器 🔗︎
- 定义: 考虑进位的加法器。
- 公式:
S = X ⊕ Y ⊕ Cin Cout = (X * Y) + ((X ⊕ Y) * Cin)
4. 两位加法机 & 多位加法机 🔗︎
- 两位加法机: 两个全加器串联。
- 多位加法机: 多个全加器串联。
5. 记忆加法机 🔗︎
- 功能: 保存上次计算的结果。
6. 选择加法机 🔗︎
- 功能: 多弄几个记忆加法器,避免输错时重新来过。
7. 自动加法机 🔗︎
- 功能: 按一下开关,自动输入并把所有数字加起来。
8. 自由加法机 🔗︎
- 功能: 可控制只计算部分的数字。
9. 对比现代计算机 🔗︎
目前的计算机能进行 10 亿次的加法运算。计算机包含以下部分:
- 存储器: 存储箱,用于存储输入的数字、要执行的代码、计算的结果。
- 运算器: 累加器,计算两个数字相加。
- 控制器: 控制板,切换开关。
- 输入设备: 输入面板,输入开关。
- 输出设备: 输出灯泡,灯泡显示输出数据。
以上包含了:输入、输出、内存、寻址、译码、指令、算术逻辑单元等概念。
虚拟电路模拟 🔗︎
- 工具: 虚拟电路模拟器
解释器和编译器的区别 🔗︎
1. 程序是如何运行的? 🔗︎
代码在执行前都需要通过语法解析器,将输入的代码字符串转换为 AST(抽象语法树)。
解释器执行流程 🔗︎
- Lexer(词法分析器): 读取代码,将代码转换为 token 序列。
- Parser(语法分析器): 把读到的 Token 序列转换为 AST(大部分情况下 Lexer 是 Parser 的一部分)。
- Lowering/Desugar: 化简 AST 或者将语法糖的 AST 节点转换为标准等价 AST 节点。
- Interpreter: 递归执行 AST。
解释器直接解释执行 AST,并返回最终结果:
function interpret(ast) {
switch (ast.type) {
case "number":
return ast.value;
case "negative":
return -interpret(ast.value);
case "op":
switch (ast.value) {
case "+":
return interpret(ast.v1) + interpret(ast.v2);
}
}
}
编译器执行流程 🔗︎
编译器将 AST 翻译成目标语言,例如汇编:
// 假定为目标机器为栈式虚拟机,然后通过 exec 函数执行
function compile(ast) {
switch (ast.type) {
case "number":
return "ds.push(" + ast.value + ")\n";
case "negative":
return "ds.push(-ds.pop())\n";
case "op":
return (
compile(ast.v1) +
compile(ast.v2) +
"ds.push(ds.pop() " +
ast.value +
" ds.pop())\n"
);
}
}
代码的本质 🔗︎
通过代码控制机器,代码是人阅读的指令。主要有两大要素:数据 和 操作(判断、选择、循环、分支等)。
内存模型 🔗︎
1. 计算机是如何存储的? 🔗︎
运算的本质 中的锁存器(64 位构成的庞大数组),本质就是内存。
2. 执行过程 🔗︎
- 顺序执行: 代码编译成一条条指令加载到内存(操作系统分配的执行区域),计数器逐步执行内存中的指令,返回结果。
- 分支选择: 跳到对应地址执行指令。
- 嵌套执行: 函数执行会在内存开辟一组连续的内存空间。函数代码执行前根据参数数量、参数大小,计算分配栈空间,栈底为内存高地址方向。
3. 活动记录 🔗︎
执行过程的活动记录由标记顶部位置的 帧指针 和标记底部位置的 栈指针 定义。当执行完毕,帧指针指向下一条指令地址。