c
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 438 KiB |
Before Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 398 KiB |
Before Width: | Height: | Size: 534 KiB |
Before Width: | Height: | Size: 443 KiB |
Before Width: | Height: | Size: 156 KiB |
@ -929,534 +929,3 @@ const int SIZE = 10; // 可以局部或全局定义,不会引发冲突
|
||||
| 使用场景 | 宏、条件编译 | 类型安全的常量 |
|
||||
|
||||
|
||||
|
||||
# 第三章:进制
|
||||
|
||||
## 3.1 概述
|
||||
|
||||
* 计算机的底层只有`二进制`,即计算机中`运算`和`存储`的`所有数据`都需要转换为`二进制`,包括:数字、字符、图片、视频等。
|
||||
|
||||
![](./assets/17.jpg)
|
||||
|
||||
* 之前,我们也提到现代的计算机(量子计算机除外)几乎都遵循`冯·诺依曼`体系结构,其理论要点如下:
|
||||
* ① **存储程序**:`程序指令`和`数据`都存储在计算机的内存中,这使得程序可以在运行时修改。
|
||||
* ② **二进制逻辑**:所有数据和指令都以`二进制`形式表示。
|
||||
* ③ **顺序执行**:指令按照它们在内存中的顺序执行,但可以有条件地改变执行顺序。
|
||||
* ④ **五大部件**:计算机由`运算器`、`控制器`、`存储器`、`输入设备`和`输出设备`组成。
|
||||
* ⑤ **指令结构**:指令由操作码和地址码组成,操作码指示要执行的操作,地址码指示操作数的位置。
|
||||
* ⑥ **中心化控制**:计算机的控制单元(CPU)负责解释和执行指令,控制数据流。
|
||||
* 所以,再次论证了为什么计算机只能识别二进制。
|
||||
|
||||
## 3.2 进制
|
||||
|
||||
### 3.2.1 常见的进制
|
||||
|
||||
* 在生活中,我们最为常用的进制就是`十进制`,其规则是`满 10 进 1` ,即:
|
||||
|
||||
![](./assets/18.jpeg)
|
||||
|
||||
* 在计算机中,常见的进制有`二进制`、`八进制`和`十六进制`,即:
|
||||
* 二进制:只能 0 和 1 ,满 2 进 1 。
|
||||
* 八进制:0 ~ 7 ,满 8 进 1 。
|
||||
* 十六进制:0 ~ 9 以及 A ~ F ,满 16 进 1 。
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 在十六进制中,除了 `0` 到 `9` 这十个数字之外,还引入了字母,以便表示超过 `9` 的值。其中,字母 `A` 对应十进制的 `10` ,字母 `B` 对应十进制的 `11` ,字母 `C` 对应十进制的 `12`,字母 `D` 对应十进制的 `13`,字母 `E` 对应十进制的 `14`,字母 `F` 对应十进制的 `15`。
|
||||
|
||||
* 进制的换算举例,如下所示:
|
||||
|
||||
| 十进制 | 二进制 | 八进制 | 十六进制 |
|
||||
| ------ | ------ | ------ | -------- |
|
||||
| 0 | 0 | 0 | 0 |
|
||||
| 1 | 1 | 1 | 1 |
|
||||
| 2 | 10 | 2 | 2 |
|
||||
| 3 | 11 | 3 | 3 |
|
||||
| 4 | 100 | 4 | 4 |
|
||||
| 5 | 101 | 5 | 5 |
|
||||
| 6 | 110 | 6 | 6 |
|
||||
| 7 | 111 | 7 | 7 |
|
||||
| 8 | 1000 | 10 | 8 |
|
||||
| 9 | 1001 | 11 | 9 |
|
||||
| 10 | 1010 | 12 | a 或 A |
|
||||
| 11 | 1011 | 13 | b 或 B |
|
||||
| 12 | 1100 | 14 | c 或 C |
|
||||
| 13 | 1101 | 15 | d 或 D |
|
||||
| 14 | 1110 | 16 | e 或 E |
|
||||
| 15 | 1111 | 17 | f 或 F |
|
||||
| 16 | 10000 | 20 | 10 |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
* 二进制和十六进制的关系:十六进制是以 16 为基数的进制系统,16 在二进制中表示为 ( 2^4 ),即:一个十六进制可以表示 4 位二进制。
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 十六进制的范围是:0 ~ F (0 ~ 15)对应的二进制数的范围是:0000 ~ 1111 (0 ~ 15)。
|
||||
|
||||
* 每个十六进制数都可以映射到一个唯一的 4 位二进制数,即:
|
||||
|
||||
| 十六进制 | 二进制 |
|
||||
| -------- | ------ |
|
||||
| 0 | 0000 |
|
||||
| 1 | 0001 |
|
||||
| 2 | 0010 |
|
||||
| 3 | 0011 |
|
||||
| 4 | 0100 |
|
||||
| 5 | 0101 |
|
||||
| 6 | 0110 |
|
||||
| 7 | 0111 |
|
||||
| 8 | 1000 |
|
||||
| 9 | 1001 |
|
||||
| A | 1010 |
|
||||
| B | 1011 |
|
||||
| C | 1100 |
|
||||
| D | 1101 |
|
||||
| E | 1110 |
|
||||
| F | 1111 |
|
||||
|
||||
>[!NOTE]
|
||||
>
|
||||
>由此可见,每个十六进制数字确实由 4 位二进制数表示。
|
||||
|
||||
* 二进制和八进制的关系:八进制是以 8 为基数的进制系统,8 在二进制中表示为 ( 2^3 );即:一个八进制位可以表示 3 个二进制位。
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 八进制的范围是:0 ~ 7 对应的二进制数的范围是:000 ~ 111。
|
||||
|
||||
* 每个八进制数位都可以映射到一个唯一的 3 位二进制数,即:
|
||||
|
||||
| 八进制 | 二进制 |
|
||||
| ------ | ------ |
|
||||
| 0 | 000 |
|
||||
| 1 | 001 |
|
||||
| 2 | 010 |
|
||||
| 3 | 011 |
|
||||
| 4 | 100 |
|
||||
| 5 | 101 |
|
||||
| 6 | 110 |
|
||||
| 7 | 111 |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 由此可见,每个八进制数字确实由 3 位二进制数表示。
|
||||
|
||||
### 3.2.2 C 语言中如何表示不同进制的整数?
|
||||
|
||||
* 规则如下:
|
||||
* 在 C 语言中,如果是`二进制`(字面常量),则需要在二进制整数前加上 `0b` 或 `0B` 。
|
||||
* 在 C 语言中,如果是`八进制`(字面常量),则需要在八进制整数前加上 `0` 。
|
||||
* 在 C 语言中,如果是`十进制`(字面常量),正常数字表示即可。
|
||||
* 在 C 语言中,如果是`十六进制`(字面常量),则需要在十六进制整数前加上 `0x`或`0X` 。
|
||||
|
||||
|
||||
|
||||
* 示例:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
|
||||
int num1 = 0b10100110; // 二进制
|
||||
int num2 = 0717563; // 八进制
|
||||
int num3 = 1000; // 十进制
|
||||
int num4 = 0xaf72; // 十六进制
|
||||
|
||||
printf("num1 = %d\n", num1); // num1 = 166
|
||||
printf("num2 = %d\n", num2); // num2 = 237427
|
||||
printf("num3 = %d\n", num3); // num3 = 1000
|
||||
printf("num4 = %d\n", num4); // num4 = 44914
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2.3 输出格式
|
||||
|
||||
* 在 C 语言中,可以使用不同的`格式占位符`来`输出`不同`进制`的整数,如下所示:
|
||||
* `%d`:十进制整数。
|
||||
* `%o` :八进制整数。
|
||||
* `%x`:十六进制整数。
|
||||
* `%#o` :显示前缀 `0` 的八进制整数。
|
||||
* `%#x` :显示前缀 `0x` 的十六进制整数。
|
||||
* `%#X` :显示前缀 `0X` 的十六进制整数。
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> C 语言中没有输出二进制数的格式占位符!!!
|
||||
|
||||
|
||||
|
||||
* 示例:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
|
||||
int num = 100;
|
||||
|
||||
printf("%d 的十进制整数: %d\n", num, num); // 100 的十进制整数: 100
|
||||
printf("%d 的八进制整数: %o\n", num, num); // 100 的八进制整数: 144
|
||||
printf("%d 的十六进制整数: %x\n", num, num); // 100 的十六进制整数: 64
|
||||
printf("%d 的八进制(前缀)整数: %#o\n", num, num); // 100 的八进制(前缀)整数: 0144
|
||||
printf("%d 的十六进制(前缀)整数: %#x\n", num, num); // 100 的十六进制(前缀)整数: 0x64
|
||||
printf("%d 的十六进制(前缀)整数: %#X\n", num, num); // 100 的十六进制(前缀)整数: 0X64
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 3.3 进制的运算规则
|
||||
|
||||
### 3.3.1 概述
|
||||
|
||||
* `十进制`的运算规则,如下所示:
|
||||
* 逢`十`进`一`(针对加法而言)。
|
||||
* 借`一`当`十`(针对减法而言)。
|
||||
* `二进制`的运算规则,如下所示:
|
||||
* 逢`二`进`一`(针对加法而言)。
|
||||
* 借`一`当`二`(针对减法而言)。
|
||||
* `八进制`的运算规则,如下所示:
|
||||
* 逢`八`进`一`(针对加法而言)。
|
||||
* 借`一`当`八`(针对减法而言)。
|
||||
* `十六进制`的运算规则,如下所示:
|
||||
* 逢`十六`进`一`(针对加法而言)。
|
||||
* 借`一`当`十六`(针对减法而言)。
|
||||
|
||||
### 3.3.2 二进制的运算
|
||||
|
||||
* 二进制的加法:`1 + 0 = 1` 、`1 + 1 = 10`、`11 + 10 = 101`、`111 + 111 = 1110`。
|
||||
|
||||
![](./assets/19.svg)
|
||||
|
||||
* 二进制的减法:`1 - 0 = 1` 、`10 - 1 = 1`、`101 - 11 = 10`、`1100 - 111 = 101` 。
|
||||
|
||||
![](./assets/20.svg)
|
||||
|
||||
### 3.3.3 八进制的运算
|
||||
|
||||
* 八进制的加法:`3 + 4 = 7` 、`5 + 6 = 13`、`75 + 42 = 137`、`2427 + 567 = 3216`。
|
||||
|
||||
![](./assets/21.svg)
|
||||
|
||||
* 八进制的减法:`6 - 4 = 2` 、`52 - 27 = 33`、`307 - 141 = 146`、`7430 - 1451 = 5757` 。
|
||||
|
||||
![](./assets/22.svg)
|
||||
|
||||
### 3.3.4 十六进制的运算
|
||||
|
||||
* 十六进制的加法:`6 + 7 = D` 、`18 + BA = D2`、`595 + 792 = D27`、`2F87 + F8A = 3F11`。
|
||||
|
||||
![](./assets/23.svg)
|
||||
|
||||
* 十六进制的减法:`D - 3 = A` 、`52 - 2F = 23`、`E07 - 141 = CC6`、`7CA0 - 1CB1 = 5FEF` 。
|
||||
|
||||
![](./assets/24.svg)
|
||||
|
||||
## 3.4 进制的转换
|
||||
|
||||
### 3.4.1 概述
|
||||
|
||||
* 不同进制的转换,如下所示:
|
||||
|
||||
![](./assets/25.png)
|
||||
|
||||
* 在计算机中,数据是从右往左的方式排列的;其中,最右边的是低位,最左边的是高位,即:
|
||||
|
||||
![](./assets/26.svg)
|
||||
|
||||
### 3.4.2 二进制和十进制的转换
|
||||
|
||||
#### 3.4.2.1 二进制转换为十进制
|
||||
|
||||
* 规则:从最低位开始,将每个位上的数提取出来,乘以 2 的 (位数 - 1 )次方,然后求和。
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> * ① 在学术界,将这种计算规则,称为`位权相加法`。
|
||||
> * ② `八进制转换为十进制`、`十六进制转换为十进制`和`二进制转换为十进制`的算法相同!!!
|
||||
|
||||
|
||||
|
||||
* 示例:十进制转十进制
|
||||
|
||||
![](./assets/27.svg)
|
||||
|
||||
|
||||
|
||||
* 示例:二进制转十进制
|
||||
|
||||
![](./assets/28.svg)
|
||||
|
||||
#### 3.4.2.2 十进制转换二进制
|
||||
|
||||
* 规则:将该数不断除以 2 ,直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的二进制。
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> * ① 在学术界,将这种计算规则,称为`短除法`或`连续除2取余法`。
|
||||
> * ② 很好理解,只有不断地除以 2 ,就能保证最大的数字不超过 2 ,这不就是二进制(只能有 0 或 1)吗?
|
||||
> * ③ `八进制转换为二进制`、`十六进制转换为二进制`和`十进制转换为二进制`的算法相同!!!
|
||||
|
||||
|
||||
|
||||
* 示例:十进制转十进制
|
||||
|
||||
![](./assets/29.svg)
|
||||
|
||||
|
||||
|
||||
* 示例:十进制转二进制
|
||||
|
||||
![](./assets/30.svg)
|
||||
|
||||
### 3.4.3 二进制转八进制
|
||||
|
||||
* 规则:从右向左,每 3 位二进制就是一个八进制,不足补 0(分组转换法)。
|
||||
|
||||
|
||||
|
||||
* 示例:011 101 001 -> 351
|
||||
|
||||
![](./assets/31.svg)
|
||||
|
||||
|
||||
|
||||
### 3.4.4 二进制转十六进制
|
||||
|
||||
* 规则:从右向左,每 4 位二进制就是一个十六进制,不足补 0(分组转换法)。
|
||||
|
||||
|
||||
|
||||
* 示例:1110 1001 -> 0xE9
|
||||
|
||||
![](./assets/32.svg)
|
||||
|
||||
## 3.5 原码、反码和补码
|
||||
|
||||
### 3.5.1 概述
|
||||
|
||||
* 机器数:一个数在计算机的存储形式是二进制,我们称这些二进制数为机器数。机器数可以是有符号的,用机器数的最高位来存放符号位,`0` 表示正数,`1` 表示负数。
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 这里讨论的适用于`有符号位`的整数,如:int 等。
|
||||
> * ② 这里讨论的不适用于`无符号位`的整数,即:unsinged int 等。
|
||||
|
||||
![](./assets/33.svg)
|
||||
|
||||
* 真值(数据位):因为机器数带有符号位,所以机器数的形式值不等于其真实表示的值(真值),以机器数 1000 0001 为例,其真正表示的值(首位是符号位)为 -1,而形式值却是 129 ,因此将带有符号位的机器数的真正表示的值称为机器数的真值。
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 这里讨论的适用于`有符号位`的整数,如:int 等。
|
||||
> * ② 这里讨论的不适用于`无符号位`的整数,即:unsinged int 等。
|
||||
|
||||
![](./assets/34.svg)
|
||||
|
||||
### 3.5.2 原码
|
||||
|
||||
* 原码的表示与机器数真值表示的一样,即用第一位表示符号,其余位表示数值。
|
||||
* 规则:
|
||||
* 正数的`原码`是它本身对应的二进制数,符号位是 0 。
|
||||
* 负数的`原码`是它本身绝对值对应的二进制数,但是符号位是 1 。
|
||||
* `+1` 的原码,使用 `16` 位二进数来表示,就是:
|
||||
|
||||
| 十进制数 | 原码(16位二进制数) |
|
||||
| -------- | --------------------- |
|
||||
| +1 | `0`000 0000 0000 0001 |
|
||||
|
||||
* `-1` 的原码,使用 `16` 位二进数来表示,就是:
|
||||
|
||||
| 十进制数 | 原码(16位二进制数) |
|
||||
| -------- | --------------------- |
|
||||
| -1 | `1`000 0000 0000 0001 |
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 按照原码的规则,会出现 `+0` 和 `-0` 的情况,即:`0`000 0000 0000 0001(+0)、`1`000 0000 0000 0001(-0),显然不符合实际情况。
|
||||
>* ② 所以,计算机底层虽然存储和计算的都是二进数,但显然不是原码。
|
||||
|
||||
### 3.5.3 反码
|
||||
|
||||
* 规则:
|
||||
|
||||
* 正数的反码和它的原码相同。
|
||||
* 负数的反码是在其原码的基础上,符号位不变,其余各位取反。
|
||||
|
||||
* `+1` 的反码,使用 `16` 位二进数来表示,就是:
|
||||
|
||||
| 十进制数 | 原码(16位二进制数) | 反码(16位二进制数) |
|
||||
| -------- | --------------------- | --------------------- |
|
||||
| +1 | `0`000 0000 0000 0001 | `0`000 0000 0000 0001 |
|
||||
|
||||
* `-1` 的反码,使用 `16` 位二进数来表示,就是:
|
||||
|
||||
| 十进制数 | 原码(16位二进制数) | 反码(16位二进制数) |
|
||||
| -------- | --------------------- | --------------------- |
|
||||
| -1 | `1`000 0000 0000 0001 | `1`111 1111 1111 1110 |
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 按照反码的规则,如果是 `+0`,对应的原码是 `0`000 0000 0000 0000;那么,其反码还是 `0`000 0000 0000 0000;如果是 `-0`,对应的原码是 `1`000 0000 0000 0000,其反码是 `1`111 1111 1111 1111,显然不符合实际情况。
|
||||
>* ② 所以,计算机底层虽然存储和计算的都是二进数,但显然不是反码。
|
||||
|
||||
### 3.5.4 补码
|
||||
|
||||
* 规则:
|
||||
|
||||
* 正数的补码和它的原码相同。
|
||||
* 负数的补码是在其反码的基础上 + 1 。
|
||||
* `+1` 的补码,使用 `16` 位二进数来表示,就是:
|
||||
|
||||
| 十进制数 | 原码(16位二进制数) | 反码(16位二进制数) | 补码(16位二进制数) |
|
||||
| -------- | --------------------- | --------------------- | --------------------- |
|
||||
| +1 | `0`000 0000 0000 0001 | `0`000 0000 0000 0001 | `0`000 0000 0000 0001 |
|
||||
|
||||
* `-1` 的补码,使用 `16` 位二进数来表示,就是:
|
||||
|
||||
| 十进制数 | 原码(16位二进制数) | 反码(16位二进制数) | 补码(16位二进制数) |
|
||||
| -------- | --------------------- | --------------------- | --------------------- |
|
||||
| -1 | `1`000 0000 0000 0001 | `1`111 1111 1111 1110 | `1`111 1111 1111 1111 |
|
||||
|
||||
* 如果 `0` ,按照 `+0` 的情况进行处理,如下所示:
|
||||
|
||||
![](./assets/35.svg)
|
||||
|
||||
* 如果 `0` ,按照 `-0` 的情况进行处理,如下所示:
|
||||
|
||||
![](./assets/36.svg)
|
||||
|
||||
* `+1` 和 `-1` 的`原码`和`补码`的转换过程,如下所示:
|
||||
|
||||
![](./assets/37.svg)
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 补码表示法解决了`原码`和`反码`存在的`两种`零(`+0` 和 `-0`)的问题,即:在补码表示法中,只有`一个`零,即 `0000 0000`。
|
||||
>* ②补码使得`加法运算`和`减法运算`可以统一处理,通过将减法运算`转换`为加法运算,可以简化硬件设计,提高了运算效率。
|
||||
> * ③ 计算机底层`存储`和`计算`的都是`二进数的补码`。换言之,当`读取`整数的时候,需要采用`逆向`的转换,即:将补码转换为原码。正数的原码、反码、补码都是一样的,三码合一。负数的补码转换为原码的方法就是先减去 `1` ,得到反码,再按位取反,得到原码(符号位是不能借位的)。
|
||||
|
||||
### 3.5.5 总结
|
||||
|
||||
* ① 计算机底层`存储`和`计算`的都是`二进数的补码`。换言之,当`读取`整数的时候,需要采用`逆向`的转换,即:将补码转换为原码。
|
||||
* ② 正数的原码、反码和补码都是一样的,三码合一。
|
||||
* ③ 负数的反码是在其原码的基础上,按位取反(0 变 1 ,1 变 0 ),符号位不变;负数的补码是其反码 + 1 。
|
||||
* ④ 0 的补码是 0 。
|
||||
* ⑤ 负数的补码转换为原码的方法就是先减去 `1` ,得到反码,再按位取反,得到原码(符号位是不能借位的)。
|
||||
|
||||
## 3.6 计算机底层为什么使用补码?
|
||||
|
||||
* `加法`和`减法`是计算机中最基本的运算,计算机时时刻刻都离不开它们,所以它们由硬件直接支持。为了提高加法和减法的运行效率,硬件电路必须设计得尽量简单。
|
||||
* 对于有符号位的数字来说,内存需要区分符号位和数值位:对于人类来说,很容易识别(最高位是 0 还是 1);但是,对于计算机来说,需要设计专门的电路,这无疑增加了硬件的复杂性,增加了计算时间。如果能将符号位和数值位等同起来,让它们一起参与运算,不再加以区分,这样硬件电路就可以变得非常简单。
|
||||
* 此外,加法和减法也可以合并为一种运算,即:加法运算。换言之,减去一个数就相当于加上这个数的相反数,如:`5 - 3` 相当于 `5 +(-3)`,`10 -(-9)`相当于 `10 + 9` 。
|
||||
|
||||
* 如果能够实现上述的两个目标,那么只需要设计一种简单的、不用区分符号位和数值位的加法电路,就能同时实现加法运算和减法运算,而且非常高效。其实,这两个目标已经实现了,真正的计算机的硬件电路就是这样设计的。
|
||||
* 但是,简化硬件电路是有代价的,这个代价就是`有符号数`在存储和读取的时候都要继续转换。这也是对于有符号数的运算来说,计算机底层为什么使用`补码`的原因所在。
|
||||
|
||||
## 3.7 补码到底是如何简化硬件电路的?
|
||||
|
||||
* 假设 6 和 18 都是 short 类型,现在我们要计算 `6 - 18` 的结果,根据运算规则,它等价于 `6 +(-18)`。如果按照采用`原码`来计算,那么运算过程是这样的,如下所示:
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 直接使用原码表示整数,让符号位也参与运算,那么对于减法来说,结果显然是不正确的。
|
||||
|
||||
![](./assets/38.svg)
|
||||
|
||||
* 于是,人们开始继续探索,不断试错,终于设计出了`反码`,如下所示:
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 直接使用反码表示整数,让符号位也参与运算,对于 6 +(-18)来说,结果貌似正确。
|
||||
|
||||
![](./assets/39.svg)
|
||||
|
||||
* 如果我们将`被减数`和`减数`对调一下,即:计算 `18 - 6` 的结果,也就是 `18 +(-6)`的结果,继续采用`反码`来进行运算,如下所示:
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> * ① 6 - 18,即:6+(-18),如果采用`反码`计算,结果是正确的;但是,18 - 6,即:18 +(-6),如果采用`反码`计算,结果相差 1 。
|
||||
> * ② 可以推断:如果按照`反码`来计算,小数 - 大数,结果正确;而大数 - 小数,结果相差 1 。
|
||||
|
||||
![](./assets/40.svg)
|
||||
|
||||
* 对于这个相差的 `1` 必须进行纠正,但是又不能影响`小数-大数`的结果。于是,人们又绞尽脑汁设计出了`补码`,给`反码`打了一个`“补丁”`,终于把相差的 `1` 给纠正过来了。那么,`6 - 18` 按照`补码`的运算过程,如下所示:
|
||||
|
||||
![](./assets/41.svg)
|
||||
|
||||
* 那么,`18 - 6` 按照`补码`的运算过程,如下所示:
|
||||
|
||||
![](./assets/42.svg)
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 总结:采用`补码`的形式正好将相差的 `1`纠正过来,也没有影响到小数减大数,这个“补丁”非常巧妙。
|
||||
>
|
||||
> * ① 小数减去大数,结果为负,之前(负数从反码转换为补码需要 +1)加上的 1 ,后来(负数从补码转换为反码要 -1)还需要减去,正好抵消掉,所以不会受到影响。
|
||||
> * ② 大数减去小数,结果为正,之前(负数从反码转换为补码需要 +1)加上的 1 ,后来(正数的补码和反码相同,从补码转换为反码不用 -1)就没有再减去,不能抵消掉,这就相当于给计算结果多加了一个 1。
|
||||
>
|
||||
> `补码`这种天才般的设计,一举达成了之前加法运算和减法运算提到的两个目标,简化了硬件电路。
|
||||
|
||||
## 3.8 问题抛出
|
||||
|
||||
* 在 C 语言中,对于`有符号位`的整数,是使用 `0` 作为正数,`1` 作为负数,来表示`符号位`,并使用`数据位`来表示的是数据的`真值`,如下所示:
|
||||
|
||||
```c
|
||||
int a = 10;
|
||||
int b = -10;
|
||||
```
|
||||
|
||||
![](./assets/43.svg)
|
||||
|
||||
* 但是,对于`无符号位`的整数而言,是`没有`符号位和数据位,即:没有原码、反码、补码的概念。无符号位的整数的数值都是直接使用二进制来表示的(也可以理解为,对于无符号位的整数,计算机底层存储的就是其原码),如下所示:
|
||||
|
||||
```c
|
||||
unsigned int a = 10;
|
||||
unsigned int b = -10;
|
||||
```
|
||||
|
||||
![](./assets/44.svg)
|
||||
|
||||
* 这就是导致了一个结果就是:如果我定义一个`有符号`的负数,却让其输出`无符号`,必然造成结果不对,如下所示:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
char *getBinary(int num) {
|
||||
static char binaryString[33];
|
||||
int i, j;
|
||||
|
||||
for (i = sizeof(num) * 8 - 1, j = 0; i >= 0; i--, j++) {
|
||||
const int bit = (num >> i) & 1;
|
||||
binaryString[j] = bit + '0';
|
||||
}
|
||||
|
||||
binaryString[j] = '\0';
|
||||
return binaryString;
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
// 禁用 stdout 缓冲区
|
||||
setbuf(stdout, NULL);
|
||||
|
||||
int num = -10;
|
||||
printf("b=%s\n", getBinary(num)); // b=11111111111111111111111111110110
|
||||
printf("b=%d\n", num); // b=-10
|
||||
printf("b=%u\n", num); // b=4294967286
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
* 其实,C 语言的底层逻辑很简单,C 语言压根不关心你定义的是有符号数还是无符号数,它只关心内存(如果定义的是有符号数,那就按照有符号数的规则来存储;如果定义的是无符号数,那就按照无符号数的规则来存储)。换言之,有符号数可以按照无符号数的规则来输出,无符号数也可以按照有符号数的规则来输出,至于输出结果对不对,那是程序员的事情,和 C 语言没有任何关系。
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 实际开发中,`printf` 函数中的常量、变量或表达式,需要和格式占位符一一对应;否则,将会出现数据错误的现象。
|
||||
> * ② 正因为上述的原因,很多现代化的编程语言,如:Java 等,直接取消了无符号的概念。但是,很多数据库是使用 C 语言开发的,如:MySQL 等,就提供了创建数据表的字段为无符号类型的功能,即:`UNSIGNED`(正整数) ,不要感觉困惑!!!
|
||||
> * ③ 对于 `1000 0000 …… 0000 0000` 这个特殊的补码,无法按照上述的方法转换为原码,所以计算机直接规定这个补码对应的值就是 `-2³¹`,至于为什么,下节我们会详细分析。
|
||||
|
||||
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 217 KiB |
Before Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 217 KiB |
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 211 KiB |
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 486 KiB After Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 438 KiB |
Before Width: | Height: | Size: 495 KiB After Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 451 KiB After Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 522 KiB After Width: | Height: | Size: 398 KiB |
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 507 KiB After Width: | Height: | Size: 507 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 534 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 443 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 410 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 211 KiB |
Before Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 438 KiB |
Before Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 486 KiB After Width: | Height: | Size: 486 KiB |
Before Width: | Height: | Size: 398 KiB |
Before Width: | Height: | Size: 507 KiB |
Before Width: | Height: | Size: 534 KiB |
Before Width: | Height: | Size: 443 KiB |
Before Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 426 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 495 KiB After Width: | Height: | Size: 495 KiB |
Before Width: | Height: | Size: 217 KiB |
Before Width: | Height: | Size: 509 KiB |
Before Width: | Height: | Size: 451 KiB After Width: | Height: | Size: 451 KiB |
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 167 KiB |
Before Width: | Height: | Size: 522 KiB After Width: | Height: | Size: 522 KiB |
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 9.6 KiB |
@ -8,7 +8,7 @@
|
||||
|
||||
* 计算机的底层只有`二进制`,即计算机中`运算`和`存储`的`所有数据`都需要转换为`二进制`,包括:数字、字符、图片、视频等。
|
||||
|
||||
![](./assets/17.jpg)
|
||||
![](./assets/1.jpg)
|
||||
|
||||
## 1.2 冯·诺依曼体系结构
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
|
||||
* 在生活中,我们最为常用的进制就是`十进制`,其规则是`满 10 进 1` ,即:
|
||||
|
||||
![](./assets/18.jpeg)
|
||||
![](./assets/2.jpeg)
|
||||
|
||||
* 在计算机中,常见的进制有`二进制`、`八进制`和`十六进制`,即:
|
||||
* 二进制:只能 0 和 1 ,满 2 进 1 。
|
||||
@ -148,7 +148,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2.3 输出格式
|
||||
## 2.3 输出格式
|
||||
|
||||
* 在 C 语言中,可以使用不同的`格式占位符`来`输出`不同`进制`的整数,如下所示:
|
||||
* `%d`:十进制整数。
|
||||
@ -184,9 +184,11 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 3.3 进制的运算规则
|
||||
|
||||
### 3.3.1 概述
|
||||
|
||||
# 第三章: 进制的运算规则
|
||||
|
||||
## 3.1 概述
|
||||
|
||||
* `十进制`的运算规则,如下所示:
|
||||
* 逢`十`进`一`(针对加法而言)。
|
||||
@ -201,51 +203,53 @@ int main() {
|
||||
* 逢`十六`进`一`(针对加法而言)。
|
||||
* 借`一`当`十六`(针对减法而言)。
|
||||
|
||||
### 3.3.2 二进制的运算
|
||||
## 3.2 二进制的运算
|
||||
|
||||
* 二进制的加法:`1 + 0 = 1` 、`1 + 1 = 10`、`11 + 10 = 101`、`111 + 111 = 1110`。
|
||||
|
||||
![](./assets/19.svg)
|
||||
![](./assets/3.svg)
|
||||
|
||||
* 二进制的减法:`1 - 0 = 1` 、`10 - 1 = 1`、`101 - 11 = 10`、`1100 - 111 = 101` 。
|
||||
|
||||
![](./assets/20.svg)
|
||||
![](./assets/4.svg)
|
||||
|
||||
### 3.3.3 八进制的运算
|
||||
## 3.3 八进制的运算
|
||||
|
||||
* 八进制的加法:`3 + 4 = 7` 、`5 + 6 = 13`、`75 + 42 = 137`、`2427 + 567 = 3216`。
|
||||
|
||||
![](./assets/21.svg)
|
||||
![](./assets/5.svg)
|
||||
|
||||
* 八进制的减法:`6 - 4 = 2` 、`52 - 27 = 33`、`307 - 141 = 146`、`7430 - 1451 = 5757` 。
|
||||
|
||||
![](./assets/22.svg)
|
||||
![](./assets/6.svg)
|
||||
|
||||
### 3.3.4 十六进制的运算
|
||||
## 3.4 十六进制的运算
|
||||
|
||||
* 十六进制的加法:`6 + 7 = D` 、`18 + BA = D2`、`595 + 792 = D27`、`2F87 + F8A = 3F11`。
|
||||
|
||||
![](./assets/23.svg)
|
||||
![](./assets/7.svg)
|
||||
|
||||
* 十六进制的减法:`D - 3 = A` 、`52 - 2F = 23`、`E07 - 141 = CC6`、`7CA0 - 1CB1 = 5FEF` 。
|
||||
|
||||
![](./assets/24.svg)
|
||||
![](./assets/8.svg)
|
||||
|
||||
## 3.4 进制的转换
|
||||
|
||||
### 3.4.1 概述
|
||||
|
||||
# 第四章:进制的转换
|
||||
|
||||
## 4.1 概述
|
||||
|
||||
* 不同进制的转换,如下所示:
|
||||
|
||||
![](./assets/25.png)
|
||||
![](./assets/9.png)
|
||||
|
||||
* 在计算机中,数据是从右往左的方式排列的;其中,最右边的是低位,最左边的是高位,即:
|
||||
|
||||
![](./assets/26.svg)
|
||||
![](./assets/10.svg)
|
||||
|
||||
### 3.4.2 二进制和十进制的转换
|
||||
## 4.2 二进制和十进制的转换
|
||||
|
||||
#### 3.4.2.1 二进制转换为十进制
|
||||
### 4.2.1 二进制转换为十进制
|
||||
|
||||
* 规则:从最低位开始,将每个位上的数提取出来,乘以 2 的 (位数 - 1 )次方,然后求和。
|
||||
|
||||
@ -258,15 +262,15 @@ int main() {
|
||||
|
||||
* 示例:十进制转十进制
|
||||
|
||||
![](./assets/27.svg)
|
||||
![](./assets/11.svg)
|
||||
|
||||
|
||||
|
||||
* 示例:二进制转十进制
|
||||
|
||||
![](./assets/28.svg)
|
||||
![](./assets/12.svg)
|
||||
|
||||
#### 3.4.2.2 十进制转换二进制
|
||||
### 4.2.2 十进制转换二进制
|
||||
|
||||
* 规则:将该数不断除以 2 ,直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的二进制。
|
||||
|
||||
@ -280,15 +284,15 @@ int main() {
|
||||
|
||||
* 示例:十进制转十进制
|
||||
|
||||
![](./assets/29.svg)
|
||||
![](./assets/13.svg)
|
||||
|
||||
|
||||
|
||||
* 示例:十进制转二进制
|
||||
|
||||
![](./assets/30.svg)
|
||||
![](./assets/14.svg)
|
||||
|
||||
### 3.4.3 二进制转八进制
|
||||
### 4.2.3 二进制转八进制
|
||||
|
||||
* 规则:从右向左,每 3 位二进制就是一个八进制,不足补 0(分组转换法)。
|
||||
|
||||
@ -296,11 +300,11 @@ int main() {
|
||||
|
||||
* 示例:011 101 001 -> 351
|
||||
|
||||
![](./assets/31.svg)
|
||||
![](./assets/15.svg)
|
||||
|
||||
|
||||
|
||||
### 3.4.4 二进制转十六进制
|
||||
### 4.2.4 二进制转十六进制
|
||||
|
||||
* 规则:从右向左,每 4 位二进制就是一个十六进制,不足补 0(分组转换法)。
|
||||
|
||||
@ -308,11 +312,13 @@ int main() {
|
||||
|
||||
* 示例:1110 1001 -> 0xE9
|
||||
|
||||
![](./assets/32.svg)
|
||||
![](./assets/16.svg)
|
||||
|
||||
## 3.5 原码、反码和补码
|
||||
|
||||
### 3.5.1 概述
|
||||
|
||||
# 第五章:原码、反码和补码
|
||||
|
||||
## 5.1 概述
|
||||
|
||||
* 机器数:一个数在计算机的存储形式是二进制,我们称这些二进制数为机器数。机器数可以是有符号的,用机器数的最高位来存放符号位,`0` 表示正数,`1` 表示负数。
|
||||
|
||||
@ -321,7 +327,7 @@ int main() {
|
||||
> * ① 这里讨论的适用于`有符号位`的整数,如:int 等。
|
||||
> * ② 这里讨论的不适用于`无符号位`的整数,即:unsinged int 等。
|
||||
|
||||
![](./assets/33.svg)
|
||||
![](./assets/17.svg)
|
||||
|
||||
* 真值(数据位):因为机器数带有符号位,所以机器数的形式值不等于其真实表示的值(真值),以机器数 1000 0001 为例,其真正表示的值(首位是符号位)为 -1,而形式值却是 129 ,因此将带有符号位的机器数的真正表示的值称为机器数的真值。
|
||||
|
||||
@ -330,9 +336,9 @@ int main() {
|
||||
> * ① 这里讨论的适用于`有符号位`的整数,如:int 等。
|
||||
> * ② 这里讨论的不适用于`无符号位`的整数,即:unsinged int 等。
|
||||
|
||||
![](./assets/34.svg)
|
||||
![](./assets/18.svg)
|
||||
|
||||
### 3.5.2 原码
|
||||
## 5.2 原码
|
||||
|
||||
* 原码的表示与机器数真值表示的一样,即用第一位表示符号,其余位表示数值。
|
||||
* 规则:
|
||||
@ -355,7 +361,7 @@ int main() {
|
||||
> * ① 按照原码的规则,会出现 `+0` 和 `-0` 的情况,即:`0`000 0000 0000 0001(+0)、`1`000 0000 0000 0001(-0),显然不符合实际情况。
|
||||
>* ② 所以,计算机底层虽然存储和计算的都是二进数,但显然不是原码。
|
||||
|
||||
### 3.5.3 反码
|
||||
## 5.3 反码
|
||||
|
||||
* 规则:
|
||||
|
||||
@ -379,7 +385,7 @@ int main() {
|
||||
> * ① 按照反码的规则,如果是 `+0`,对应的原码是 `0`000 0000 0000 0000;那么,其反码还是 `0`000 0000 0000 0000;如果是 `-0`,对应的原码是 `1`000 0000 0000 0000,其反码是 `1`111 1111 1111 1111,显然不符合实际情况。
|
||||
>* ② 所以,计算机底层虽然存储和计算的都是二进数,但显然不是反码。
|
||||
|
||||
### 3.5.4 补码
|
||||
## 5.4 补码
|
||||
|
||||
* 规则:
|
||||
|
||||
@ -399,15 +405,15 @@ int main() {
|
||||
|
||||
* 如果 `0` ,按照 `+0` 的情况进行处理,如下所示:
|
||||
|
||||
![](./assets/35.svg)
|
||||
![](./assets/19.svg)
|
||||
|
||||
* 如果 `0` ,按照 `-0` 的情况进行处理,如下所示:
|
||||
|
||||
![](./assets/36.svg)
|
||||
![](./assets/20.svg)
|
||||
|
||||
* `+1` 和 `-1` 的`原码`和`补码`的转换过程,如下所示:
|
||||
|
||||
![](./assets/37.svg)
|
||||
![](./assets/21.svg)
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
@ -415,7 +421,7 @@ int main() {
|
||||
>* ②补码使得`加法运算`和`减法运算`可以统一处理,通过将减法运算`转换`为加法运算,可以简化硬件设计,提高了运算效率。
|
||||
> * ③ 计算机底层`存储`和`计算`的都是`二进数的补码`。换言之,当`读取`整数的时候,需要采用`逆向`的转换,即:将补码转换为原码。正数的原码、反码、补码都是一样的,三码合一。负数的补码转换为原码的方法就是先减去 `1` ,得到反码,再按位取反,得到原码(符号位是不能借位的)。
|
||||
|
||||
### 3.5.5 总结
|
||||
## 5.5 总结
|
||||
|
||||
* ① 计算机底层`存储`和`计算`的都是`二进数的补码`。换言之,当`读取`整数的时候,需要采用`逆向`的转换,即:将补码转换为原码。
|
||||
* ② 正数的原码、反码和补码都是一样的,三码合一。
|
||||
@ -423,7 +429,11 @@ int main() {
|
||||
* ④ 0 的补码是 0 。
|
||||
* ⑤ 负数的补码转换为原码的方法就是先减去 `1` ,得到反码,再按位取反,得到原码(符号位是不能借位的)。
|
||||
|
||||
## 3.6 计算机底层为什么使用补码?
|
||||
|
||||
|
||||
# 第六章:计算机底层为什么使用补码?
|
||||
|
||||
## 6.1 概述
|
||||
|
||||
* `加法`和`减法`是计算机中最基本的运算,计算机时时刻刻都离不开它们,所以它们由硬件直接支持。为了提高加法和减法的运行效率,硬件电路必须设计得尽量简单。
|
||||
* 对于有符号位的数字来说,内存需要区分符号位和数值位:对于人类来说,很容易识别(最高位是 0 还是 1);但是,对于计算机来说,需要设计专门的电路,这无疑增加了硬件的复杂性,增加了计算时间。如果能将符号位和数值位等同起来,让它们一起参与运算,不再加以区分,这样硬件电路就可以变得非常简单。
|
||||
@ -432,7 +442,7 @@ int main() {
|
||||
* 如果能够实现上述的两个目标,那么只需要设计一种简单的、不用区分符号位和数值位的加法电路,就能同时实现加法运算和减法运算,而且非常高效。其实,这两个目标已经实现了,真正的计算机的硬件电路就是这样设计的。
|
||||
* 但是,简化硬件电路是有代价的,这个代价就是`有符号数`在存储和读取的时候都要继续转换。这也是对于有符号数的运算来说,计算机底层为什么使用`补码`的原因所在。
|
||||
|
||||
## 3.7 补码到底是如何简化硬件电路的?
|
||||
## 6.2 补码到底是如何简化硬件电路的?
|
||||
|
||||
* 假设 6 和 18 都是 short 类型,现在我们要计算 `6 - 18` 的结果,根据运算规则,它等价于 `6 +(-18)`。如果按照采用`原码`来计算,那么运算过程是这样的,如下所示:
|
||||
|
||||
@ -440,7 +450,7 @@ int main() {
|
||||
>
|
||||
> 直接使用原码表示整数,让符号位也参与运算,那么对于减法来说,结果显然是不正确的。
|
||||
|
||||
![](./assets/38.svg)
|
||||
![](./assets/22.svg)
|
||||
|
||||
* 于是,人们开始继续探索,不断试错,终于设计出了`反码`,如下所示:
|
||||
|
||||
@ -448,7 +458,7 @@ int main() {
|
||||
>
|
||||
> 直接使用反码表示整数,让符号位也参与运算,对于 6 +(-18)来说,结果貌似正确。
|
||||
|
||||
![](./assets/39.svg)
|
||||
![](./assets/23.svg)
|
||||
|
||||
* 如果我们将`被减数`和`减数`对调一下,即:计算 `18 - 6` 的结果,也就是 `18 +(-6)`的结果,继续采用`反码`来进行运算,如下所示:
|
||||
|
||||
@ -457,15 +467,15 @@ int main() {
|
||||
> * ① 6 - 18,即:6+(-18),如果采用`反码`计算,结果是正确的;但是,18 - 6,即:18 +(-6),如果采用`反码`计算,结果相差 1 。
|
||||
> * ② 可以推断:如果按照`反码`来计算,小数 - 大数,结果正确;而大数 - 小数,结果相差 1 。
|
||||
|
||||
![](./assets/40.svg)
|
||||
![](./assets/24.svg)
|
||||
|
||||
* 对于这个相差的 `1` 必须进行纠正,但是又不能影响`小数-大数`的结果。于是,人们又绞尽脑汁设计出了`补码`,给`反码`打了一个`“补丁”`,终于把相差的 `1` 给纠正过来了。那么,`6 - 18` 按照`补码`的运算过程,如下所示:
|
||||
|
||||
![](./assets/41.svg)
|
||||
![](./assets/25.svg)
|
||||
|
||||
* 那么,`18 - 6` 按照`补码`的运算过程,如下所示:
|
||||
|
||||
![](./assets/42.svg)
|
||||
![](./assets/26.svg)
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
@ -476,7 +486,7 @@ int main() {
|
||||
>
|
||||
> `补码`这种天才般的设计,一举达成了之前加法运算和减法运算提到的两个目标,简化了硬件电路。
|
||||
|
||||
## 3.8 问题抛出
|
||||
## 6.3 问题抛出
|
||||
|
||||
* 在 C 语言中,对于`有符号位`的整数,是使用 `0` 作为正数,`1` 作为负数,来表示`符号位`,并使用`数据位`来表示的是数据的`真值`,如下所示:
|
||||
|
||||
@ -485,7 +495,7 @@ int a = 10;
|
||||
int b = -10;
|
||||
```
|
||||
|
||||
![](./assets/43.svg)
|
||||
![](./assets/27.svg)
|
||||
|
||||
* 但是,对于`无符号位`的整数而言,是`没有`符号位和数据位,即:没有原码、反码、补码的概念。无符号位的整数的数值都是直接使用二进制来表示的(也可以理解为,对于无符号位的整数,计算机底层存储的就是其原码),如下所示:
|
||||
|
||||
@ -494,7 +504,7 @@ unsigned int a = 10;
|
||||
unsigned int b = -10;
|
||||
```
|
||||
|
||||
![](./assets/44.svg)
|
||||
![](./assets/28.svg)
|
||||
|
||||
* 这就是导致了一个结果就是:如果我定义一个`有符号`的负数,却让其输出`无符号`,必然造成结果不对,如下所示:
|
||||
|
||||
|