✨ feat: 添加文档功能和页面模板
This commit is contained in:
451
web/doc/2.2 数组的插入与删除.md
Normal file
451
web/doc/2.2 数组的插入与删除.md
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
# 2.2 数组的插入与删除
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、先把数组想成一排固定座位
|
||||||
|
|
||||||
|
今天我们只学两件事:
|
||||||
|
|
||||||
|
- 往数组里放进一个新元素,这叫**插入**。
|
||||||
|
- 从数组里拿走一个元素,这叫**删除**。
|
||||||
|
|
||||||
|
数组可以想成教室里一排**紧紧挨着的固定座位**,每个位置都不能乱跳。
|
||||||
|
|
||||||
|
比如下面这个数组:
|
||||||
|
|
||||||
|
| 位置(第几个) | 第1个 | 第2个 | 第3个 | 第4个 | 第5个 |
|
||||||
|
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
|
| 下标 | 0 | 1 | 2 | 3 | 4 |
|
||||||
|
| 内容 | 12 | 18 | 23 | 31 | 40 |
|
||||||
|
|
||||||
|
这里要特别注意:
|
||||||
|
|
||||||
|
- 平时说“第 1 个位置、第 2 个位置”,是按照人的习惯数的。
|
||||||
|
- 在 C++ 数组里,下标从 `0` 开始。
|
||||||
|
|
||||||
|
所以:
|
||||||
|
|
||||||
|
- 第 1 个位置,对应下标 `0`
|
||||||
|
- 第 3 个位置,对应下标 `2`
|
||||||
|
- 第 `pos` 个位置,对应下标 `pos - 1`
|
||||||
|
|
||||||
|
> 这节课讲图的时候,我们主要说“第几个位置”;写代码的时候,再换成下标。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、为什么数组的插入和删除要“搬家”?
|
||||||
|
|
||||||
|
数组里的元素是**连续排在一起**的。
|
||||||
|
|
||||||
|
这就像一排已经坐好的同学:
|
||||||
|
|
||||||
|
- 想在中间插入一个人,就得有人挪一挪,腾出空位。
|
||||||
|
- 想删掉中间一个人,就会空出一个洞,后面的人要补上来。
|
||||||
|
|
||||||
|
所以数组的插入和删除,最关键的不是“放进去”或“拿出来”,而是:
|
||||||
|
|
||||||
|
> **怎样移动其他元素,才能既不丢数据,也不弄乱顺序。**
|
||||||
|
|
||||||
|
这就是本节最重要的地方。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、数组插入:先腾位置,再放新元素
|
||||||
|
|
||||||
|
### 1. 例子:在第 3 个位置插入 20
|
||||||
|
|
||||||
|
原数组:
|
||||||
|
|
||||||
|
```text
|
||||||
|
位置: 1 2 3 4 5 6
|
||||||
|
内容: [12] [18] [23] [31] [40] [ ]
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:这里第 6 个位置先留空,表示数组容量足够大,可以插入新元素。
|
||||||
|
|
||||||
|
现在要把 `20` 插入到**第 3 个位置**。
|
||||||
|
|
||||||
|
插入后应该变成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
位置: 1 2 3 4 5 6
|
||||||
|
内容: [12] [18] [20] [23] [31] [40]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 插入时,元素是怎样移动的?
|
||||||
|
|
||||||
|
要想让第 3 个位置空出来,原来第 3、4、5 个位置上的元素都要向后移动一格。
|
||||||
|
|
||||||
|
但一定要记住:
|
||||||
|
|
||||||
|
> **插入时,要从后往前移。**
|
||||||
|
|
||||||
|
移动过程如下:
|
||||||
|
|
||||||
|
```text
|
||||||
|
原来: [12] [18] [23] [31] [40] [ ]
|
||||||
|
第1步: [12] [18] [23] [31] [40] [40] 把 40 往后移
|
||||||
|
第2步: [12] [18] [23] [31] [31] [40] 把 31 往后移
|
||||||
|
第3步: [12] [18] [23] [23] [31] [40] 把 23 往后移
|
||||||
|
第4步: [12] [18] [20] [23] [31] [40] 把 20 放进第 3 个位置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 为什么插入要从后往前移?
|
||||||
|
|
||||||
|
因为如果你从前往后移,前面的数据会把后面的数据盖掉。
|
||||||
|
|
||||||
|
来看一个错误示范。还是在第 3 个位置插入 `20`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
原来: [12] [18] [23] [31] [40] [ ]
|
||||||
|
|
||||||
|
错误地从前往后移:
|
||||||
|
第1步: [12] [18] [23] [23] [40] [ ]
|
||||||
|
第2步: [12] [18] [23] [23] [23] [ ]
|
||||||
|
第3步: [12] [18] [23] [23] [23] [23]
|
||||||
|
```
|
||||||
|
|
||||||
|
你会发现:原来的 `31` 和 `40` 都不见了!
|
||||||
|
|
||||||
|
原因是:
|
||||||
|
|
||||||
|
- 你先把 `23` 复制到了后面。
|
||||||
|
- 后面的位置被改掉以后,再继续搬,就只能搬到已经改坏的数据。
|
||||||
|
|
||||||
|
所以插入时一定要:
|
||||||
|
|
||||||
|
> **从最后一个元素开始,倒着往前挪。**
|
||||||
|
|
||||||
|
### 4. 插入的步骤口诀
|
||||||
|
|
||||||
|
可以记成一句话:
|
||||||
|
|
||||||
|
> **先留空,从后搬,最后放。**
|
||||||
|
|
||||||
|
具体就是:
|
||||||
|
|
||||||
|
1. 先保证数组后面有空位置。
|
||||||
|
2. 从最后一个元素开始,依次向后移动一格。
|
||||||
|
3. 移到目标位置后停下。
|
||||||
|
4. 把新元素放进去。
|
||||||
|
5. 数组长度 `n` 加 1。
|
||||||
|
|
||||||
|
### 5. 插入的课堂版 C++ 代码
|
||||||
|
|
||||||
|
下面的代码约定:
|
||||||
|
|
||||||
|
- 数组现在有 `n` 个元素。
|
||||||
|
- 要把 `x` 插入到第 `pos` 个位置。
|
||||||
|
- `pos` 从 `1` 开始数。
|
||||||
|
- 数组容量足够大。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for (int i = n; i >= pos; i--) {
|
||||||
|
a[i] = a[i - 1];
|
||||||
|
}
|
||||||
|
a[pos - 1] = x;
|
||||||
|
n++;
|
||||||
|
```
|
||||||
|
|
||||||
|
你可以这样理解这段循环:
|
||||||
|
|
||||||
|
- `i = n` 时,把最后一个元素搬到新位置。
|
||||||
|
- `i = n - 1` 时,把倒数第二个元素往后搬。
|
||||||
|
- 一直搬到第 `pos` 个位置为止。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、数组删除:先补空位,再缩短长度
|
||||||
|
|
||||||
|
### 1. 例子:删除第 3 个位置的元素
|
||||||
|
|
||||||
|
原数组:
|
||||||
|
|
||||||
|
```text
|
||||||
|
位置: 1 2 3 4 5
|
||||||
|
内容: [ 8] [11] [14] [17] [20]
|
||||||
|
```
|
||||||
|
|
||||||
|
现在要删除第 3 个位置上的 `14`。
|
||||||
|
|
||||||
|
删除后应该变成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
位置: 1 2 3 4
|
||||||
|
内容: [ 8] [11] [17] [20]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 删除时,元素是怎样移动的?
|
||||||
|
|
||||||
|
第 3 个位置删掉以后,会空出一个位置。后面的元素要依次往前补。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
> **删除时,要从前往后移。**
|
||||||
|
|
||||||
|
移动过程如下:
|
||||||
|
|
||||||
|
```text
|
||||||
|
原来: [ 8] [11] [14] [17] [20]
|
||||||
|
第1步: [ 8] [11] [17] [17] [20] 把 17 往前移
|
||||||
|
第2步: [ 8] [11] [17] [20] [20] 把 20 往前移
|
||||||
|
最后: [ 8] [11] [17] [20] 长度减 1
|
||||||
|
```
|
||||||
|
|
||||||
|
最后那个多出来的 `20` 虽然还留在原来的地方,但因为数组长度已经减 1,所以它已经**不算数组的一部分了**。
|
||||||
|
|
||||||
|
### 3. 为什么删除要从前往后移?
|
||||||
|
|
||||||
|
因为空位在前面,应该让后面的元素一个一个补上来。
|
||||||
|
|
||||||
|
如果你反过来,从后往前移,也会出问题。还是删除第 3 个位置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
原来: [ 8] [11] [14] [17] [20]
|
||||||
|
|
||||||
|
错误地从后往前移:
|
||||||
|
第1步: [ 8] [11] [14] [20] [20]
|
||||||
|
第2步: [ 8] [11] [20] [20] [20]
|
||||||
|
```
|
||||||
|
|
||||||
|
原来的 `17` 被弄丢了。
|
||||||
|
|
||||||
|
所以删除时要记住:
|
||||||
|
|
||||||
|
> **从缺口的后面开始,按顺序往前补。**
|
||||||
|
|
||||||
|
### 4. 删除的步骤口诀
|
||||||
|
|
||||||
|
可以记成一句话:
|
||||||
|
|
||||||
|
> **先删掉,从前搬,最后减。**
|
||||||
|
|
||||||
|
具体就是:
|
||||||
|
|
||||||
|
1. 找到要删除的位置。
|
||||||
|
2. 让后面的元素依次向前移动一格。
|
||||||
|
3. 数组长度 `n` 减 1。
|
||||||
|
|
||||||
|
### 5. 删除的课堂版 C++ 代码
|
||||||
|
|
||||||
|
下面的代码约定:
|
||||||
|
|
||||||
|
- 数组现在有 `n` 个元素。
|
||||||
|
- 删除第 `pos` 个位置上的元素。
|
||||||
|
- `pos` 从 `1` 开始数。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for (int i = pos; i < n; i++) {
|
||||||
|
a[i - 1] = a[i];
|
||||||
|
}
|
||||||
|
n--;
|
||||||
|
```
|
||||||
|
|
||||||
|
你可以这样理解这段循环:
|
||||||
|
|
||||||
|
- `i = pos` 时,把后一个元素补到空位上。
|
||||||
|
- `i = pos + 1` 时,再把更后面的元素补上来。
|
||||||
|
- 一直补到最后一个元素为止。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、插入和删除放在一起比较
|
||||||
|
|
||||||
|
| 操作 | 会发生什么 | 正确移动方向 | 为什么 |
|
||||||
|
|:---:|:---|:---:|:---|
|
||||||
|
| 插入 | 中间要腾出一个空位 | 从后往前 | 先保护后面的数据,不让它们被覆盖 |
|
||||||
|
| 删除 | 中间会出现一个缺口 | 从前往后 | 让后面的数据按顺序补上来 |
|
||||||
|
|
||||||
|
再记一遍:
|
||||||
|
|
||||||
|
- **插入:从后往前移**
|
||||||
|
- **删除:从前往后移**
|
||||||
|
|
||||||
|
这是本节最重要的结论。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、两个最容易忽略的小细节
|
||||||
|
|
||||||
|
### 1. 插入前,数组要有空位
|
||||||
|
|
||||||
|
如果数组已经装满了,就不能直接插入。
|
||||||
|
|
||||||
|
比如数组最多只能放 5 个数,现在已经有 5 个数了,再插入第 6 个,就没有地方放了。
|
||||||
|
|
||||||
|
所以写程序时,常常会这样做:
|
||||||
|
|
||||||
|
- 开一个更大的数组,比如最多放 `100` 个数。
|
||||||
|
- 实际已经用了多少个,用变量 `n` 记录。
|
||||||
|
|
||||||
|
### 2. 删除后,不是把数“擦掉”,而是把长度减 1
|
||||||
|
|
||||||
|
数组删除一个元素后,最后那个位置里可能还保留着旧数值,但这已经不重要了。
|
||||||
|
|
||||||
|
因为程序只看前 `n` 个元素:
|
||||||
|
|
||||||
|
- 删除前看前 `n` 个
|
||||||
|
- 删除后只看前 `n - 1` 个
|
||||||
|
|
||||||
|
所以真正关键的是:
|
||||||
|
|
||||||
|
> **长度变了,数组有效范围也变了。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、特殊情况也要会判断
|
||||||
|
|
||||||
|
### 1. 在最后面插入
|
||||||
|
|
||||||
|
如果要在第 `n + 1` 个位置插入,也就是直接接到数组末尾:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[12] [18] [23] [31] [40] -> [12] [18] [23] [31] [40] [50]
|
||||||
|
```
|
||||||
|
|
||||||
|
这时不需要搬动任何元素,直接放进去即可。
|
||||||
|
|
||||||
|
### 2. 删除最后一个元素
|
||||||
|
|
||||||
|
如果删除的是最后一个元素:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[12] [18] [23] [31] [40] -> [12] [18] [23] [31]
|
||||||
|
```
|
||||||
|
|
||||||
|
这时也不需要搬动任何元素,只要把长度减 1 就行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、最容易犯的 5 个错误
|
||||||
|
|
||||||
|
### 错误 1:插入时移动方向写反
|
||||||
|
|
||||||
|
结果:后面的数据会被覆盖。
|
||||||
|
|
||||||
|
### 错误 2:删除时移动方向写反
|
||||||
|
|
||||||
|
结果:中间的数据会丢失。
|
||||||
|
|
||||||
|
### 错误 3:忘记更新 `n`
|
||||||
|
|
||||||
|
插入后忘记 `n++`,新元素虽然放进去了,但程序可能不把它算进去。
|
||||||
|
|
||||||
|
删除后忘记 `n--`,旧数据虽然应该消失了,但程序还会把它输出出来。
|
||||||
|
|
||||||
|
### 错误 4:把“第几个位置”和“下标”搞混
|
||||||
|
|
||||||
|
记住:
|
||||||
|
|
||||||
|
- 第 `1` 个位置,对应下标 `0`
|
||||||
|
- 第 `pos` 个位置,对应下标 `pos - 1`
|
||||||
|
|
||||||
|
### 错误 5:插入时没有预留空间
|
||||||
|
|
||||||
|
结果:数组越界,程序可能出错。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、一张总图记住本节内容
|
||||||
|
|
||||||
|
```text
|
||||||
|
数组插入:先腾空位
|
||||||
|
|
||||||
|
[12] [18] [23] [31] [40] [ ]
|
||||||
|
← ← ←
|
||||||
|
[12] [18] [20] [23] [31] [40]
|
||||||
|
|
||||||
|
|
||||||
|
数组删除:先补缺口
|
||||||
|
|
||||||
|
[ 8] [11] [14] [17] [20]
|
||||||
|
→ →
|
||||||
|
[ 8] [11] [17] [20]
|
||||||
|
```
|
||||||
|
|
||||||
|
记忆口诀:
|
||||||
|
|
||||||
|
> **插入看后面,从后往前搬;删除看前面,从前往后搬。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、课堂版完整示例
|
||||||
|
|
||||||
|
下面是一份把“插入”和“删除”都写进去的课堂版程序。为了简单起见,输入位置 `pos` 按“第几个位置”来理解,也就是从 `1` 开始数。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
const int MAXN = 105;
|
||||||
|
int a[MAXN];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
void insertValue(int pos, int x) {
|
||||||
|
for (int i = n; i >= pos; i--) {
|
||||||
|
a[i] = a[i - 1];
|
||||||
|
}
|
||||||
|
a[pos - 1] = x;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteValue(int pos) {
|
||||||
|
for (int i = pos; i < n; i++) {
|
||||||
|
a[i - 1] = a[i];
|
||||||
|
}
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
cin >> n;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
cin >> a[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos, x;
|
||||||
|
cin >> pos >> x;
|
||||||
|
insertValue(pos, x);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
cout << a[i] << " ";
|
||||||
|
}
|
||||||
|
cout << "\n";
|
||||||
|
|
||||||
|
cin >> pos;
|
||||||
|
deleteValue(pos);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
cout << a[i] << " ";
|
||||||
|
}
|
||||||
|
cout << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、自己试一试
|
||||||
|
|
||||||
|
### 1. 观察移动过程
|
||||||
|
|
||||||
|
把 `9` 插入到下面数组的第 2 个位置,请你把每一步移动都画出来:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[3] [7] [12] [15] [ ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 观察删除过程
|
||||||
|
|
||||||
|
删除下面数组的第 4 个位置,请你把每一步移动都画出来:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[5] [8] [11] [14] [20]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 想一想
|
||||||
|
|
||||||
|
- 为什么插入和删除都可能需要移动很多个元素?
|
||||||
|
- 如果总是在数组最前面插入,会不会很麻烦?
|
||||||
|
|
||||||
|
如果你能清楚地回答这两个问题,就说明你已经真正理解数组的插入和删除了。
|
||||||
482
web/doc/3.1 原码反码补码.md
Normal file
482
web/doc/3.1 原码反码补码.md
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
# 3.1 原码、反码与补码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、先搞清楚:计算机为什么只认识 0 和 1?
|
||||||
|
|
||||||
|
你有没有想过,我们平时写下的数字 `5`、`-3`、`100`,计算机根本"看不懂"?
|
||||||
|
|
||||||
|
计算机的本质是**电路**。电路里的导线,要么**通电**,要么**断电**,只有两种状态。科学家就用 `1` 表示通电,用 `0` 表示断电。这就是**二进制**的由来。
|
||||||
|
|
||||||
|
**类比:** 想象一排电灯开关,每个开关只能「开」或「关」。用这一排开关,我们可以用不同的开/关组合来代表不同的数字——这就是二进制的核心思想。
|
||||||
|
|
||||||
|
| 十进制 | 二进制 |
|
||||||
|
|:---:|:------:|
|
||||||
|
| 0 | `0000` |
|
||||||
|
| 1 | `0001` |
|
||||||
|
| 2 | `0010` |
|
||||||
|
| 3 | `0011` |
|
||||||
|
| 5 | `0101` |
|
||||||
|
| 7 | `0111` |
|
||||||
|
|
||||||
|
> **为什么不用十进制?**
|
||||||
|
> 如果用十进制,每根导线需要精确区分 10 种不同的电压(0V、1V、2V……9V)。电路稍微受到干扰,电压抖动一点,数字就读错了。而二进制只需区分「高电压」和「低电压」两种情况,极难出错,制造成本也低得多。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、正数好说,负数怎么办?
|
||||||
|
|
||||||
|
表示 `5` 很简单,写成 `0101` 就行。但 `-5` 呢?
|
||||||
|
|
||||||
|
计算机存数字时,会分配固定长度的空间,比如 **8位**(8个 0 或 1)。科学家想出了一个办法:
|
||||||
|
|
||||||
|
> **把最左边(最高位)那一位专门用来表示正负,叫"符号位":**
|
||||||
|
>
|
||||||
|
> - `0` → 正数
|
||||||
|
> - `1` → 负数
|
||||||
|
|
||||||
|
剩下的 7 位用来表示数值的大小。
|
||||||
|
|
||||||
|
这就是最朴素的方案——**原码**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、原码:最直观的方案
|
||||||
|
|
||||||
|
### 规则
|
||||||
|
|
||||||
|
- **正数:** 符号位写 `0`,后面写数值的二进制。
|
||||||
|
- **负数:** 符号位写 `1`,后面写数值**绝对值**的二进制。
|
||||||
|
|
||||||
|
### 例子(8位)
|
||||||
|
|
||||||
|
| 十进制 | 原码 |
|
||||||
|
|:----:|:-----------:|
|
||||||
|
| `+5` | `0000 0101` |
|
||||||
|
| `-5` | `1000 0101` |
|
||||||
|
| `+0` | `0000 0000` |
|
||||||
|
| `-0` | `1000 0000` |
|
||||||
|
|
||||||
|
直观,容易理解!但……
|
||||||
|
|
||||||
|
### 原码的两个大麻烦
|
||||||
|
|
||||||
|
**麻烦一:零有两个写法**
|
||||||
|
|
||||||
|
`+0` 写成 `0000 0000`,`-0` 写成 `1000 0000`,但它们其实是同一个数 `0`!这会让计算机很困惑——判断一个数是不是 `0` 时,要检查两种情况。
|
||||||
|
|
||||||
|
**麻烦二:加减法会算错**
|
||||||
|
|
||||||
|
用原码做 `5 + (-5)`,把两个原码直接相加:
|
||||||
|
|
||||||
|
```
|
||||||
|
0000 0101 (+5 的原码)
|
||||||
|
+ 1000 0101 (-5 的原码)
|
||||||
|
-----------
|
||||||
|
1000 1010 ← 这是 -10 的原码,答案错了!
|
||||||
|
```
|
||||||
|
|
||||||
|
正确答案应该是 `0`,结果算出了 `-10`。这意味着计算机必须专门写一套特殊的判断逻辑来处理负数加法,硬件电路会变得很复杂。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、反码:改进的尝试
|
||||||
|
|
||||||
|
### 规则
|
||||||
|
|
||||||
|
- **正数:** 反码与原码**完全相同**。
|
||||||
|
- **负数:** 符号位保持 `1` 不变,其余 7 位**全部翻转**(`0` 变 `1`,`1` 变 `0`)。
|
||||||
|
|
||||||
|
### 例子(8位)
|
||||||
|
|
||||||
|
| 十进制 | 原码 | 反码 |
|
||||||
|
|:----:|:-----------:|:-----------:|
|
||||||
|
| `+5` | `0000 0101` | `0000 0101` |
|
||||||
|
| `-5` | `1000 0101` | `1111 1010` |
|
||||||
|
|
||||||
|
### 用反码再试试 `5 + (-5)`
|
||||||
|
|
||||||
|
```
|
||||||
|
0000 0101 (+5 的反码)
|
||||||
|
+ 1111 1010 (-5 的反码)
|
||||||
|
-----------
|
||||||
|
1111 1111 ← 这是反码,对应的真值是 -0
|
||||||
|
```
|
||||||
|
|
||||||
|
结果是 `1111 1111`,这个反码代表 `-0`。虽然不是理想的 `0000 0000`,但至少方向对了——运算逻辑有所改进。
|
||||||
|
|
||||||
|
### 反码还剩下的问题
|
||||||
|
|
||||||
|
零**依然有两种写法**:
|
||||||
|
|
||||||
|
- `0000 0000` = `+0` 的反码
|
||||||
|
- `1111 1111` = `-0` 的反码
|
||||||
|
|
||||||
|
问题没有根本解决,只是往前走了一步。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、补码:现代计算机真正使用的方案
|
||||||
|
|
||||||
|
### 规则
|
||||||
|
|
||||||
|
- **正数:** 补码与原码**完全相同**。
|
||||||
|
- **负数:** 先求反码,再在最末位**加 1**。
|
||||||
|
|
||||||
|
### 一步步求 `-5` 的补码(8位)
|
||||||
|
|
||||||
|
```
|
||||||
|
第一步:写出 -5 的原码
|
||||||
|
1000 0101
|
||||||
|
|
||||||
|
第二步:符号位不变,其余位取反(得到反码)
|
||||||
|
1111 1010
|
||||||
|
|
||||||
|
第三步:反码末位加 1(得到补码)
|
||||||
|
1111 1010
|
||||||
|
+ 1
|
||||||
|
-----------
|
||||||
|
1111 1011 ← 这就是 -5 的补码
|
||||||
|
```
|
||||||
|
|
||||||
|
### 例子(8位)
|
||||||
|
|
||||||
|
| 十进制 | 原码 | 反码 | 补码 |
|
||||||
|
|:----:|:-----------:|:-----------:|:-------------:|
|
||||||
|
| `+5` | `0000 0101` | `0000 0101` | `0000 0101` |
|
||||||
|
| `-5` | `1000 0101` | `1111 1010` | `1111 1011` |
|
||||||
|
| `+0` | `0000 0000` | `0000 0000` | `0000 0000` |
|
||||||
|
| `-0` | `1000 0000` | `1111 1111` | `0000 0000` ✓ |
|
||||||
|
|
||||||
|
> **神奇的事情:** `-0` 的反码 `1111 1111` 加 `1`,得到 `1 0000 0000`,共 9 位!但我们只保留 8 位,最高位的 `1` 自动丢弃,结果就是 `0000 0000`。这样正负零合并为同一个编码了!
|
||||||
|
|
||||||
|
### 用补码验证 `5 + (-5)`
|
||||||
|
|
||||||
|
```
|
||||||
|
0000 0101 (+5 的补码)
|
||||||
|
+ 1111 1011 (-5 的补码)
|
||||||
|
-----------
|
||||||
|
1 0000 0000 ← 产生了进位,但只保留低 8 位
|
||||||
|
|
||||||
|
结果 = 0000 0000 = 0 ✓ 正确!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 再验证 `7 + (-3)`(答案应为 4)
|
||||||
|
|
||||||
|
```
|
||||||
|
0000 0111 (+7 的补码)
|
||||||
|
+ 1111 1101 (-3 的补码)
|
||||||
|
-----------
|
||||||
|
1 0000 0100 ← 低 8 位是 0000 0100 = +4 ✓
|
||||||
|
```
|
||||||
|
|
||||||
|
补码让**减法可以用加法来做**,计算机只需要一个加法器就够了,硬件设计大大简化!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、三种编码方式总结
|
||||||
|
|
||||||
|
| | 原码 | 反码 | 补码 |
|
||||||
|
|:------------:|:-----------:|:-------------:|:---------------:|
|
||||||
|
| **正数规则** | 符号位 0 + 绝对值 | 同原码 | 同原码 |
|
||||||
|
| **负数规则** | 符号位 1 + 绝对值 | 原码各位取反(符号位除外) | 反码 + 1 |
|
||||||
|
| **零的表示** | 两种(正零/负零) | 两种(正零/负零) | 一种(唯一) |
|
||||||
|
| **能否直接做加减法** | ✗ 不能 | 部分改善 | ✓ 可以 |
|
||||||
|
| **8位表示范围** | -127 ~ +127 | -127 ~ +127 | **-128 ~ +127** |
|
||||||
|
|
||||||
|
> **补码能多表示一个负数**,原因是消灭了 `-0`,空出来的编码 `1000 0000` 就用来表示 `-128`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、补码的"逆运算":已知补码求原来的数
|
||||||
|
|
||||||
|
如果拿到一个**负数的补码**,怎么还原成十进制?
|
||||||
|
|
||||||
|
**方法:对补码再做一次"取反加一"即可。**
|
||||||
|
|
||||||
|
**例:** 已知某数的 8 位补码为 `1111 1011`,求它是多少?
|
||||||
|
|
||||||
|
```
|
||||||
|
第一步:末位加 1(取反加一 = 再做一次补码运算)
|
||||||
|
1111 1011 →(取反)→ 1000 0100 →(加1)→ 1000 0101
|
||||||
|
|
||||||
|
第二步:读结果:符号位为 1(负数),数值部分为 0000 0101 = 5
|
||||||
|
|
||||||
|
结论:1111 1011 这个补码表示的是 -5。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、用 C++ 实现原码、反码、补码的输出
|
||||||
|
|
||||||
|
学完了手算方法,我们来用 C++ 写一个程序,让计算机自动完成这些步骤。
|
||||||
|
|
||||||
|
### 核心思路
|
||||||
|
|
||||||
|
用一个长度为 8 的 `int` 数组来表示 8 位二进制,**下标 0 存最低位,下标 7 存符号位(最高位)**。
|
||||||
|
|
||||||
|
- **原码**:先把绝对值不断除以 2 取余数,填入数组;再把下标 7 设为符号位(正数为 0,负数为 1)。
|
||||||
|
- **反码**:正数与原码相同;负数把除符号位以外的每一位取反(`0` 变 `1`,`1` 变 `0`,即 `1 - bit`)。
|
||||||
|
- **补码**:正数与原码相同;负数在反码的基础上加 1,用"逐位进位"模拟加法。
|
||||||
|
|
||||||
|
### 代码
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// 将非负整数 n(0~127)的二进制填入数组 bits[]
|
||||||
|
// bits[0] 是最低位,bits[7] 是最高位(符号位)
|
||||||
|
void fillBits(int n, int bits[8]) {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
bits[i] = n % 2;
|
||||||
|
n = n / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印 8 位数组,格式为 "XXXX XXXX"(高位在前)
|
||||||
|
void printBits(int bits[8]) {
|
||||||
|
for (int i = 7; i >= 0; i--) {
|
||||||
|
cout << bits[i];
|
||||||
|
if (i == 4) cout << " ";
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出整数 n(范围 -127 ~ 127)的原码、反码、补码
|
||||||
|
void showCodes(int n) {
|
||||||
|
int original[8]; // 原码
|
||||||
|
int inverse[8]; // 反码
|
||||||
|
int complement[8]; // 补码
|
||||||
|
|
||||||
|
if (n >= 0) {
|
||||||
|
fillBits(n, original);
|
||||||
|
// 正数:三种编码完全相同
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
inverse[i] = original[i];
|
||||||
|
complement[i] = original[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 原码:绝对值的二进制,符号位设为 1
|
||||||
|
fillBits(-n, original);
|
||||||
|
original[7] = 1;
|
||||||
|
|
||||||
|
// 反码:符号位不变,其余位取反
|
||||||
|
inverse[7] = 1;
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
inverse[i] = 1 - original[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 补码:反码加 1,模拟手动进位
|
||||||
|
int carry = 1;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int sum = inverse[i] + carry;
|
||||||
|
complement[i] = sum % 2;
|
||||||
|
carry = sum / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "原码: "; printBits(original);
|
||||||
|
cout << "反码: "; printBits(inverse);
|
||||||
|
cout << "补码: "; printBits(complement);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int n;
|
||||||
|
cin >> n;
|
||||||
|
|
||||||
|
if (n < -127 || n > 127) {
|
||||||
|
cout << "请输入 -127 到 127 之间的整数!" << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "n = " << n << endl;
|
||||||
|
showCodes(n);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行示例
|
||||||
|
|
||||||
|
输入 `-5`,输出:
|
||||||
|
|
||||||
|
```
|
||||||
|
n = -5
|
||||||
|
原码: 1000 0101
|
||||||
|
反码: 1111 1010
|
||||||
|
补码: 1111 1011
|
||||||
|
```
|
||||||
|
|
||||||
|
输入 `5`,输出:
|
||||||
|
|
||||||
|
```
|
||||||
|
n = 5
|
||||||
|
原码: 0000 0101
|
||||||
|
反码: 0000 0101
|
||||||
|
补码: 0000 0101
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键代码讲解
|
||||||
|
|
||||||
|
**① `fillBits`:把整数转为二进制数组**
|
||||||
|
|
||||||
|
```
|
||||||
|
n = 5:
|
||||||
|
5 % 2 = 1 → bits[0] = 1, n = 5/2 = 2
|
||||||
|
2 % 2 = 0 → bits[1] = 0, n = 2/2 = 1
|
||||||
|
1 % 2 = 1 → bits[2] = 1, n = 1/2 = 0
|
||||||
|
其余位全为 0
|
||||||
|
```
|
||||||
|
|
||||||
|
这就是"短除法"的代码版本,与手算完全一致。
|
||||||
|
|
||||||
|
**② `inverse[i] = 1 - original[i]`:取反**
|
||||||
|
|
||||||
|
- `original[i]` 是 0 时,`1 - 0 = 1`;
|
||||||
|
- `original[i]` 是 1 时,`1 - 1 = 0`。
|
||||||
|
|
||||||
|
不需要任何特殊符号,普通减法就能完成翻转。
|
||||||
|
|
||||||
|
**③ 反码加 1 的进位模拟**
|
||||||
|
|
||||||
|
```
|
||||||
|
假设反码是 1111 1010,加 1:
|
||||||
|
carry = 1
|
||||||
|
i=0: 0+1=1, bits=1, carry=0
|
||||||
|
i=1: 1+0=1, bits=1, carry=0
|
||||||
|
...(carry 已为 0,后续不变)
|
||||||
|
结果:1111 1011 ← 这正是 -5 的补码
|
||||||
|
```
|
||||||
|
|
||||||
|
用 `sum % 2` 取本位,`sum / 2` 取进位,完全模拟了手算的竖式加法。
|
||||||
|
|
||||||
|
> **想一想:** 为什么范围限制在 `-127` 到 `127`,而不包括 `-128`?
|
||||||
|
> 因为 `-128` 没有合法的 8 位**原码**(8 位原码最多表示 `-127`),只有**补码**能表示它(`1000 0000`)。你可以尝试修改代码,增加对 `-128` 的特殊处理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、练习题
|
||||||
|
|
||||||
|
### 【第一组】进制转换热身
|
||||||
|
|
||||||
|
1. 将十进制 `13` 转换成二进制。
|
||||||
|
2. 将十进制 `25` 转换成二进制。
|
||||||
|
3. 将二进制 `0001 0110` 转换成十进制。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第二组】求原码
|
||||||
|
|
||||||
|
用**8位**二进制写出下列各数的原码:
|
||||||
|
|
||||||
|
4. `+9`
|
||||||
|
5. `-9`
|
||||||
|
6. `+20`
|
||||||
|
7. `-20`
|
||||||
|
8. `+0` 和 `-0` 的原码分别是什么?它们相同吗?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第三组】求反码
|
||||||
|
|
||||||
|
用**8位**二进制写出下列各数的反码(需要先写原码再推导):
|
||||||
|
|
||||||
|
9. `+9`
|
||||||
|
10. `-9`
|
||||||
|
11. `-20`
|
||||||
|
12. `-1`(提示:`1` 的原码是 `0000 0001`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第四组】求补码
|
||||||
|
|
||||||
|
用**8位**二进制写出下列各数的补码:
|
||||||
|
|
||||||
|
13. `+9`
|
||||||
|
14. `-9`
|
||||||
|
15. `-20`
|
||||||
|
16. `-1`
|
||||||
|
17. `-128`(这道题有些特别,想想为什么原码方法不好用?)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第五组】补码反推原值
|
||||||
|
|
||||||
|
已知以下 8 位补码,请判断符号并求出对应的十进制数:
|
||||||
|
|
||||||
|
18. `0000 1010`
|
||||||
|
19. `1111 1110`
|
||||||
|
20. `1111 0000`
|
||||||
|
21. `1000 0000`(提示:这是补码范围内的特殊值)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第六组】补码加法验证
|
||||||
|
|
||||||
|
用补码计算以下各题,并验证结果正确(用 8 位,溢出位丢弃):
|
||||||
|
|
||||||
|
22. `6 + (-2)`
|
||||||
|
23. `(-6) + (-2)`
|
||||||
|
24. `10 + (-10)`
|
||||||
|
25. `(-1) + 1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第七组】思考题
|
||||||
|
|
||||||
|
26. 为什么计算机不直接使用原码来做加减法?用 `3 + (-3)` 举例说明原码的问题。
|
||||||
|
27. 8 位补码最多能表示多少个不同的整数?范围是多少?
|
||||||
|
28. 如果把 8 位扩展到 **16 位**,用补码表示整数,范围是多少?(规律:$n$ 位补码的范围是 $-2^{n-1}$ 到 $2^{n-1}-1$)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考答案
|
||||||
|
|
||||||
|
**第一组**
|
||||||
|
|
||||||
|
1. `1101`
|
||||||
|
2. `1 1001`(即 `0001 1001`)
|
||||||
|
3. `16 + 4 + 2 = 22`
|
||||||
|
|
||||||
|
**第二组**
|
||||||
|
|
||||||
|
4. `0000 1001`
|
||||||
|
5. `1000 1001`
|
||||||
|
6. `0001 0100`
|
||||||
|
7. `1001 0100`
|
||||||
|
8. 分别是 `0000 0000` 和 `1000 0000`,**不相同**。
|
||||||
|
|
||||||
|
**第三组**
|
||||||
|
|
||||||
|
9. `0000 1001`(正数不变)
|
||||||
|
10. `1111 0110`
|
||||||
|
11. `1110 1011`
|
||||||
|
12. `1111 1110`
|
||||||
|
|
||||||
|
**第四组**
|
||||||
|
|
||||||
|
13. `0000 1001`
|
||||||
|
14. `1111 0111`
|
||||||
|
15. `1110 1100`
|
||||||
|
16. `1111 1111`
|
||||||
|
17. `-128` 的 8 位补码为 `1000 0000`(这是规定值,因为补码多表示了这一个负数)
|
||||||
|
|
||||||
|
**第五组**
|
||||||
|
|
||||||
|
18. 符号位 `0`,正数,`= +10`
|
||||||
|
19. 取反加一:`0000 0001 + 1 = 0000 0010`,`= -2`
|
||||||
|
20. 取反加一:`0000 1111 + 1 = 0001 0000`,`= -16`
|
||||||
|
21. `-128`(特殊规定值)
|
||||||
|
|
||||||
|
**第六组**
|
||||||
|
|
||||||
|
22. `0000 0110 + 1111 1110 = 0000 0100 = +4` ✓
|
||||||
|
23. `1111 1010 + 1111 1110 = 1111 1000 = -8` ✓
|
||||||
|
24. `0000 1010 + 1111 0110 = 0000 0000 = 0` ✓
|
||||||
|
25. `1111 1111 + 0000 0001 = 0000 0000 = 0` ✓
|
||||||
|
|
||||||
|
**第七组**
|
||||||
|
|
||||||
|
26. `3 + (-3)` 用原码:`0000 0011 + 1000 0011 = 1000 0110 = -6`,答案错误。
|
||||||
|
27. $2^8 = 256$ 个,范围 `-128` 到 `+127`。
|
||||||
|
28. 范围是 `-32768` 到 `+32767`(即 $-2^{15}$ 到 $2^{15}-1$)。
|
||||||
595
web/doc/3.2 二进制运算.md
Normal file
595
web/doc/3.2 二进制运算.md
Normal file
@@ -0,0 +1,595 @@
|
|||||||
|
# 3.2 二进制运算
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、为什么还要学“二进制运算”?
|
||||||
|
|
||||||
|
在上一节里,我们已经知道了计算机用二进制存数,也知道了补码能把减法变成加法。接下来要学的是:
|
||||||
|
|
||||||
|
- 按位与(AND)
|
||||||
|
- 按位或(OR)
|
||||||
|
- 按位非(NOT)
|
||||||
|
- 按位异或(XOR)
|
||||||
|
- 左移(`<<`)
|
||||||
|
- 右移(`>>`)
|
||||||
|
- 二进制加法
|
||||||
|
- 二进制减法
|
||||||
|
|
||||||
|
这些运算不是“冷知识”,而是程序里常见的底层工具:权限开关、状态压缩、快速比较、数据校验都会用到。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、先记住 6 个位运算
|
||||||
|
|
||||||
|
设两个二进制位分别是 $a$ 和 $b$,每一位只可能是 0 或 1。
|
||||||
|
|
||||||
|
### 1. 按位与 AND(符号 `&`)
|
||||||
|
|
||||||
|
规则:只有两位都为 1,结果才是 1。
|
||||||
|
|
||||||
|
| a | b | a & b |
|
||||||
|
|:---:|:---:|:-----:|
|
||||||
|
| 0 | 0 | 0 |
|
||||||
|
| 0 | 1 | 0 |
|
||||||
|
| 1 | 0 | 0 |
|
||||||
|
| 1 | 1 | 1 |
|
||||||
|
|
||||||
|
可以理解为“都同意才通过”。
|
||||||
|
|
||||||
|
### 2. 按位或 OR(符号 `|`)
|
||||||
|
|
||||||
|
规则:只要有一位是 1,结果就是 1。
|
||||||
|
|
||||||
|
| a | b | a \| b |
|
||||||
|
|:---:|:---:|:------:|
|
||||||
|
| 0 | 0 | 0 |
|
||||||
|
| 0 | 1 | 1 |
|
||||||
|
| 1 | 0 | 1 |
|
||||||
|
| 1 | 1 | 1 |
|
||||||
|
|
||||||
|
可以理解为“有人同意就通过”。
|
||||||
|
|
||||||
|
### 3. 按位非 NOT(符号 `~`,一元运算)
|
||||||
|
|
||||||
|
规则:0 变 1,1 变 0。
|
||||||
|
|
||||||
|
| a | ~a |
|
||||||
|
|:---:|:---:|
|
||||||
|
| 0 | 1 |
|
||||||
|
| 1 | 0 |
|
||||||
|
|
||||||
|
### 4. 按位异或 XOR(符号 `^`)
|
||||||
|
|
||||||
|
规则:两位不同为 1,相同为 0。
|
||||||
|
|
||||||
|
| a | b | a ^ b |
|
||||||
|
|:---:|:---:|:-----:|
|
||||||
|
| 0 | 0 | 0 |
|
||||||
|
| 0 | 1 | 1 |
|
||||||
|
| 1 | 0 | 1 |
|
||||||
|
| 1 | 1 | 0 |
|
||||||
|
|
||||||
|
可以理解为“不同就亮灯”。
|
||||||
|
|
||||||
|
### 5. 左移(符号 `<<`)
|
||||||
|
|
||||||
|
规则:`x << k` 表示所有位向左移动 $k$ 位,右侧补 0。
|
||||||
|
|
||||||
|
- 在不溢出的情况下,数值相当于乘 $2^k$。
|
||||||
|
- 固定 32 位时,左边移出去的高位会被丢弃。
|
||||||
|
|
||||||
|
例:8 位下 `00101101 << 2 = 10110100`。
|
||||||
|
|
||||||
|
### 6. 右移(符号 `>>`)
|
||||||
|
|
||||||
|
规则:`x >> k` 表示所有位向右移动 $k$ 位。
|
||||||
|
|
||||||
|
- 对无符号数,左侧补 0。
|
||||||
|
- 对有符号数,很多语言会做“算术右移”(补符号位),所以课堂里先按无符号来理解更稳妥。
|
||||||
|
|
||||||
|
例:8 位下 `00101101 >> 3 = 00000101`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、C++ 里位运算的优先级(一定要加括号)
|
||||||
|
|
||||||
|
下面按 cppreference 的顺序,只保留“和计算最相关”的部分(第 5-7、9-15 级):
|
||||||
|
|
||||||
|
| 优先级(高 -> 低) | 运算符 | 含义 | |
|
||||||
|
|:-----------:|:----------------- |:----- |:---:|
|
||||||
|
| 5 | `*` `/` `%` | 乘、除、模 | |
|
||||||
|
| 6 | `+` `-` | 加、减 | |
|
||||||
|
| 7 | `<<` `>>` | 左移、右移 | |
|
||||||
|
| 9 | `<` `<=` `>` `>=` | 关系比较 | |
|
||||||
|
| 10 | `==` `!=` | 相等比较 | |
|
||||||
|
| 11 | `&` | 按位与 | |
|
||||||
|
| 12 | `^` | 按位异或 | |
|
||||||
|
| 13 | `\|` | 按位或 | |
|
||||||
|
| 14 | `&&` | 逻辑与 | |
|
||||||
|
| 15 | `\|\|` | 逻辑或 | |
|
||||||
|
|
||||||
|
看 5 个典型例子:
|
||||||
|
|
||||||
|
1) `a + b << 1`
|
||||||
|
- 实际等价于 `(a + b) << 1`
|
||||||
|
- 不是 `a + (b << 1)`
|
||||||
|
2) `x | y & z`
|
||||||
|
- 实际等价于 `x | (y & z)`
|
||||||
|
- 不是 `(x | y) & z`
|
||||||
|
3) `a ^ b & c`
|
||||||
|
- 实际等价于 `a ^ (b & c)`
|
||||||
|
- 不是 `(a ^ b) & c`
|
||||||
|
4) `x + y > z`
|
||||||
|
- 实际等价于 `(x + y) > z`
|
||||||
|
- 不是 `x + (y > z)`
|
||||||
|
5) `a & b == 0`
|
||||||
|
- 实际等价于 `a & (b == 0)`(因为 `==` 高于 `&`)
|
||||||
|
- 想判断“按位与结果是否为 0”,应写成 `(a & b) == 0`
|
||||||
|
|
||||||
|
课堂建议:只要一个表达式里混用了两类以上运算符,就主动加括号,别赌记忆。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、位串上的运算:逐位独立进行
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
a = 00101101
|
||||||
|
b = 00010111
|
||||||
|
```
|
||||||
|
|
||||||
|
逐位计算:
|
||||||
|
|
||||||
|
```
|
||||||
|
a & b = 00000101
|
||||||
|
a | b = 00111111
|
||||||
|
a ^ b = 00111010
|
||||||
|
~a = 11010010
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:`~a` 的结果长度和机器位数有关。课堂里我们常固定成 8 位或 32 位来讨论。
|
||||||
|
|
||||||
|
### 竖式写法示范(像小学列竖式)
|
||||||
|
|
||||||
|
把高位写在左边、低位写在右边,同一列对齐后逐列运算。
|
||||||
|
|
||||||
|
1) 按位与 `&`
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 0 1 0 1 1 0 1 (a)
|
||||||
|
& 0 0 0 1 0 1 1 1 (b)
|
||||||
|
----------------------
|
||||||
|
0 0 0 0 0 1 0 1 (a & b)
|
||||||
|
```
|
||||||
|
|
||||||
|
2) 按位或 `|`
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 0 1 0 1 1 0 1 (a)
|
||||||
|
| 0 0 0 1 0 1 1 1 (b)
|
||||||
|
----------------------
|
||||||
|
0 0 1 1 1 1 1 1 (a | b)
|
||||||
|
```
|
||||||
|
|
||||||
|
3) 按位异或 `^`
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 0 1 0 1 1 0 1 (a)
|
||||||
|
^ 0 0 0 1 0 1 1 1 (b)
|
||||||
|
----------------------
|
||||||
|
0 0 1 1 1 0 1 0 (a ^ b)
|
||||||
|
```
|
||||||
|
|
||||||
|
4) 左移与右移也可按“横向挪位”理解
|
||||||
|
|
||||||
|
```text
|
||||||
|
a : 0 0 1 0 1 1 0 1
|
||||||
|
a << 2 : 1 0 1 1 0 1 0 0 (左移两格,右侧补 0)
|
||||||
|
a >> 3 : 0 0 0 0 0 1 0 1 (右移三格,左侧补 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、二进制加法与减法
|
||||||
|
|
||||||
|
### 1. 二进制加法
|
||||||
|
|
||||||
|
和十进制竖式一样,也是“本位求和 + 进位”。
|
||||||
|
|
||||||
|
单个位相加规则:
|
||||||
|
|
||||||
|
- $0+0=0$,进位 0
|
||||||
|
- $0+1=1$,进位 0
|
||||||
|
- $1+0=1$,进位 0
|
||||||
|
- $1+1=0$,进位 1
|
||||||
|
|
||||||
|
如果再加上原来的进位,就变成三数相加。
|
||||||
|
|
||||||
|
竖式例子(8 位):`00101101 + 00010111`
|
||||||
|
|
||||||
|
```text
|
||||||
|
进位: 0 0 1 1 1 1 1
|
||||||
|
0 0 1 0 1 1 0 1
|
||||||
|
+ 0 0 0 1 0 1 1 1
|
||||||
|
----------------------
|
||||||
|
0 1 0 0 0 1 0 0
|
||||||
|
```
|
||||||
|
|
||||||
|
可让学生从最右列开始,逐列写“本列结果位”和“向左进位”。
|
||||||
|
|
||||||
|
### 2. 二进制减法
|
||||||
|
|
||||||
|
可以按“借位法”逐位减,也可以用补码思想把减法变加法。
|
||||||
|
|
||||||
|
在本章练习里,统一按 **32 位无符号整数** 处理:
|
||||||
|
|
||||||
|
- 超过 $2^{32}-1$ 的高位进位丢弃
|
||||||
|
- 不足 0 时按 32 位环绕(相当于加上 $2^{32}$)
|
||||||
|
|
||||||
|
竖式例子(8 位):`00101101 - 00010111`
|
||||||
|
|
||||||
|
```text
|
||||||
|
借位: 0 0 1 1 1 1 0
|
||||||
|
0 0 1 0 1 1 0 1
|
||||||
|
- 0 0 0 1 0 1 1 1
|
||||||
|
----------------------
|
||||||
|
0 0 0 1 0 1 1 0
|
||||||
|
```
|
||||||
|
|
||||||
|
从最右列开始,若不够减就向左借 1(借 1 相当于当前位加 2)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、C++:用数组模拟按位运算与加减
|
||||||
|
|
||||||
|
下面给一份课堂版代码:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
const int LEN = 32;
|
||||||
|
|
||||||
|
// 把无符号整数转成二进制数组:bits[0] 是最低位
|
||||||
|
void toBits(unsigned int x, int bits[]) {
|
||||||
|
for (int i = 0; i < LEN; i++) {
|
||||||
|
bits[i] = x % 2;
|
||||||
|
x /= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把二进制数组转回无符号整数
|
||||||
|
unsigned int toUInt(const int bits[]) {
|
||||||
|
unsigned int x = 0;
|
||||||
|
for (int i = LEN - 1; i >= 0; i--) {
|
||||||
|
x = x * 2 + bits[i];
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印 32 位二进制串(高位在前)
|
||||||
|
void printBits(const int bits[]) {
|
||||||
|
for (int i = LEN - 1; i >= 0; i--) {
|
||||||
|
cout << bits[i];
|
||||||
|
}
|
||||||
|
cout << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
void bitAnd(const int a[], const int b[], int c[]) {
|
||||||
|
for (int i = 0; i < LEN; i++) c[i] = a[i] & b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void bitOr(const int a[], const int b[], int c[]) {
|
||||||
|
for (int i = 0; i < LEN; i++) c[i] = a[i] | b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void bitXor(const int a[], const int b[], int c[]) {
|
||||||
|
for (int i = 0; i < LEN; i++) c[i] = a[i] ^ b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void bitNot(const int a[], int c[]) {
|
||||||
|
for (int i = 0; i < LEN; i++) c[i] = 1 - a[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 32 位无符号加法:溢出进位自动丢弃
|
||||||
|
void addBits(const int a[], const int b[], int c[]) {
|
||||||
|
int carry = 0;
|
||||||
|
for (int i = 0; i < LEN; i++) {
|
||||||
|
int sum = a[i] + b[i] + carry;
|
||||||
|
c[i] = sum % 2;
|
||||||
|
carry = sum / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 32 位无符号减法:逐位借位
|
||||||
|
void subBits(const int a[], const int b[], int c[]) {
|
||||||
|
int borrow = 0;
|
||||||
|
for (int i = 0; i < LEN; i++) {
|
||||||
|
int cur = a[i] - b[i] - borrow;
|
||||||
|
if (cur >= 0) {
|
||||||
|
c[i] = cur;
|
||||||
|
borrow = 0;
|
||||||
|
} else {
|
||||||
|
c[i] = cur + 2;
|
||||||
|
borrow = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
unsigned int A, B;
|
||||||
|
cin >> A >> B;
|
||||||
|
|
||||||
|
int a[LEN], b[LEN], c[LEN];
|
||||||
|
toBits(A, a);
|
||||||
|
toBits(B, b);
|
||||||
|
|
||||||
|
cout << "A = "; printBits(a);
|
||||||
|
cout << "B = "; printBits(b);
|
||||||
|
|
||||||
|
bitAnd(a, b, c);
|
||||||
|
cout << "A AND B= "; printBits(c);
|
||||||
|
|
||||||
|
bitOr(a, b, c);
|
||||||
|
cout << "A OR B= "; printBits(c);
|
||||||
|
|
||||||
|
bitXor(a, b, c);
|
||||||
|
cout << "A XOR B= "; printBits(c);
|
||||||
|
|
||||||
|
bitNot(a, c);
|
||||||
|
cout << "NOT A = "; printBits(c);
|
||||||
|
|
||||||
|
addBits(a, b, c);
|
||||||
|
cout << "A + B = " << toUInt(c) << '\n';
|
||||||
|
|
||||||
|
subBits(a, b, c);
|
||||||
|
cout << "A - B = " << toUInt(c) << '\n';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、练习题
|
||||||
|
|
||||||
|
### 【第一组】按位运算热身(进阶版,8 位)
|
||||||
|
|
||||||
|
已知:
|
||||||
|
|
||||||
|
- `a = 00101101`
|
||||||
|
- `b = 00010111`
|
||||||
|
1. 求 `a & b`。
|
||||||
|
2. 求 `a | b`。
|
||||||
|
3. 求 `a ^ b`。
|
||||||
|
4. 求 `~a`。
|
||||||
|
5. 求 `~b`。
|
||||||
|
6. 求 `(a & b) ^ a`。
|
||||||
|
7. 求 `(a | b) ^ b`。
|
||||||
|
8. 求 `(a ^ b) & a`。
|
||||||
|
9. 求 `(a ^ b) | b`。
|
||||||
|
10. 求 `(~a) & b`。
|
||||||
|
11. 求 `(~b) | a`。
|
||||||
|
12. 求 `a & (~b)`。
|
||||||
|
13. 求 `(a | b) & (a ^ b)`。
|
||||||
|
|
||||||
|
再设:
|
||||||
|
|
||||||
|
- `c = 01011000`
|
||||||
|
- `d = 00110101`
|
||||||
|
14. 求 `c & d`。
|
||||||
|
15. 求 `c | d`。
|
||||||
|
16. 求 `c ^ d`。
|
||||||
|
17. 求 `(c ^ d) ^ c`。
|
||||||
|
18. 求 `(c & d) | (c ^ d)`。
|
||||||
|
19. 求 `~(c ^ d)`。
|
||||||
|
20. 求 `(~c) ^ d`。
|
||||||
|
|
||||||
|
再做移位:
|
||||||
|
|
||||||
|
21. 求 `a << 1`(8 位)。
|
||||||
|
22. 求 `a << 3`(8 位)。
|
||||||
|
23. 求 `a >> 2`(8 位)。
|
||||||
|
24. 求 `b >> 1`(8 位)。
|
||||||
|
25. 求 `(a << 2) & 0b11111111`(8 位掩码保留)。
|
||||||
|
26. 求 `(b << 1) ^ (a >> 2)`(8 位)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第二组】真值与规律(先判断,再写一句理由)
|
||||||
|
|
||||||
|
27. 对任意位串 `x`,是否总有 `x ^ x == 0`?
|
||||||
|
28. 对任意位串 `x`,是否总有 `x & x == x`?
|
||||||
|
29. 对任意位串 `x`,是否总有 `x | x == x`?
|
||||||
|
30. 对任意位串 `x`,是否总有 `x ^ 0 == x`?
|
||||||
|
31. 对任意位串 `x`,是否总有 `x ^ FULL_MASK == ~x`?(其中 `FULL_MASK` 指整个位宽全 1)
|
||||||
|
32. 对任意位串 `x`,是否总有 `(~x) & x == 0`?
|
||||||
|
33. 对任意位串 `x`,是否总有 `(~x) | x == FULL_MASK`?
|
||||||
|
34. 对任意非负整数 `x`,是否总有 `(x << 1) == 2 * x`?
|
||||||
|
35. 对任意非负整数 `x`,是否总有 `(x >> 1) == x / 2`(整除)?
|
||||||
|
36. 若 `x` 是 2 的幂,是否总有 `x > 0 && (x & (x - 1)) == 0`?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第三组】加减法进阶(8 位,丢弃溢出位)
|
||||||
|
|
||||||
|
37. 计算:`00000101 + 00000110`
|
||||||
|
38. 计算:`11111111 + 00000001`
|
||||||
|
39. 计算:`00001010 - 00000011`
|
||||||
|
40. 计算:`00000000 - 00000001`
|
||||||
|
41. 计算:`10000000 + 10000000`
|
||||||
|
42. 计算:`01111111 + 00000001`
|
||||||
|
43. 计算:`01010101 + 00110011`
|
||||||
|
44. 计算:`00100000 - 00011111`
|
||||||
|
45. 计算:`00010000 - 00100000`
|
||||||
|
46. 计算:`10101010 - 01010101`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第四组】逆向构造题(更像算法题)
|
||||||
|
|
||||||
|
设 `a = 11001010`,求一个 8 位 `x`,使得:
|
||||||
|
|
||||||
|
47. `a & x == 10001000`(若有多解,写出任意一个)
|
||||||
|
48. `a | x == 11101110`(若有多解,写出任意一个)
|
||||||
|
49. `a ^ x == 01100111`(写唯一解)
|
||||||
|
|
||||||
|
再设 `u = 00110110`,`v = 00000110`:
|
||||||
|
|
||||||
|
50. 构造一个 8 位 `y`,使得 `y & u == v`。
|
||||||
|
51. 判断是否存在 8 位 `z`,使得 `z | u == v`,若存在给出一个,若不存在说明原因。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 【第五组】常用位技巧(探究题,重点)
|
||||||
|
|
||||||
|
这一组不要先背公式,先按题目把例子算出来,再归纳。
|
||||||
|
|
||||||
|
#### A. 探究“最低位 1”(lowbit)
|
||||||
|
|
||||||
|
对下面每个 `x`,先写出二进制,再计算 `-x`(按补码),最后算出 `x & (-x)`:
|
||||||
|
|
||||||
|
52. `x = 12`
|
||||||
|
|
||||||
|
53. `x = 40`
|
||||||
|
|
||||||
|
54. `x = 44`
|
||||||
|
|
||||||
|
55. `x = 72`
|
||||||
|
|
||||||
|
56. 观察 52-55 的结果,归纳一句话:`x & (-x)` 保留了 `x` 的哪一部分?
|
||||||
|
|
||||||
|
#### B. 探究“去掉最低位 1”
|
||||||
|
|
||||||
|
对下面每个 `x`,计算 `x - 1`,再算 `x & (x - 1)`:
|
||||||
|
|
||||||
|
57. `x = 12`
|
||||||
|
|
||||||
|
58. `x = 40`
|
||||||
|
|
||||||
|
59. `x = 44`
|
||||||
|
|
||||||
|
60. `x = 72`
|
||||||
|
|
||||||
|
61. 观察 57-60 的结果,归纳一句话:`x & (x-1)` 对二进制位做了什么变化?
|
||||||
|
|
||||||
|
#### C. 由例子得到“2 的幂”判定
|
||||||
|
|
||||||
|
分别计算 `x & (x - 1)`,并记录是否为 0:
|
||||||
|
|
||||||
|
62. `x = 1`
|
||||||
|
|
||||||
|
63. `x = 2`
|
||||||
|
|
||||||
|
64. `x = 4`
|
||||||
|
|
||||||
|
65. `x = 8`
|
||||||
|
|
||||||
|
66. `x = 3`
|
||||||
|
|
||||||
|
67. `x = 6`
|
||||||
|
|
||||||
|
68. 根据 62-67,总结:当 `x > 0` 时,什么条件等价于“`x` 是 2 的幂”?
|
||||||
|
|
||||||
|
#### D. 应用题(把结论用起来)
|
||||||
|
|
||||||
|
69. 不用循环,判断 `64` 是否是 2 的幂,并写出关键表达式值。
|
||||||
|
70. 不用循环,判断 `72` 是否是 2 的幂,并写出关键表达式值。
|
||||||
|
71. 设 `x = 01011000`(8 位),先求 `x & (-x)`,再给出最低位 1 的位置(最低位记第 0 位)。
|
||||||
|
72. 设 `x = 00101000`(8 位),先求 `x & (-x)`,再给出最低位 1 的位置(最低位记第 0 位)。
|
||||||
|
73. 设 `x = 10000000`(8 位),先求 `x & (-x)`,再给出最低位 1 的位置(最低位记第 0 位)。
|
||||||
|
74. 从 `x = 90` 开始,反复执行 `x = x & (x - 1)` 直到变成 0,需要几步?由此得到 `90` 的二进制中有几个 1?
|
||||||
|
75. 给定偶数 `n`,写出一个表达式快速判断它是否能被 4 整除,并说明对应的“二进制末尾特征”。
|
||||||
|
|
||||||
|
## 参考答案
|
||||||
|
|
||||||
|
**第一组**
|
||||||
|
|
||||||
|
1. `00000101`
|
||||||
|
2. `00111111`
|
||||||
|
3. `00111010`
|
||||||
|
4. `11010010`
|
||||||
|
5. `11101000`
|
||||||
|
6. `00101000`
|
||||||
|
7. `00101000`
|
||||||
|
8. `00101000`
|
||||||
|
9. `00111111`
|
||||||
|
10. `00010010`
|
||||||
|
11. `11101101`
|
||||||
|
12. `00101000`
|
||||||
|
13. `00111010`
|
||||||
|
14. `00010000`
|
||||||
|
15. `01111101`
|
||||||
|
16. `01101101`
|
||||||
|
17. `00110101`
|
||||||
|
18. `01111101`
|
||||||
|
19. `10010010`
|
||||||
|
20. `10010010`
|
||||||
|
21. `01011010`
|
||||||
|
22. `01101000`
|
||||||
|
23. `00001011`
|
||||||
|
24. `00001011`
|
||||||
|
25. `10110100`
|
||||||
|
26. `00100101`
|
||||||
|
|
||||||
|
**第二组**
|
||||||
|
|
||||||
|
27. 是。
|
||||||
|
28. 是。
|
||||||
|
29. 是。
|
||||||
|
30. 是。
|
||||||
|
31. 是(全 1 掩码下逐位翻转)。
|
||||||
|
32. 是。
|
||||||
|
33. 是。
|
||||||
|
34. 在不溢出的前提下是。
|
||||||
|
35. 是(无符号或非负整数语境下)。
|
||||||
|
36. 是。
|
||||||
|
|
||||||
|
**第三组**
|
||||||
|
|
||||||
|
37. `00001011`(十进制 11)
|
||||||
|
38. `00000000`(十进制 0)
|
||||||
|
39. `00000111`(十进制 7)
|
||||||
|
40. `11111111`(十进制 255)
|
||||||
|
41. `00000000`(十进制 0)
|
||||||
|
42. `10000000`(十进制 128)
|
||||||
|
43. `10001000`(十进制 136)
|
||||||
|
44. `00000001`(十进制 1)
|
||||||
|
45. `11110000`(十进制 240)
|
||||||
|
46. `01010101`(十进制 85)
|
||||||
|
|
||||||
|
**第四组(给一种可行解)**
|
||||||
|
|
||||||
|
47. 可取 `x = 10001100`。
|
||||||
|
48. 可取 `x = 01100100`。
|
||||||
|
49. 唯一解 `x = 10101101`。
|
||||||
|
50. 可取 `y = 11100110`(只要在 `u` 为 1 的位上与 `v` 对齐即可,`u` 为 0 的位任意)。
|
||||||
|
51. 不存在,因为按位或不会把 `u` 中的 1 变成 0,而 `u = 00110110` 在第 5、4、2、1 位已有 1,但 `v = 00000110` 在第 5、4 位是 0。
|
||||||
|
|
||||||
|
**第五组**
|
||||||
|
|
||||||
|
52. `12 = 1100`,`-12`(8 位)是 `11110100`,`12 & (-12) = 0100`(十进制 4)
|
||||||
|
53. `40 = 101000`,`40 & (-40) = 001000`(十进制 8)
|
||||||
|
54. `44 = 101100`,`44 & (-44) = 000100`(十进制 4)
|
||||||
|
55. `72 = 1001000`,`72 & (-72) = 0001000`(十进制 8)
|
||||||
|
56. 结论:`x & (-x)` 只保留最低位的 1,其余位清零(即 lowbit)
|
||||||
|
57. `12 & 11 = 1100 & 1011 = 1000`(十进制 8)
|
||||||
|
58. `40 & 39 = 101000 & 100111 = 100000`(十进制 32)
|
||||||
|
59. `44 & 43 = 101100 & 101011 = 101000`(十进制 40)
|
||||||
|
60. `72 & 71 = 1001000 & 1000111 = 1000000`(十进制 64)
|
||||||
|
61. 结论:`x & (x-1)` 会把最低位 1 消掉,且更低位保持为 0
|
||||||
|
62. `1 & 0 = 0`
|
||||||
|
63. `2 & 1 = 0`
|
||||||
|
64. `4 & 3 = 0`
|
||||||
|
65. `8 & 7 = 0`
|
||||||
|
66. `3 & 2 = 2`(非 0)
|
||||||
|
67. `6 & 5 = 4`(非 0)
|
||||||
|
68. 结论:`x > 0 && (x & (x - 1)) == 0` 当且仅当 `x` 是 2 的幂
|
||||||
|
69. `64 & 63 = 0`,所以是 2 的幂
|
||||||
|
70. `72 & 71 = 64`,非 0,所以不是 2 的幂
|
||||||
|
71. lowbit 是 `00001000`(8),最低位 1 在第 3 位
|
||||||
|
72. lowbit 是 `00001000`(8),最低位 1 在第 3 位
|
||||||
|
73. lowbit 是 `10000000`(128),最低位 1 在第 7 位
|
||||||
|
74. `90 -> 88 -> 80 -> 64 -> 0`,共 4 步,所以有 4 个 1
|
||||||
|
75. 可用 `(n & 3) == 0`,对应二进制末两位为 `00`
|
||||||
1222
web/doc/3.3 高阶位运算.md
Normal file
1222
web/doc/3.3 高阶位运算.md
Normal file
File diff suppressed because it is too large
Load Diff
58
web/docs.php
Normal file
58
web/docs.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
require_once('./include/db_info.inc.php');
|
||||||
|
require_once('./include/setlang.php');
|
||||||
|
require_once('./include/const.inc.php');
|
||||||
|
|
||||||
|
$docs_dir = __DIR__ . '/doc';
|
||||||
|
$doc_entries = array();
|
||||||
|
|
||||||
|
if (is_dir($docs_dir)) {
|
||||||
|
$doc_files = glob($docs_dir . '/*.md');
|
||||||
|
if ($doc_files !== false) {
|
||||||
|
sort($doc_files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
|
foreach ($doc_files as $doc_file) {
|
||||||
|
$doc_entries[] = array(
|
||||||
|
'file' => basename($doc_file),
|
||||||
|
'title' => preg_replace('/\.md$/i', '', basename($doc_file))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected_file = '';
|
||||||
|
if (isset($_GET['file'])) {
|
||||||
|
$selected_file = basename($_GET['file']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($selected_file) && !empty($doc_entries)) {
|
||||||
|
$selected_file = $doc_entries[0]['file'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected_title = '文档';
|
||||||
|
$selected_markdown = '';
|
||||||
|
|
||||||
|
if (!empty($selected_file)) {
|
||||||
|
$selected_path = realpath($docs_dir . DIRECTORY_SEPARATOR . $selected_file);
|
||||||
|
$docs_root = realpath($docs_dir);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$selected_path !== false &&
|
||||||
|
$docs_root !== false &&
|
||||||
|
strpos($selected_path, $docs_root . DIRECTORY_SEPARATOR) === 0 &&
|
||||||
|
is_file($selected_path) &&
|
||||||
|
preg_match('/\.md$/i', $selected_path)
|
||||||
|
) {
|
||||||
|
$selected_markdown = file_get_contents($selected_path);
|
||||||
|
if ($selected_markdown === false) {
|
||||||
|
$selected_markdown = '';
|
||||||
|
} else {
|
||||||
|
$selected_markdown = preg_replace('/^\xEF\xBB\xBF/', '', $selected_markdown);
|
||||||
|
}
|
||||||
|
$selected_title = preg_replace('/\.md$/i', '', basename($selected_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$show_title = $selected_title . ' - ' . $OJ_NAME;
|
||||||
|
|
||||||
|
require('template/' . $OJ_TEMPLATE . '/docs.php');
|
||||||
|
?>
|
||||||
208
web/template/syzoj/docs.php
Normal file
208
web/template/syzoj/docs.php
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<?php include('template/' . $OJ_TEMPLATE . '/header.php'); ?>
|
||||||
|
<script src="include/vditor-adapter.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.docs-shell {
|
||||||
|
display: flex;
|
||||||
|
gap: 18px;
|
||||||
|
min-height: calc(100vh - 180px);
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar,
|
||||||
|
.docs-content {
|
||||||
|
background: rgba(255, 255, 255, 0.88);
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.08);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar {
|
||||||
|
width: 300px;
|
||||||
|
padding: 18px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar-header {
|
||||||
|
padding: 0 20px 14px;
|
||||||
|
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar-header p {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-nav {
|
||||||
|
padding: 12px;
|
||||||
|
max-height: calc(100vh - 260px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-nav a {
|
||||||
|
display: block;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #1f2937;
|
||||||
|
transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-nav a:hover,
|
||||||
|
.docs-nav a:focus {
|
||||||
|
background: rgba(37, 99, 235, 0.1);
|
||||||
|
color: #1d4ed8;
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-nav a.active {
|
||||||
|
background: linear-gradient(135deg, #1d4ed8, #2563eb);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 12px 30px rgba(37, 99, 235, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-empty {
|
||||||
|
padding: 14px 20px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 22px 28px 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content-header span {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-render {
|
||||||
|
min-height: 320px;
|
||||||
|
color: #111827;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-render .vditor-reset {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-render table tr td {
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 120px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-render table th {
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
min-width: 120px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: #eff6ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.docs-shell {
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-nav {
|
||||||
|
max-height: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content {
|
||||||
|
padding: 18px 18px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-content-header span {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="docs-shell">
|
||||||
|
<aside class="docs-sidebar">
|
||||||
|
<div class="docs-sidebar-header">
|
||||||
|
<h2>文档目录</h2>
|
||||||
|
<p>左侧选择 Markdown 文档,右侧查看渲染结果。</p>
|
||||||
|
</div>
|
||||||
|
<div class="docs-nav">
|
||||||
|
<?php if (empty($doc_entries)) { ?>
|
||||||
|
<div class="docs-empty">当前 doc 目录下还没有 Markdown 文档。</div>
|
||||||
|
<?php } else { ?>
|
||||||
|
<?php foreach ($doc_entries as $doc_entry) { ?>
|
||||||
|
<a
|
||||||
|
href="docs.php?file=<?php echo rawurlencode($doc_entry['file']); ?>"
|
||||||
|
class="<?php if ($doc_entry['file'] === $selected_file) echo 'active'; ?>"
|
||||||
|
>
|
||||||
|
<?php echo htmlspecialchars($doc_entry['title'], ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="docs-content">
|
||||||
|
<div class="docs-content-header">
|
||||||
|
<h1><?php echo htmlspecialchars($selected_title, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||||||
|
<span><?php echo empty($selected_file) ? '未选择文档' : htmlspecialchars($selected_file, ENT_QUOTES, 'UTF-8'); ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($selected_markdown)) { ?>
|
||||||
|
<div id="doc-render" class="docs-render md"><?php echo htmlspecialchars($selected_markdown, ENT_NOQUOTES, 'UTF-8'); ?></div>
|
||||||
|
<?php } else { ?>
|
||||||
|
<div class="ui info message">没有可显示的文档内容。</div>
|
||||||
|
<?php } ?>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
if (!$('#doc-render').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HustOJVditor.renderMarkdownBlocks('#doc-render', {
|
||||||
|
useTextContent: true
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error('Failed to render docs markdown.', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include('template/' . $OJ_TEMPLATE . '/footer.php'); ?>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
$url=basename($_SERVER['REQUEST_URI']);
|
$url=basename(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
|
||||||
$dir=basename(getcwd());
|
$dir=basename(getcwd());
|
||||||
if($dir=="discuss3") $path_fix="../";
|
if($dir=="discuss3") $path_fix="../";
|
||||||
else $path_fix="";
|
else $path_fix="";
|
||||||
@@ -154,6 +154,8 @@
|
|||||||
href="cppreference.php">
|
href="cppreference.php">
|
||||||
<i class="help icon"></i><?php echo $MSG_CPPREFRENCE?>
|
<i class="help icon"></i><?php echo $MSG_CPPREFRENCE?>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="item <?php if ($url=="docs.php") echo "active";?>"
|
||||||
|
href="<?php echo $path_fix?>docs.php"><i class="book icon"></i> 文档</a>
|
||||||
<!-- 排名 -->
|
<!-- 排名 -->
|
||||||
<a class="desktop-only item <?php if ($url=="ranklist.php") echo "active";?> "
|
<a class="desktop-only item <?php if ($url=="ranklist.php") echo "active";?> "
|
||||||
href="<?php echo $path_fix?>ranklist.php"><i class="signal icon"></i> <?php echo $MSG_RANKLIST?></a>
|
href="<?php echo $path_fix?>ranklist.php"><i class="signal icon"></i> <?php echo $MSG_RANKLIST?></a>
|
||||||
|
|||||||
Reference in New Issue
Block a user