feat(courseware): add protected PPTX viewer

This commit is contained in:
2026-09-03 18:06:03 +08:00
parent fd53bab639
commit 7495d40d4d
28 changed files with 1823 additions and 4117 deletions
+16
View File
@@ -0,0 +1,16 @@
# 课件怎么放
学生打开导航里的「课件」,点进去就能直接翻 PPT。不需要导出图片,也不需要后台上传。
把 PowerPoint 文件放进**仓库根目录**的 `courseware/`,用 git 提交:
```
courseware/2.2 数组的插入与删除.pptx
courseware/3.1 原码反码补码.pptx
```
目录里每一个 `.pptx` 就是一课,文件名就是标题。网页不能直接访问这个目录;播放器通过 `courseware-file.php` 读取文件。
请用 **`.pptx`**PowerPoint 2007 及以后)。旧的 `.ppt` 请在 PowerPoint 里「另存为 → pptx」再提交。
动画、特殊字体、SmartArt 在网页里可能和上课投影不完全一样,但可以翻页看内容。
Binary file not shown.
+145
View File
@@ -0,0 +1,145 @@
<?php
require_once __DIR__ . '/../web/include/courseware.inc.php';
function cw_assert($condition, $message) {
if (!$condition) {
fwrite(STDERR, "FAIL: " . $message . PHP_EOL);
exit(1);
}
}
function cw_rmdir_tree($dir) {
if (!is_dir($dir)) {
return;
}
$items = scandir($dir);
if ($items === false) {
return;
}
foreach ($items as $name) {
if ($name === '.' || $name === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $name;
if (is_dir($path) && !is_link($path)) {
cw_rmdir_tree($path);
} else {
@unlink($path);
}
}
@rmdir($dir);
}
$src = file_get_contents(__DIR__ . '/../web/include/courseware.inc.php');
cw_assert($src !== false, 'courseware.inc.php is readable');
cw_assert(stripos($src, 'ZipArchive') === false, 'courseware.inc.php does not use ZipArchive');
cw_assert(!function_exists('courseware_count_slides'), 'slide counting helper is gone');
$fixture = realpath(__DIR__ . '/../courseware/示例课件.pptx');
cw_assert($fixture !== false && is_file($fixture), 'example pptx exists in repo-root courseware/');
$tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'hustoj-cw-' . bin2hex(random_bytes(4));
cw_assert(mkdir($tmp), 'create temp root');
cw_assert(copy($fixture, $tmp . DIRECTORY_SEPARATOR . '数组 插入.pptx'), 'copy chinese spaced pptx');
cw_assert(copy($fixture, $tmp . DIRECTORY_SEPARATOR . '2 item.pptx'), 'copy 2 item pptx');
cw_assert(copy($fixture, $tmp . DIRECTORY_SEPARATOR . '10 item.pptx'), 'copy 10 item pptx');
cw_assert(copy($fixture, $tmp . DIRECTORY_SEPARATOR . '3 item.pptx'), 'copy 3 item pptx');
cw_assert(copy($fixture, $tmp . DIRECTORY_SEPARATOR . 'Upper.PPTX'), 'copy uppercase extension');
cw_assert(copy($fixture, $tmp . DIRECTORY_SEPARATOR . 'Mixed.Pptx'), 'copy mixed-case extension');
file_put_contents($tmp . DIRECTORY_SEPARATOR . 'notes.txt', 'ignore');
file_put_contents($tmp . DIRECTORY_SEPARATOR . 'old.ppt', 'ignore');
$GLOBALS['_courseware_root'] = $tmp;
cw_assert(!courseware_is_slug(''), 'reject empty slug');
cw_assert(!courseware_is_slug('.'), 'reject dot slug');
cw_assert(!courseware_is_slug('..'), 'reject parent slug');
cw_assert(!courseware_is_slug('../etc'), 'reject parent path slug');
cw_assert(!courseware_is_slug('a/b'), 'reject slash slug');
cw_assert(!courseware_is_slug('a\\b'), 'reject backslash slug');
cw_assert(!courseware_is_slug("a\0b"), 'reject NUL slug');
cw_assert(courseware_is_slug('2.2-array'), 'accept dotted slug');
cw_assert(courseware_is_slug('数组 插入'), 'accept unicode spaced slug');
cw_assert(courseware_build_deck('') === null, 'empty slug is null');
cw_assert(courseware_build_deck("a\0b") === null, 'NUL slug is null');
cw_assert(courseware_build_deck('../etc') === null, 'traversal slug is null');
cw_assert(courseware_build_deck('a/b') === null, 'slash slug is null');
cw_assert(courseware_build_deck('missing') === null, 'missing deck is null');
cw_assert(courseware_build_deck('notes') === null, 'non-pptx is ignored');
cw_assert(courseware_build_deck('old') === null, 'ppt without x is ignored');
$zh = courseware_build_deck('数组 插入');
cw_assert($zh !== null, 'chinese spaced pptx loads');
cw_assert($zh['title'] === '数组 插入', 'title keeps spaces and unicode');
cw_assert($zh['filename'] === '数组 插入.pptx', 'filename is actual directory entry');
cw_assert($zh['url'] === 'courseware-file.php?deck=' . rawurlencode('数组 插入'), 'url uses file endpoint');
cw_assert(!array_key_exists('page_count', $zh), 'does not fake page count');
$scan_names = array();
foreach (scandir($tmp) as $entry_name) {
if ($entry_name === '.' || $entry_name === '..') {
continue;
}
$scan_names[$entry_name] = true;
}
$upper = courseware_build_deck('Upper');
cw_assert($upper !== null, 'uppercase PPTX extension is accepted');
cw_assert(isset($scan_names[$upper['filename']]), 'uppercase deck uses an actual directory entry');
cw_assert(strtolower($upper['filename']) === 'upper.pptx', 'uppercase slug maps without concatenating .pptx');
$mixed = courseware_build_deck('Mixed');
cw_assert($mixed !== null, 'mixed-case Pptx extension is accepted');
cw_assert(isset($scan_names[$mixed['filename']]), 'mixed-case deck uses an actual directory entry');
cw_assert(strtolower($mixed['filename']) === 'mixed.pptx', 'mixed-case slug maps without concatenating .pptx');
$decks = courseware_list_decks();
$titles = array();
foreach ($decks as $deck) {
$titles[] = $deck['title'];
cw_assert(!array_key_exists('page_count', $deck), 'listed decks do not fake page count');
}
cw_assert(count($decks) === 6, 'only pptx files are listed');
cw_assert($titles === array('2 item', '3 item', '10 item', 'Mixed', 'Upper', '数组 插入'), 'natural sort of titles');
cw_assert(courseware_clamp_page($zh, 0) === 1, 'clamp low without page count');
cw_assert(courseware_clamp_page($zh, 9) === 9, 'unknown total keeps requested page');
cw_assert(courseware_clamp_page(array('page_count' => 3), 99) === 3, 'clamp high when count is known');
$outside = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'hustoj-cw-out-' . bin2hex(random_bytes(4));
cw_assert(mkdir($outside), 'create outside dir');
$secret = $outside . DIRECTORY_SEPARATOR . 'secret.pptx';
cw_assert(copy($fixture, $secret), 'copy secret pptx outside root');
cw_assert(!courseware_inside_root(realpath($secret), realpath($tmp)), 'outside realpath is rejected');
$escape = $tmp . DIRECTORY_SEPARATOR . 'escape.pptx';
$link_ok = @symlink($secret, $escape);
if ($link_ok) {
cw_assert(courseware_build_deck('escape') === null, 'out-of-root symlink is rejected');
} else {
fwrite(STDOUT, "SKIP symlink escape test (cannot create symlink)\n");
}
$rel_link = $tmp . DIRECTORY_SEPARATOR . 'relescape.pptx';
$rel_ok = @symlink('..' . DIRECTORY_SEPARATOR . basename($outside) . DIRECTORY_SEPARATOR . 'secret.pptx', $rel_link);
if (!$rel_ok) {
$rel_ok = @symlink(realpath($secret), $rel_link);
}
if ($rel_ok) {
cw_assert(courseware_build_deck('relescape') === null, 'relative out-of-root symlink is rejected');
} else {
fwrite(STDOUT, "SKIP relative symlink test (cannot create symlink)\n");
}
unset($GLOBALS['_courseware_root']);
$example = courseware_build_deck('示例课件');
cw_assert($example !== null, 'example pptx is present in default root');
cw_assert($example['filename'] === '示例课件.pptx', 'example uses actual filename');
cw_assert($example['url'] === 'courseware-file.php?deck=' . rawurlencode('示例课件'), 'example url uses file endpoint');
cw_assert(!array_key_exists('page_count', $example), 'example does not fake page count');
cw_rmdir_tree($tmp);
cw_rmdir_tree($outside);
fwrite(STDOUT, "OK courseware_test.php\n");
+53
View File
@@ -0,0 +1,53 @@
<?php
require_once('./include/db_info.inc.php');
require_once('./include/courseware.inc.php');
$method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
if ($method !== 'GET' && $method !== 'HEAD') {
header('Allow: GET, HEAD');
http_response_code(405);
exit;
}
if (isset($OJ_NEED_LOGIN) && $OJ_NEED_LOGIN && !isset($_SESSION[$OJ_NAME . '_' . 'user_id'])) {
http_response_code(401);
exit;
}
$slug = isset($_GET['deck']) ? strval($_GET['deck']) : '';
$found = courseware_resolve_pptx($slug);
if ($found === null) {
http_response_code(404);
exit;
}
$path = $found['path'];
$filename = $found['filename'];
$size = @filesize($path);
if ($size === false) {
http_response_code(404);
exit;
}
$fp = null;
if ($method !== 'HEAD') {
$fp = fopen($path, 'rb');
if ($fp === false) {
http_response_code(404);
exit;
}
}
$mime = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
header('Content-Type: ' . $mime);
header('Content-Length: ' . $size);
header('Content-Disposition: ' . courseware_content_disposition($filename));
header('X-Content-Type-Options: nosniff');
header('Cache-Control: private, no-store');
if ($method === 'HEAD') {
exit;
}
fpassthru($fp);
fclose($fp);
+22
View File
@@ -0,0 +1,22 @@
<?php
require_once('./include/db_info.inc.php');
require_once('./include/setlang.php');
require_once('./include/const.inc.php');
require_once('./include/courseware.inc.php');
$OJ_COURSEWARE_PAGE = true;
$decks = courseware_list_decks();
$selected_slug = isset($_GET['deck']) ? strval($_GET['deck']) : '';
$selected_deck = $selected_slug === '' ? null : courseware_build_deck($selected_slug);
$current_page = 1;
if ($selected_deck !== null) {
$current_page = courseware_clamp_page($selected_deck, isset($_GET['page']) ? $_GET['page'] : 1);
$show_title = $selected_deck['title'] . ' - ' . $OJ_NAME;
} else {
$selected_slug = '';
$show_title = $MSG_COURSEWARE . ' - ' . $OJ_NAME;
}
require('template/' . $OJ_TEMPLATE . '/courseware.php');
-451
View File
@@ -1,451 +0,0 @@
# 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
View File
@@ -1,482 +0,0 @@
# 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
View File
@@ -1,595 +0,0 @@
# 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 变 11 变 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`
File diff suppressed because it is too large Load Diff
-376
View File
@@ -1,376 +0,0 @@
# 4.0 流程图的画法与读法
---
## 一、这节课到底学什么?
很多同学觉得流程图难,不是因为图形多,而是因为总会犯下面几种错:
- 只盯着框看,不顺着箭头走。
- 只看条件,不看两条分支分别去哪。
- 看到回头箭头,没有意识到这是一段循环。
- 自己画图时,漏了初始化、漏了结束、漏了更新变量。
所以这份讲义不走“纯概念说明”路线,而是直接看图、读图、再学怎么手绘。
---
## 二、先看一张图,认识流程图里最常见的 4 种框
```mermaid
flowchart LR
A([开始]) --> B[/输入一个数 n/]
B --> C[把 n 加 1]
C --> D{n 是偶数吗?}
D -- 是 --> E[/输出 偶数/]
D -- 否 --> F[/输出 奇数/]
E --> G([结束])
F --> G
```
这张图先不用细抠题意,只要认识图形就行:
- 圆角框:开始、结束。
- 平行四边形:输入、输出。
- 长方形:处理步骤,也就是“做一件事”。
- 菱形:判断,要从这里分路。
你可以先把流程图想成“做事路线图”:
- 框,表示现在做什么。
- 箭头,表示下一步去哪。
> 读流程图时,真正重要的不是图画得漂不漂亮,而是你能不能顺着箭头走清楚。
---
## 三、示范 1:最基础的单判断流程图
题目:输入一个整数,判断它是偶数还是奇数。
```mermaid
flowchart TD
A([开始]) --> B[/输入 n/]
B --> C{n 能被 2 整除吗?}
C -- 是 --> D[/输出 偶数/]
C -- 否 --> E[/输出 奇数/]
D --> F([结束])
E --> F
```
读这张图时,顺序应该这样说:
1. 从“开始”出发。
2. 输入一个整数 n。
3. 判断 n 能不能被 2 整除。
4. 如果能,就输出“偶数”。
5. 如果不能,就输出“奇数”。
6. 结束。
这类图最容易错的地方只有一个:
> 判断框一定要看两条路,不能只看条件本身。
也就是说,你看到菱形时,要同时问自己两句:
- 条件成立,往哪走?
- 条件不成立,往哪走?
---
## 四、示范 2:看到回头箭头,就要想到“循环”
题目:输入 n,求 1 到 n 的和。
```mermaid
flowchart TD
A([开始]) --> B[/输入 n/]
B --> C[sum = 0, i = 1]
C --> D{i 还没超过 n 吗?}
D -- 否 --> E[/输出 sum/]
E --> F([结束])
D -- 是 --> G[sum = sum + i]
G --> H[i = i + 1]
H --> D
```
这张图里,最关键的是最后那条回头箭头:
- 它从后面回到了前面的判断框。
- 这说明有一段步骤要重复做。
- 这就是循环。
### 拿一组小数据,顺着图走一遍
设 n = 4。
| 现在的 i | 做完以后 sum | 接下来去哪? |
|:---:|:---:|:---|
| 1 | 1 | i 变成 2,回去继续判断 |
| 2 | 3 | i 变成 3,回去继续判断 |
| 3 | 6 | i 变成 4,回去继续判断 |
| 4 | 10 | i 变成 5,回去继续判断 |
| 5 | 不再累加 | 因为 5 已经超过 4,所以输出 10 |
这就是读流程图最稳的方法:
> 如果看图有点晕,就拿一组很小的数据,自己沿着箭头走一遍。
---
## 五、示范 3:有些流程会“提前停止”
题目:从 1 开始往上加,如果和第一次超过 20,就立刻输出并结束。
```mermaid
flowchart TD
A([开始]) --> B[/输入 n/]
B --> C[sum = 0, i = 1]
C --> D{i 还没超过 n 吗?}
D -- 否 --> E[/输出 sum/]
E --> F([结束])
D -- 是 --> G[sum = sum + i]
G --> H{sum 已经大于 20 吗?}
H -- 是 --> E
H -- 否 --> I[i = i + 1]
I --> D
```
这张图比上一张多了一个判断框。
它告诉你:
- 有时候流程不一定走到最下面才结束。
- 只要中途满足条件,就可以直接输出并结束。
### 为什么这类图容易读错?
因为很多同学会下意识地认为:
“图上有的步骤,都会执行。”
其实不是。
真正正确的想法是:
> 只有你顺着箭头真的能走到那一步,那一步才会执行。
比如 n = 10
- 1 + 2 + 3 + 4 + 5 = 15。
- 再加 6sum 变成 21。
- 这时已经大于 20,所以直接结束。
- 后面的 7、8、9、10 根本不会再处理。
---
## 六、示范 4:双重循环最容易把人绕晕
题目:老师给你一张 3 行 4 列的表,从左到右、从上到下寻找第一个 0,找到就立刻输出它的位置。
```mermaid
flowchart TD
A([开始]) --> B[row = 1]
B --> C{row 还在第 1 到第 3 行吗?}
C -- 否 --> D[/输出 没找到/]
D --> E([结束])
C -- 是 --> F[col = 1]
F --> G{col 还在第 1 到第 4 列吗?}
G -- 否 --> H[row = row + 1]
H --> C
G -- 是 --> I[/读当前位置的数 x/]
I --> J{x 是 0 吗?}
J -- 是 --> K[/输出 row 和 col/]
K --> E
J -- 否 --> L[col = col + 1]
L --> G
```
这一张图里,其实有两层重复:
- 外层:一行一行看。
- 内层:每一行里,一列一列看。
读双重循环时,一定要问清楚:
- 现在正在动的是哪一个变量?
- 这一条回边,是回到内层判断,还是回到外层判断?
- 找到答案以后,是结束一层,还是结束全部?
### 这题里有两种“结束”意思完全不同
第一种:一整行看完了。
- 这不是整张图结束。
- 只是 col 走完了。
- 所以下一步是 row 加 1,去下一行。
第二种:已经找到第一个 0。
- 这不是只结束当前一行。
- 而是整个过程直接输出并结束。
> 双重循环最容易错的地方,不是看不见箭头,而是分不清“结束一层”和“结束全部”。
---
## 七、示范 5:分支、循环、提前结束放在同一张图里
题目:输入 n,求 1 到 n 中所有 3 的倍数之和;如果和第一次超过 100,就立刻停止并输出。
```mermaid
flowchart TD
A([开始]) --> B[/输入 n/]
B --> C[sum = 0, i = 1]
C --> D{i 还没超过 n 吗?}
D -- 否 --> E[/输出 sum/]
E --> F([结束])
D -- 是 --> G{i 是 3 的倍数吗?}
G -- 是 --> H[sum = sum + i]
G -- 否 --> I[i = i + 1]
H --> J{sum 已经大于 100 吗?}
J -- 是 --> E
J -- 否 --> I
I --> D
```
这张图值得你重点观察 3 个地方:
1. 外面有一个循环,因为最后会回到“i 还没超过 n 吗”。
2. 中间有一个分支,因为要判断 i 是不是 3 的倍数。
3. 还有一个提前结束,因为 sum 超过 100 时可以直接停。
如果你能把这张图读顺,说明你已经不是只会看“最基础单流程”了。
---
## 八、自己手绘流程图,应该怎么下笔?
看到题目以后,不要一上来就急着画框。
更稳的顺序是下面这 6 步:
### 第 1 步:先把做法写成人话
例如:
1. 输入 n。
2. 令 sum = 0i = 1。
3. 如果 i 已经超过 n,就输出 sum。
4. 如果 i 是 3 的倍数,就把它加到 sum 里。
5. 如果 sum 已经超过 100,就输出 sum。
6. 否则 i 加 1,再继续。
如果这几句话都说不顺,流程图通常也画不顺。
### 第 2 步:先找变量
最常见的几类变量是:
- 输入变量:题目给你的数。
- 循环变量:决定“做到第几个”的变量。
- 结果变量:最后要输出的量。
- 标记变量:比如“是否找到”。
### 第 3 步:先搭大骨架
很多流程图,骨架其实都差不多:
开始 → 输入 → 初始化 → 判断 → 处理 → 更新 → 回到判断 → 输出 → 结束
先把主干摆出来,再补细节,图就不会乱。
### 第 4 步:每个判断框都要有两条路
判断框不能只画一条路。
你必须写清楚:
- 是,去哪。
- 否,去哪。
### 第 5 步:循环的回边,通常回到判断框
很多同学会把箭头直接接回处理中间某一步,这很容易画错。
更常见、更稳的画法是:
- 处理完一轮。
- 更新变量。
- 回到“是否继续”的判断框。
### 第 6 步:画完以后,用小数据手推
这一招非常重要。
你可以拿最小的几组数据来试:
- n = 1 会怎样?
- n = 0 会怎样?
- 第一次就满足结束条件会怎样?
如果你自己都推不顺,说明图还得改。
---
## 九、手绘时最容易犯的错误
下面这些错,课堂上特别常见:
1. 漏掉开始和结束。
2. 忘了初始化变量。
3. 判断框只画出一条路。
4. 循环变量没有更新,结果图会一直转圈。
5. 回边接错位置,没有回到判断框。
6. 双重循环里,没有分清是结束一层还是结束全部。
7. 一个框里塞太多话,别人读不清楚。
8. 箭头方向不清,让人只能靠猜。
---
## 十、画完之后,按这张检查单自查
1. 有没有明确的开始和结束?
2. 输入和输出有没有写清楚?
3. 初始化有没有漏掉?
4. 每个判断框是不是都有两条路?
5. 每个循环变量有没有更新?
6. 回头箭头是不是回到了合适的位置?
7. 如果有提前停止,是不是画清楚了?
8. 如果有双重循环,是不是分清了结束一层和结束全部?
9. 能不能拿一组小数据,从头走到尾?
只要这 9 条都能过,这张图一般就比较靠谱了。
---
## 十一、课堂练习
下面 3 题,不要求你写代码,先试着手绘流程图:
### 练习 1
输入 n,输出 1 到 n 中偶数的个数。
### 练习 2
从左到右读一串数,遇到 0 就停止,输出前面所有数的和。
### 练习 3
读一个 3 行 4 列的表,找到第一个大于 100 的数,输出它的位置;如果没有找到,就输出“没有”。
做这 3 题时,请先写“人话步骤”,再画图。
---
## 十二、这一节最该记住的几句话
1. 流程图不是背图形,而是按顺序表达做事步骤。
2. 读图时,最重要的是顺着箭头走。
3. 看到菱形,就要同时看“是”和“否”两条路。
4. 看到回头箭头,通常就要想到循环。
5. 不是图上写了什么都会执行,要看箭头能不能真的走到那一步。
6. 画图前先把步骤说清楚,画完后再拿小数据手推。
如果你能做到这 6 条,流程图这一关就算真正入门了。
-728
View File
@@ -1,728 +0,0 @@
# 4.1 自定义函数和递归
---
## 一、这一节到底学什么?
这一节不要求你一下子把函数和递归全背下来。
你只要先抓住下面 4 件事就够了:
1. 知道什么时候要把一段代码单独写成函数。
2. 会写最基础的自定义函数定义。
3. 能顺着流程图读懂“函数被调用时发生了什么”。
4. 初步看懂递归是怎样一层一层调用、再一层一层返回的。
整份讲义里,我们会尽量用能直接代数字的小例子来讲,不走太空的定义路线。
---
## 二、为什么要写函数,而不是一直往下写?
先看一种很常见的情况:
- 你要多次做同一件事。
- 这件事本身可以起一个名字。
- 把它拆出去以后,主程序会更清楚。
比如你要处理一张全班成绩表。
题目要求你完成下面这些事:
- 读入每个同学的语文、数学、英语成绩。
- 计算每个同学的总分。
- 计算每个同学的平均分。
- 判断每个同学是否及格。
- 最后输出全班总分前 3 名。
如果你完全不用函数,程序很可能会变成这样:
- 主程序里一大段是在算总分。
- 后面又来一大段是在算平均分。
- 再后面又来一大段是在判断是否及格。
- 如果输出前 3 名时还要反复比较、交换,主程序会更长。
这样写会有几个明显问题:
- 主程序会越来越像一团挤在一起的步骤,不容易看出主线。
- 同样的计算规则如果写了好几次,改错时很容易漏掉某一处。
- 过几天再回头看,你很难一下分清哪一段在“算分数”,哪一段在“判及格”,哪一段在“排前 3 名”。
这时如果把任务拆开:
- 写一个函数专门求总分。
- 写一个函数专门求平均分。
- 写一个函数专门判断是否及格。
主程序就会清楚很多。
所以你可以把“一段完成固定任务的代码”单独拿出来,给它起个名字,这就是函数。
你可以先把函数理解成:
> 一个能完成某项任务的小工具。
主程序需要它时,就“叫它来做事”。
---
## 三、最基础的函数长什么样?
最常见的写法是:
```cpp
()
{
}
```
先不用被名字吓住,直接把这 3 部分拆开看:
先配一个很具体的例子:
```cpp
int total_score(int chinese, int math, int english)
{
int sum = chinese + math + english;
return sum;
}
```
这个函数做的事非常明确:
- 给它语文、数学、英语 3 个分数。
- 它帮你算出总分。
- 最后把总分交回来。
### 1. 返回值类型
表示这个函数做完以后,要交回什么类型的结果。
例如:
- `int`:交回一个整数。
- `double`:交回一个小数。
- `bool`:交回真或假。
- `void`:不交回结果。
在刚才那个例子里:
- `total_score` 最后交回来的是总分。
- 总分通常是整数。
- 所以这里写 `int`
### 2. 函数名
就是这个函数的名字。
名字最好能让人一眼看出用途,比如:
- `add`
- `is_even`
- `sum_to_n`
如果是给六年级同学自己写,名字能看懂比“高大上”更重要。
像:
- `total_score`
- `print_line`
- `sum_to_n`
都比随便写成 `f1``abc` 更容易读。
### 3. 参数列表
表示调用这个函数时,要给它哪些数据。
例如前面的成绩函数:
```cpp
int total_score(int chinese, int math, int english)
```
意思就是:
- 这个函数叫 `total_score`
- 它需要 3 个整数,分别叫 `chinese``math``english`
- 它最后会返回一个整数,也就是总分。
如果把真实数字代进去,比如:
- 语文 92 分
- 数学 87 分
- 英语 95 分
那么这个函数的任务就是算:
$$
92 + 87 + 95 = 274
$$
---
## 四、函数的意义:同一件事,只写一次
先看一个场景。
你需要算 3 个同学各自的总分:
- 小明:语文 92,数学 87,英语 95
- 小红:语文 78,数学 85,英语 80
- 小刚:语文 88,数学 90,英语 76
如果不用函数,主程序会这样写:
```cpp
int sum1 = 92 + 87 + 95; // 小明的总分
int sum2 = 78 + 85 + 80; // 小红的总分
int sum3 = 88 + 90 + 76; // 小刚的总分
```
如果有 30 个同学,就要写 30 行。
而且"加 3 个数"这件事,每次都要重复写一遍。
这时可以定义一个函数,把"怎么加"这件事单独写好:
```cpp
int total_score(int chinese, int math, int english)
{
int sum = chinese + math + english;
return sum;
}
```
然后在主函数里,每个同学用一行搞定:
```cpp
int sum1 = total_score(92, 87, 95); // 小明的总分,得到 274
int sum2 = total_score(78, 85, 80); // 小红的总分,得到 243
int sum3 = total_score(88, 90, 76); // 小刚的总分,得到 254
```
"怎么算总分"只定义了一次,却被叫了 3 次。
这就是函数最核心的意义:
> 把一件事的做法写一次,想用多少次就叫多少次。
### 用流程图看清楚"调用"发生了什么
下面这张图展示的是:主函数叫了 `total_score` 两次,每次都跳出去执行函数,结束后再回来继续。
```mermaid
flowchart LR
subgraph M["主函数 main"]
A([开始]) --> B["准备小明的分数\n92, 87, 95"]
B --> C["调用①"]
C --> H["sum1 = 274"]
H --> I["准备小红的分数\n78, 85, 80"]
I --> J["调用②"]
J --> K["sum2 = 243"]
K --> L[/输出 sum1, sum2/]
L --> Z([结束])
end
subgraph F["函数 total_score(只写了一次)"]
D["收到参数"] --> E["sum = chinese + math + english"]
E --> G["return sum"]
end
C -- "传入 92,87,95" --> D
G -- "返回 274" --> H
J -- "传入 78,85,80" --> D
G -- "返回 243" --> K
```
注意流程图里的关键点:
- 每次调用,都会"跳进"函数那个框里执行。
- 函数执行完以后,再"跳回"主函数继续往下走。
- 函数本身的代码只写了一份,但被用了两次。
---
## 五、值传递到底是什么意思?
这是这一节最重要的基础概念。
看下面这组代码:
```cpp
int add_bonus(int score)
{
score = score + 10;
return score;
}
int main()
{
int math = 87;
int new_score = add_bonus(math);
}
```
很多同学第一眼会担心:
- 函数里把 `score` 改了,那外面的 `math` 会不会也变?
这一节里,答案是:
> 不会。
因为我们现在只讲值传递。
调用 `add(x, y)` 时,发生的是:
调用 `add_bonus(math)` 时,发生的是:
-`math` 当前的值 87 交给函数里的 `score`
- 函数内部拿到的是一份新的 87。
所以:
- `score``math` 不是同一个变量。
你可以把它理解成:
- 主程序把这个分数抄了一份给函数。
- 函数只是在自己的小房间里处理这份副本。
- 函数结束以后,主程序里的原变量不受影响。
### 用一组数据走一遍
`math = 87` 时:
- 进入函数时,`score = 87`
- 执行 `score = score + 10` 后,`score = 97`
- 返回 97。
但主函数里的:
- `math` 还是 87。
最后会变成:
- `math = 87`
- `new_score = 97`
也就是说:
- 原来的分数没被直接改掉。
- 只是函数帮你算出了“加 10 分以后”的新结果。
这一点一定要和“函数内部变量的变化”区分开。
---
## 六、函数调用时,流程图应该怎么看?
先看一个非常基础的例子:主函数调用 `total_score(chinese, math, english)`
```mermaid
flowchart TD
A([开始]) --> B["chinese = 92, math = 87, english = 95"]
B --> C["调用 total_score(chinese, math, english)"]
C --> D["把 92, 87, 95 交给函数里的 3 个参数"]
D --> E["sum = 92 + 87 + 95"]
E --> F["return 274"]
F --> G["ans 得到返回值"]
G --> H[/输出 ans/]
H --> I([结束])
```
这张图里最重要的是中间 3 步:
1. 调用函数。
2. 把实参的值交给形参。
3. 函数算完以后,把结果返回。
### 这里有两个名字要分清
- 实参:调用时真正传进去的东西,比如 `chinese``math``english`
- 形参:函数定义里接收数据的变量,比如 `chinese``math``english` 这 3 个参数名。
你不需要死背这两个词,但一定要明白它们不是同一个层面的名字。
---
## 七、把“主函数”和“被调用函数”分开看,会更清楚
如果你总觉得函数调用时会乱,可以把它想成两块:
- 主函数负责“发任务”。
- 被调用函数负责“做任务”。
看这张图:
```mermaid
flowchart LR
subgraph M[主函数 main]
A[准备 3 个分数] --> B["调用 total_score(...)"]
C[接住返回值 ans] --> D[/输出 ans/]
end
subgraph F[函数 total_score]
E[收到 3 个分数] --> F1[把 3 个分数加起来]
F1 --> G[返回总分]
end
B --> E
G --> C
```
这样看会更像真实过程:
- 主函数把任务送进去。
- `add` 做完再把结果送回来。
> 函数不是“贴在原地执行的一行代码”,而是一次完整的小过程。
---
## 八、什么时候用 `void` 函数?
不是所有函数都一定要“返回一个结果”。
比如下面这个函数:
```cpp
void print_line()
{
cout << "----------" << endl;
}
```
它的任务只是输出一行分隔线。
所以:
- 它做事。
- 但它不需要交回一个数。
这时就可以写成 `void`
你可以把它理解成:
> `void` 函数负责“做动作”,不负责“交答案”。
如果把它放进主函数里:
```cpp
print_line();
cout << "欢迎来到成绩统计程序" << endl;
print_line();
```
它的作用就很直观:
- 不是算分数。
- 不是算答案。
- 只是帮你把输出排得更整齐。
---
## 九、递归到底是什么?
递归只有一句核心定义:
> 函数在自己的函数体里调用自己。
第一次看到这句话,很多同学会立刻觉得:
- “那不是会一直套下去吗?”
所以学递归时,最关键的不是先背代码,而是先抓住两件事:
1. 一定要有“停止条件”。
2. 每次递归都要朝着停止条件靠近。
如果没有停止条件,函数就会没完没了地继续调用。
如果虽然有停止条件,但每次都没有更接近它,那也一样会出问题。
---
## 十、先看一个最简单的递归函数
题目:求 1 到 n 的和。
我们把它写成:
```cpp
int sum_to_n(int n)
{
if (n == 1)
{
return 1;
}
return n + sum_to_n(n - 1);
}
```
这段代码可以读成:
- 如果 `n == 1`,那答案就是 1,直接返回。
- 如果 `n > 1`,那么
`1 + 2 + ... + n`
就等于
`n + (1 + 2 + ... + (n - 1))`
所以它把原问题拆成了一个更小的问题:
- `sum_to_n(n)`
- 变成
- `n + sum_to_n(n - 1)`
这就是递归最常见的思路:
> 大问题,拆成一个同类但更小的问题。
你可以把它想成一堆叠起来的小卡片:
- 要算前 4 张卡片上的数字和。
- 可以先拿最上面这一张 4。
- 剩下的问题就变成“前 3 张卡片的和是多少”。
这样就从“4 张”慢慢变成“3 张”“2 张”“1 张”。
---
## 十一、递归流程图怎么画?
先看结构图:
```mermaid
flowchart TD
A(["进入 sum_to_n(n)"]) --> B{n == 1 吗?}
B -- 是 --> C[return 1]
B -- 否 --> D["计算 sum_to_n(n - 1)"]
D --> E[返回 n + 子问题结果]
C --> F([结束本层调用])
E --> F
```
这张图里,你必须抓住两条路:
- 一条是“直接结束”的路,也就是停止条件。
- 一条是“继续缩小问题”的路,也就是递归调用。
没有前者,停不下来。
没有后者,就不是递归。
---
## 十二、拿 `sum_to_n(4)` 手推一遍,最重要
只看代码,很多同学会晕。
这时最稳的方法就是:
> 拿一个很小的数,自己顺着调用过程走一遍。
### 第一步:往下调用
```text
sum_to_n(4)
= 4 + sum_to_n(3)
sum_to_n(3)
= 3 + sum_to_n(2)
sum_to_n(2)
= 2 + sum_to_n(1)
sum_to_n(1)
= 1
```
看到这里,终于碰到了停止条件。
### 第二步:往回返回
```text
sum_to_n(1) = 1
sum_to_n(2) = 2 + 1 = 3
sum_to_n(3) = 3 + 3 = 6
sum_to_n(4) = 4 + 6 = 10
```
所以最后答案是 10。
### 再用表格看一遍
| 这一层在算谁? | 它先等谁? | 等到以后自己返回多少? |
|:---:|:---:|:---:|
| `sum_to_n(4)` | 等 `sum_to_n(3)` | `4 + 6 = 10` |
| `sum_to_n(3)` | 等 `sum_to_n(2)` | `3 + 3 = 6` |
| `sum_to_n(2)` | 等 `sum_to_n(1)` | `2 + 1 = 3` |
| `sum_to_n(1)` | 不用再等 | `1` |
这张表很重要,因为它把“先等谁、再返回多少”写得很清楚。
---
## 十三、递归最容易绕晕的地方,不在“往下”,而在“往回”
很多同学能接受:
- `sum_to_n(4)` 去叫 `sum_to_n(3)`
- `sum_to_n(3)` 再去叫 `sum_to_n(2)`
但一到返回时就乱了。
原因通常是没有意识到:
> 每一层函数调用,都在等自己的“下一层”先把结果交回来。
也就是说:
- `sum_to_n(4)` 不能立刻算完。
- 它必须先等 `sum_to_n(3)` 返回。
- `sum_to_n(3)` 又得先等 `sum_to_n(2)`
- 一直等到最小那层先返回。
所以递归过程常常像两段:
1. 一层一层往下走。
2. 一层一层往上交答案。
---
## 十四、把递归看成“排队等结果”,会更好懂
再看一张图:
```mermaid
flowchart TD
A["sum_to_n(4)"] --> B["等待 sum_to_n(3)"]
B --> C["等待 sum_to_n(2)"]
C --> D["等待 sum_to_n(1)"]
D --> E["sum_to_n(1) 返回 1"]
E --> F["sum_to_n(2) 返回 3"]
F --> G["sum_to_n(3) 返回 6"]
G --> H["sum_to_n(4) 返回 10"]
```
这张图要表达的重点不是代码细节,而是“顺序”:
- 先不断往下等。
- 再从最底层开始往回交结果。
你甚至可以把它读成下面这几句话:
- 4 这一层先别急,先去问 3。
- 3 这一层也先别急,先去问 2。
- 2 这一层再去问 1。
- 1 最简单,直接回答:我是 1。
- 然后答案一层一层往回传。
如果你能把这张图说顺,说明你已经抓住递归最核心的运行过程了。
---
## 十五、值传递放到递归里,还是一样的
递归虽然看起来更复杂,但参数传递规则没有变。
例如:
- 调用 `sum_to_n(4)` 时,这一层的 `n` 是 4。
- 它再调用 `sum_to_n(3)` 时,下一层会有一个新的 `n`,值是 3。
- 再下一层调用 `sum_to_n(2)` 时,又会有一个新的 `n`,值是 2。
所以:
- 每一层都有自己的 `n`
- 它们不是同一个变量。
- 只是名字都叫 `n` 而已。
这也是值传递的一部分:
> 每次调用,都会得到这一层自己的那份参数值。
---
## 十六、写递归时,先问自己这两个问题
在真正动手写递归前,最好先问:
### 1. 什么时候停?
也就是停止条件是什么。
例如:
- `n == 1` 时停。
- `n == 0` 时停。
### 2. 怎么把问题变小?
也就是下一次调用时,参数怎么变化。
例如:
- `n - 1`
- `n / 2`
如果这两个问题答不出来,递归代码通常也写不稳。
---
## 十七、最常见的错误
下面这些错非常常见:
1. 写了递归调用,却没有停止条件。
2. 虽然写了停止条件,但永远到不了它。
3. 只会往下套,不会分析怎么往回返回。
4. 把“这一层的参数”和“下一层的参数”混成同一个东西。
5. 以为函数里的参数改了,主函数里的变量也会跟着改。
第 5 条在这一节里尤其要避免,因为本节只讲值传递。
---
## 十八、这一节最该记住的几句话
1. 函数就是把一段固定任务单独拿出来,起个名字反复用。
2. 最基础的函数定义要看清:返回值类型、函数名、参数列表。
3. 调用函数时,主程序是“发任务”,函数是“做任务”。
4. 本节只讲值传递,所以函数拿到的是一份新的值,不会直接改掉外面的原变量。
5. 递归就是函数调用自己。
6. 递归必须同时具备两件事:停止条件、缩小问题。
7. 读递归最稳的方法,是拿小数据手推,先看往下调用,再看往回返回。
---
## 十九、课堂练习
### 练习 1
写一个函数 `int square(int x)`,返回 `x * x`
要求:
- 先说清楚参数是什么。
- 再说清楚返回值是什么。
### 练习 2
主函数中有 `a = 6`,调用 `triple(a)`,其中函数内部把形参乘以 3。
问题:
- 函数返回什么?
- 主函数里的 `a` 会不会变?
### 练习 3
`sum_to_n(5)` 的递归调用过程手写出来,写清楚:
- 往下调用到了哪一层。
- 往回返回时每层结果是多少。
如果这 3 题都能自己说顺,这一节就算真正入门了。
+2 -70
View File
@@ -1,71 +1,3 @@
<?php
require_once('./include/db_info.inc.php');
require_once('./include/setlang.php');
require_once('./include/const.inc.php');
function normalize_doc_title($text) {
$text = trim((string)$text);
$text = preg_replace('/[\s\p{P}\p{S}]+/u', '', $text);
if (function_exists('mb_strtolower')) {
return mb_strtolower($text, 'UTF-8');
}
return strtolower($text);
}
$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 = '';
$show_selected_title = true;
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));
if (preg_match('/^\s*#\s+(.+)$/mu', $selected_markdown, $heading_match)) {
$show_selected_title = normalize_doc_title($heading_match[1]) !== normalize_doc_title($selected_title);
}
}
}
$show_title = $selected_title . ' - ' . $OJ_NAME;
require('template/' . $OJ_TEMPLATE . '/docs.php');
?>
header('Location: courseware.php', true, 302);
exit;
+225
View File
@@ -0,0 +1,225 @@
<?php
/**
* File-based courseware: one .pptx file per lesson in the repo-root courseware/ directory.
*/
if (!function_exists('courseware_root')) {
function courseware_root() {
if (isset($GLOBALS['_courseware_root']) && $GLOBALS['_courseware_root'] !== '') {
$resolved = realpath($GLOBALS['_courseware_root']);
return $resolved === false ? '' : $resolved;
}
$resolved = realpath(__DIR__ . '/../../courseware');
return $resolved === false ? '' : $resolved;
}
}
if (!function_exists('courseware_is_slug')) {
function courseware_is_slug($slug) {
if (!is_string($slug) || $slug === '' || $slug === '.' || $slug === '..') {
return false;
}
if (strpos($slug, "\0") !== false) {
return false;
}
if (strpos($slug, '/') !== false || strpos($slug, '\\') !== false) {
return false;
}
return true;
}
}
if (!function_exists('courseware_is_pptx_name')) {
function courseware_is_pptx_name($name) {
if (!is_string($name) || strlen($name) < 6) {
return false;
}
if (strpos($name, "\0") !== false || strpos($name, '/') !== false || strpos($name, '\\') !== false) {
return false;
}
return strtolower(substr($name, -5)) === '.pptx';
}
}
if (!function_exists('courseware_inside_root')) {
function courseware_inside_root($path, $root) {
$root_norm = rtrim(str_replace('\\', '/', $root), '/');
$path_norm = rtrim(str_replace('\\', '/', $path), '/');
if ($root_norm === '' || $path_norm === '') {
return false;
}
$insensitive = (DIRECTORY_SEPARATOR === '\\');
if ($insensitive) {
if (strcasecmp($path_norm, $root_norm) === 0) {
return true;
}
$prefix = $root_norm . '/';
return strncasecmp($path_norm, $prefix, strlen($prefix)) === 0;
}
return $path_norm === $root_norm || strpos($path_norm, $root_norm . '/') === 0;
}
}
if (!function_exists('courseware_is_absolute_path')) {
function courseware_is_absolute_path($path) {
if (!is_string($path) || $path === '') {
return false;
}
if ($path[0] === '/' || $path[0] === '\\') {
return true;
}
return (bool) preg_match('#^[A-Za-z]:[\\\\/]#', $path);
}
}
if (!function_exists('courseware_scan_pptx_entries')) {
function courseware_scan_pptx_entries() {
$root = courseware_root();
$found = array();
if ($root === '' || !is_dir($root)) {
return $found;
}
$entries = @scandir($root);
if ($entries === false) {
return $found;
}
foreach ($entries as $name) {
if ($name === '.' || $name === '..') {
continue;
}
if (!courseware_is_pptx_name($name)) {
continue;
}
$slug = substr($name, 0, -5);
if (!courseware_is_slug($slug)) {
continue;
}
if (!isset($found[$slug])) {
$found[$slug] = $name;
}
}
return $found;
}
}
if (!function_exists('courseware_link_stays_inside')) {
function courseware_link_stays_inside($raw, $root) {
if (!is_link($raw)) {
return true;
}
$link = @readlink($raw);
if ($link === false || strpos($link, "\0") !== false) {
return false;
}
$target = courseware_is_absolute_path($link)
? $link
: (dirname($raw) . DIRECTORY_SEPARATOR . $link);
$resolved_link = realpath($target);
if ($resolved_link === false || !courseware_inside_root($resolved_link, $root)) {
return false;
}
return true;
}
}
if (!function_exists('courseware_resolve_pptx')) {
function courseware_resolve_pptx($slug) {
if (!courseware_is_slug($slug)) {
return null;
}
$root = courseware_root();
if ($root === '') {
return null;
}
$entries = courseware_scan_pptx_entries();
if (!isset($entries[$slug])) {
return null;
}
$name = $entries[$slug];
$raw = $root . DIRECTORY_SEPARATOR . $name;
if (!courseware_link_stays_inside($raw, $root)) {
return null;
}
$resolved = realpath($raw);
if ($resolved === false || !is_file($resolved)) {
return null;
}
if (!courseware_inside_root($resolved, $root)) {
return null;
}
if (!courseware_is_pptx_name(basename($resolved)) && !courseware_is_pptx_name($name)) {
return null;
}
return array(
'slug' => $slug,
'title' => $slug,
'filename' => $name,
'path' => $resolved,
);
}
}
if (!function_exists('courseware_build_deck')) {
function courseware_build_deck($slug) {
$found = courseware_resolve_pptx($slug);
if ($found === null) {
return null;
}
return array(
'slug' => $found['slug'],
'title' => $found['title'],
'filename' => $found['filename'],
'url' => 'courseware-file.php?deck=' . rawurlencode($found['slug']),
);
}
}
if (!function_exists('courseware_list_decks')) {
function courseware_list_decks() {
$decks = array();
$seen = array();
foreach (courseware_scan_pptx_entries() as $slug => $name) {
unset($name);
if (isset($seen[$slug])) {
continue;
}
$deck = courseware_build_deck($slug);
if ($deck !== null) {
$seen[$slug] = true;
$decks[] = $deck;
}
}
usort($decks, function ($a, $b) {
return strnatcasecmp($a['title'], $b['title']);
});
return $decks;
}
}
if (!function_exists('courseware_clamp_page')) {
function courseware_clamp_page($deck, $page) {
$page = intval($page);
if ($page < 1) {
return 1;
}
$count = (is_array($deck) && isset($deck['page_count'])) ? intval($deck['page_count']) : 0;
if ($count < 1) {
return $page;
}
if ($page > $count) {
return $count;
}
return $page;
}
}
if (!function_exists('courseware_content_disposition')) {
function courseware_content_disposition($filename) {
$safe = str_replace(array("\r", "\n", '"', '\\'), '_', strval($filename));
$ascii = preg_replace('/[^\x20-\x7E]/', '_', $safe);
if ($ascii === '' || $ascii === '_' || $ascii === '.pptx') {
$ascii = 'courseware.pptx';
}
return 'inline; filename="' . $ascii . '"; filename*=UTF-8\'\'' . rawurlencode($filename);
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 gptsci.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because one or more lines are too long
+651
View File
@@ -0,0 +1,651 @@
JSZip is dual licensed. At your choice you may use it under the MIT license *or* the GPLv3
license.
The MIT License
===============
Copyright (c) 2009-2016 Stuart Knightley, David Duponchel, Franz Buchinger, António Afonso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
GPL version 3
=============
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
File diff suppressed because one or more lines are too long
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS="讨论版";
$MSG_HOME="主页";
$MSG_PROBLEMS="问题";
$MSG_COURSEWARE="课件";
$MSG_COURSEWARE_EMPTY="暂无课件";
$MSG_COURSEWARE_BACK="返回课件目录";
$MSG_COURSEWARE_PREV="上一页";
$MSG_COURSEWARE_NEXT="下一页";
$MSG_COURSEWARE_OPENING="正在打开课件…";
$MSG_COURSEWARE_PAGE_UNKNOWN="未知";
$MSG_COURSEWARE_PAGE_N="第%s页";
$MSG_COURSEWARE_ERR_LOGIN="请先登录后再查看课件。";
$MSG_COURSEWARE_ERR_NOT_FOUND="找不到这个课件。";
$MSG_COURSEWARE_ERR_BROKEN="打不开这个课件。请提交 .pptx 文件(PowerPoint 2007 及以后)。旧版 .ppt 需要另存为 .pptx。";
$MSG_COURSEWARE_ERR_PLAYER="播放器脚本没有加载成功。";
$MSG_DEMO="演示";
$MSG_STATUS="状态";
$MSG_PYTHON_DOC="python文档";
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS ="討論版";
$MSG_HOME ="主頁";
$MSG_PROBLEMS ="問題";
$MSG_COURSEWARE ="課件";
$MSG_COURSEWARE_EMPTY="暫無課件";
$MSG_COURSEWARE_BACK="返回課件目錄";
$MSG_COURSEWARE_PREV="上一頁";
$MSG_COURSEWARE_NEXT="下一頁";
$MSG_COURSEWARE_OPENING="正在打開課件…";
$MSG_COURSEWARE_PAGE_UNKNOWN="未知";
$MSG_COURSEWARE_PAGE_N="第%s頁";
$MSG_COURSEWARE_ERR_LOGIN="請先登錄後再查看課件。";
$MSG_COURSEWARE_ERR_NOT_FOUND="找不到這個課件。";
$MSG_COURSEWARE_ERR_BROKEN="打不開這個課件。請提交 .pptx 檔(PowerPoint 2007 及以後)。舊版 .ppt 需要另存為 .pptx。";
$MSG_COURSEWARE_ERR_PLAYER="播放器腳本沒有加載成功。";
$MSG_STATUS ="狀態";
$MSG_RANKLIST ="排名";
$MSG_CONTEST ="競賽&作業";
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS="Web Board";
$MSG_HOME="Home";
$MSG_PROBLEMS="ProblemSet";
$MSG_COURSEWARE="Slides";
$MSG_COURSEWARE_EMPTY="No slides yet";
$MSG_COURSEWARE_BACK="Back to slides";
$MSG_COURSEWARE_PREV="Previous page";
$MSG_COURSEWARE_NEXT="Next page";
$MSG_COURSEWARE_OPENING="Opening slides…";
$MSG_COURSEWARE_PAGE_UNKNOWN="unknown";
$MSG_COURSEWARE_PAGE_N="Page %s";
$MSG_COURSEWARE_ERR_LOGIN="Please sign in to view slides.";
$MSG_COURSEWARE_ERR_NOT_FOUND="These slides could not be found.";
$MSG_COURSEWARE_ERR_BROKEN="These slides could not be opened. Please submit a .pptx file (PowerPoint 2007 or later). Save older .ppt files as .pptx first.";
$MSG_COURSEWARE_ERR_PLAYER="The slide player script failed to load.";
$MSG_STATUS="Status";
$MSG_RANKLIST="Ranklist";
$MSG_CONTEST="Contest";
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS="برد";
$MSG_HOME="خانه";
$MSG_PROBLEMS="مسئله ها";
$MSG_COURSEWARE="اسلاید";
$MSG_COURSEWARE_EMPTY="هنوز اسلایدی نیست";
$MSG_COURSEWARE_BACK="بازگشت به فهرست اسلاید";
$MSG_COURSEWARE_PREV="صفحه قبل";
$MSG_COURSEWARE_NEXT="صفحه بعد";
$MSG_COURSEWARE_OPENING="در حال باز کردن اسلاید…";
$MSG_COURSEWARE_PAGE_UNKNOWN="نامشخص";
$MSG_COURSEWARE_PAGE_N="صفحه %s";
$MSG_COURSEWARE_ERR_LOGIN="برای دیدن اسلاید ابتدا وارد شوید.";
$MSG_COURSEWARE_ERR_NOT_FOUND="این اسلاید پیدا نشد.";
$MSG_COURSEWARE_ERR_BROKEN="نمی‌توان این اسلاید را باز کرد. لطفاً فایل .pptx (پاورپوینت ۲۰۰۷ به بعد) بفرستید. فایل‌های قدیمی .ppt را ابتدا به .pptx ذخیره کنید.";
$MSG_COURSEWARE_ERR_PLAYER="اسکریپت پخش‌کننده اسلاید بارگذاری نشد.";
$MSG_STATUS="وضعیت ها";
$MSG_RANKLIST="رتبه بندی";
$MSG_CONTEST="مسابقه ها";
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS="묻고답하기";
$MSG_HOME="처음화면";
$MSG_PROBLEMS="문제";
$MSG_COURSEWARE="수업자료";
$MSG_COURSEWARE_EMPTY="수업자료가 없습니다";
$MSG_COURSEWARE_BACK="수업자료 목록으로";
$MSG_COURSEWARE_PREV="이전 페이지";
$MSG_COURSEWARE_NEXT="다음 페이지";
$MSG_COURSEWARE_OPENING="수업자료를 여는 중…";
$MSG_COURSEWARE_PAGE_UNKNOWN="미정";
$MSG_COURSEWARE_PAGE_N="%s페이지";
$MSG_COURSEWARE_ERR_LOGIN="수업자료를 보려면 먼저 로그인하세요.";
$MSG_COURSEWARE_ERR_NOT_FOUND="해당 수업자료를 찾을 수 없습니다.";
$MSG_COURSEWARE_ERR_BROKEN="이 수업자료를 열 수 없습니다. PowerPoint 2007 이후의 .pptx 파일을 제출하세요. 이전 .ppt 파일은 .pptx로 다시 저장해야 합니다.";
$MSG_COURSEWARE_ERR_PLAYER="슬라이드 플레이어 스크립트를 불러오지 못했습니다.";
$MSG_STATUS="채점기록";
$MSG_RANKLIST="순위";
$MSG_CONTEST="대회";
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS="กระดานข่าว";
$MSG_HOME="หน้าแรก";
$MSG_PROBLEMS="โจทย์";
$MSG_COURSEWARE="สไลด์";
$MSG_COURSEWARE_EMPTY="ยังไม่มีสไลด์";
$MSG_COURSEWARE_BACK="กลับไปรายการสไลด์";
$MSG_COURSEWARE_PREV="หน้าก่อน";
$MSG_COURSEWARE_NEXT="หน้าถัดไป";
$MSG_COURSEWARE_OPENING="กำลังเปิดสไลด์…";
$MSG_COURSEWARE_PAGE_UNKNOWN="ยังไม่ทราบ";
$MSG_COURSEWARE_PAGE_N="หน้า %s";
$MSG_COURSEWARE_ERR_LOGIN="กรุณาเข้าสู่ระบบก่อนดูสไลด์";
$MSG_COURSEWARE_ERR_NOT_FOUND="ไม่พบสไลด์นี้";
$MSG_COURSEWARE_ERR_BROKEN="เปิดสไลด์นี้ไม่ได้ กรุณาส่งไฟล์ .pptx (PowerPoint 2007 เป็นต้นไป) ไฟล์ .ppt รุ่นเก่าต้องบันทึกเป็น .pptx ก่อน";
$MSG_COURSEWARE_ERR_PLAYER="สคริปต์โปรแกรมเล่นสไลด์โหลดไม่สำเร็จ";
$MSG_STATUS="สถานะ";
$MSG_RANKLIST="จัดอันดับ";
$MSG_CONTEST="การแข่งขัน";
+12
View File
@@ -4,6 +4,18 @@
$MSG_BBS="تور بەت";
$MSG_HOME="باش بەت";
$MSG_PROBLEMS="مەسىلىلەر";
$MSG_COURSEWARE="دەرسلىك";
$MSG_COURSEWARE_EMPTY="دەرسلىك يوق";
$MSG_COURSEWARE_BACK="دەرسلىك تىزىملىكىگە قايتىش";
$MSG_COURSEWARE_PREV="ئالدىنقى بەت";
$MSG_COURSEWARE_NEXT="كېيىنكى بەت";
$MSG_COURSEWARE_OPENING="دەرسلىك ئېچىلىۋاتىدۇ…";
$MSG_COURSEWARE_PAGE_UNKNOWN="نامەلۇم";
$MSG_COURSEWARE_PAGE_N="%s-بەت";
$MSG_COURSEWARE_ERR_LOGIN="دەرسلىكنى كۆرۈشتىن بۇرۇن تىزىملىتىڭ.";
$MSG_COURSEWARE_ERR_NOT_FOUND="بۇ دەرسلىك تېپىلمىدى.";
$MSG_COURSEWARE_ERR_BROKEN="بۇ دەرسلىكنى ئاچقىلى بولمىدى. PowerPoint 2007 ۋە ئۇنىڭدىن كېيىنكى .pptx ھۆججىتىنى يوللاڭ. كونىراق .ppt نى ئاۋۋال .pptx قىلىپ ساقلاڭ.";
$MSG_COURSEWARE_ERR_PLAYER="سلايد قويغۇچ قوليازمىسى يۈكلەنمىدى.";
$MSG_STATUS="ھالەت";
$MSG_RANKLIST="دەرىجە";
$MSG_CONTEST="مۇسابىقە";
+583
View File
@@ -0,0 +1,583 @@
<?php
$is_player = $selected_deck !== null;
$caption_colors = array('#e86a5c', '#2f9e99', '#f0a04b');
include('template/' . $OJ_TEMPLATE . '/header.php');
?>
<style>
body#MainBg-C.cw-catalog-page {
background: #f3eadc !important;
background-image:
repeating-linear-gradient(
to bottom,
rgba(120, 90, 50, 0.05) 0,
rgba(120, 90, 50, 0.05) 1px,
transparent 1px,
transparent 32px
) !important;
}
body#MainBg-C.cw-player-page {
background: #1b2430 !important;
}
#main.ui.main.container.cw-main {
width: 100% !important;
max-width: none !important;
margin: 0 !important;
padding: 0 24px 48px !important;
}
.cw-player-page #main.ui.main.container.cw-main {
padding: 0 !important;
}
.cw-player-page .footer {
display: none;
}
.cw-hero {
text-align: center;
padding: 28px 0 8px;
}
.cw-hero h1 {
margin: 0;
font-size: 3.4rem;
font-weight: 800;
color: #e86a5c;
letter-spacing: 0.04em;
line-height: 1.1;
}
.cw-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 28px;
max-width: 1180px;
margin: 28px auto 0;
}
.cw-card {
display: block;
background: #fff;
border-radius: 18px;
overflow: hidden;
text-decoration: none !important;
color: inherit;
box-shadow: 0 14px 32px rgba(80, 50, 20, 0.12);
transform: translateY(0);
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.cw-card:hover,
.cw-card:focus {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(80, 50, 20, 0.16);
}
.cw-card-cover {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
aspect-ratio: 16 / 9;
background:
radial-gradient(circle at 20% 20%, rgba(255,255,255,0.35), transparent 42%),
linear-gradient(160deg, #f7d3c8, #f6efe4 55%, #d8efe9);
}
.cw-card-meta {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 14px 16px 16px;
color: #fff;
}
.cw-card-title {
font-size: 1.05rem;
font-weight: 700;
line-height: 1.35;
}
.cw-empty {
max-width: 640px;
margin: 40px auto 0;
padding: 28px 24px;
text-align: center;
background: rgba(255, 255, 255, 0.72);
border-radius: 18px;
color: #4b3b2a;
font-size: 1.05rem;
line-height: 1.7;
}
.cw-theater {
min-height: calc(100vh - 77px);
display: flex;
flex-direction: column;
padding: 18px 20px 24px;
color: #f4f1ea;
}
.cw-back {
align-self: flex-start;
color: #f4f1ea !important;
font-size: 1.05rem;
font-weight: 600;
text-decoration: underline;
text-underline-offset: 4px;
margin-bottom: 8px;
}
.cw-stage {
flex: 1;
display: grid;
grid-template-columns: 72px minmax(0, 1fr) 72px;
align-items: center;
gap: 12px;
min-height: 0;
}
.cw-nav-btn {
width: 64px;
height: 64px;
border: 0;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 2rem;
cursor: pointer;
color: #fff;
background: #5b6570;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
}
.cw-nav-btn.next {
background: #e86a5c;
}
.cw-nav-btn.is-disabled,
.cw-nav-btn:disabled {
opacity: 0.28;
pointer-events: none;
}
.cw-frame {
position: relative;
width: 100%;
max-height: calc(100vh - 220px);
aspect-ratio: 16 / 9;
margin: 0 auto;
background: #fff;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
cursor: pointer;
}
.cw-frame canvas {
display: block;
width: 100%;
height: 100%;
}
.cw-status {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
text-align: center;
color: #3b3228;
font-size: 1.15rem;
font-weight: 600;
background: #fff8ee;
}
.cw-status.is-error {
color: #9b2c2c;
}
.cw-status[hidden] {
display: none;
}
.cw-pager {
text-align: center;
margin-top: 16px;
}
.cw-pager-num {
font-size: 1.8rem;
font-weight: 800;
letter-spacing: 0.04em;
}
.cw-pager-num em {
font-style: normal;
color: #e86a5c;
border-bottom: 4px solid #e86a5c;
padding: 0 2px;
}
.cw-dots {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.cw-dots button {
width: 10px;
height: 10px;
padding: 0;
border: 0;
border-radius: 50%;
background: #7b8490;
cursor: pointer;
}
.cw-dots button.is-current {
background: #e86a5c;
}
.cw-dots button:disabled {
cursor: default;
}
.cw-mobile-nav {
display: none;
justify-content: space-between;
padding: 8px 8px 0;
}
@media (max-width: 991px) {
.cw-hero h1 {
font-size: 2.4rem;
}
.cw-stage {
grid-template-columns: 1fr;
}
.cw-frame {
max-height: none;
}
.cw-mobile-nav {
display: flex;
}
}
</style>
<script>
document.body.classList.add('<?php echo $is_player ? 'cw-player-page' : 'cw-catalog-page'; ?>');
var main = document.getElementById('main');
if (main) {
main.classList.add('cw-main');
}
</script>
<?php if ($is_player) { ?>
<div class="cw-theater">
<a class="cw-back" href="courseware.php"><?php echo htmlspecialchars($MSG_COURSEWARE_BACK, ENT_QUOTES, 'UTF-8'); ?></a>
<div class="cw-stage">
<button type="button" class="cw-nav-btn desktop-only" id="cw-prev" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_PREV, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true"></button>
<div class="cw-frame" id="cw-frame">
<canvas id="cw-canvas"></canvas>
<div class="cw-status" id="cw-status" role="status" aria-live="polite"><?php echo htmlspecialchars($MSG_COURSEWARE_OPENING, ENT_QUOTES, 'UTF-8'); ?></div>
</div>
<button type="button" class="cw-nav-btn next desktop-only" id="cw-next" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_NEXT, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true"></button>
</div>
<div class="cw-mobile-nav">
<button type="button" class="cw-nav-btn" id="cw-prev-m" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_PREV, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true"></button>
<button type="button" class="cw-nav-btn next" id="cw-next-m" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_NEXT, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true"></button>
</div>
<div class="cw-pager">
<div class="cw-pager-num" aria-live="polite"><em id="cw-page-now"><?php echo intval($current_page); ?></em> / <span id="cw-page-total"><?php echo htmlspecialchars($MSG_COURSEWARE_PAGE_UNKNOWN, ENT_QUOTES, 'UTF-8'); ?></span></div>
<div class="cw-dots" id="cw-dots" role="tablist"></div>
</div>
</div>
<script src="<?php echo $path_fix; ?>include/pptxviewjs/jszip.min.js"></script>
<script src="<?php echo $path_fix; ?>include/pptxviewjs/PptxViewJS.min.js"></script>
<script>
(function () {
var slug = <?php echo json_encode($selected_deck['slug'], JSON_UNESCAPED_UNICODE); ?>;
var page = <?php echo intval($current_page); ?>;
var desiredPage = page;
var total = 0;
var totalKnown = false;
var ready = false;
var failed = false;
var viewer = null;
var navQueue = Promise.resolve();
var navBusy = false;
var canvas = document.getElementById('cw-canvas');
var statusEl = document.getElementById('cw-status');
var buttons = {
prev: [document.getElementById('cw-prev'), document.getElementById('cw-prev-m')],
next: [document.getElementById('cw-next'), document.getElementById('cw-next-m')]
};
var msg = {
unknown: <?php echo json_encode($MSG_COURSEWARE_PAGE_UNKNOWN, JSON_UNESCAPED_UNICODE); ?>,
pageN: <?php echo json_encode($MSG_COURSEWARE_PAGE_N, JSON_UNESCAPED_UNICODE); ?>,
login: <?php echo json_encode($MSG_COURSEWARE_ERR_LOGIN, JSON_UNESCAPED_UNICODE); ?>,
missing: <?php echo json_encode($MSG_COURSEWARE_ERR_NOT_FOUND, JSON_UNESCAPED_UNICODE); ?>,
broken: <?php echo json_encode($MSG_COURSEWARE_ERR_BROKEN, JSON_UNESCAPED_UNICODE); ?>,
player: <?php echo json_encode($MSG_COURSEWARE_ERR_PLAYER, JSON_UNESCAPED_UNICODE); ?>
};
function setStatus(text, isError) {
if (!text) {
statusEl.hidden = true;
statusEl.removeAttribute('role');
statusEl.setAttribute('role', 'status');
statusEl.setAttribute('aria-live', 'polite');
return;
}
statusEl.hidden = false;
statusEl.textContent = text;
statusEl.classList.toggle('is-error', !!isError);
statusEl.setAttribute('role', isError ? 'alert' : 'status');
statusEl.setAttribute('aria-live', isError ? 'assertive' : 'polite');
}
function setBtnState(list, disabled) {
list.forEach(function (btn) {
if (!btn) {
return;
}
btn.disabled = !!disabled;
btn.setAttribute('aria-disabled', disabled ? 'true' : 'false');
btn.classList.toggle('is-disabled', !!disabled);
});
}
function clampPage(value) {
var next = parseInt(value, 10);
if (isNaN(next) || next < 1) {
next = 1;
}
if (totalKnown && total >= 1 && next > total) {
next = total;
}
return next;
}
function shownPage() {
if (viewer && ready && !failed) {
return viewer.getCurrentSlideIndex() + 1;
}
return page;
}
function updateChrome() {
if (viewer && ready) {
page = viewer.getCurrentSlideIndex() + 1;
total = viewer.getSlideCount() || total;
totalKnown = total >= 1;
}
document.getElementById('cw-page-now').textContent = String(page);
document.getElementById('cw-page-total').textContent = totalKnown ? String(total) : msg.unknown;
var disableNav = !ready || failed;
setBtnState(buttons.prev, disableNav || desiredPage <= 1);
setBtnState(buttons.next, disableNav || (totalKnown && desiredPage >= total));
var dots = document.getElementById('cw-dots');
if (totalKnown && dots.childNodes.length !== total) {
dots.innerHTML = '';
for (var i = 1; i <= total; i++) {
var dot = document.createElement('button');
dot.type = 'button';
dot.setAttribute('aria-label', msg.pageN.replace('%s', String(i)));
(function (index) {
dot.addEventListener('click', function (event) {
event.stopPropagation();
go(index);
});
})(i);
dots.appendChild(dot);
}
}
Array.prototype.forEach.call(dots.children, function (dot, idx) {
dot.classList.toggle('is-current', idx + 1 === page);
dot.disabled = !ready || failed;
dot.setAttribute('aria-disabled', (!ready || failed) ? 'true' : 'false');
});
if (window.history && window.history.replaceState) {
window.history.replaceState(null, '', 'courseware.php?deck=' + encodeURIComponent(slug) + '&page=' + page);
}
}
function go(nextPage) {
if (!viewer || !ready || failed) {
return;
}
desiredPage = clampPage(nextPage);
if (navBusy) {
return;
}
navBusy = true;
navQueue = navQueue.then(function pump() {
if (!viewer || !ready || failed) {
navBusy = false;
return;
}
var target = clampPage(desiredPage);
var current = shownPage();
if (target === current) {
page = current;
navBusy = false;
updateChrome();
return;
}
return viewer.goToSlide(target - 1).then(function () {
page = shownPage();
updateChrome();
if (clampPage(desiredPage) !== page) {
return pump();
}
navBusy = false;
});
}).catch(function () {
navBusy = false;
setStatus(msg.broken, true);
});
}
function sizeCanvas() {
var frame = document.getElementById('cw-frame');
var rect = frame.getBoundingClientRect();
var ratio = window.devicePixelRatio || 1;
canvas.width = Math.max(1, Math.floor(rect.width * ratio));
canvas.height = Math.max(1, Math.floor(rect.height * ratio));
}
function fail(kind) {
failed = true;
ready = false;
if (kind === 'login') {
setStatus(msg.login, true);
} else if (kind === 'missing') {
setStatus(msg.missing, true);
} else if (kind === 'player') {
setStatus(msg.player, true);
} else {
setStatus(msg.broken, true);
}
updateChrome();
}
try {
if (!window.Chart || !window.JSZip || !window.PptxViewJS || !window.PptxViewJS.PPTXViewer) {
fail('player');
return;
}
sizeCanvas();
viewer = new PptxViewJS.PPTXViewer({
canvas: canvas,
slideSizeMode: 'fit',
backgroundColor: '#ffffff'
});
viewer.on('slideChanged', updateChrome);
viewer.on('renderComplete', function () {
if (!failed) {
setStatus('');
updateChrome();
}
});
viewer.loadFromArrayBuffer = function (buffer) {
return this.loadFile(buffer);
};
fetch('courseware-file.php?deck=' + encodeURIComponent(slug), { credentials: 'same-origin' }).then(function (res) {
if (res.status === 401) {
throw { kind: 'login' };
}
if (res.status === 404 || !res.ok) {
throw { kind: 'missing' };
}
return res.arrayBuffer();
}).then(function (buf) {
return viewer.loadFromArrayBuffer(buf);
}).then(function () {
total = viewer.getSlideCount() || 0;
totalKnown = total >= 1;
if (!totalKnown) {
throw { kind: 'broken' };
}
ready = true;
page = clampPage(page);
desiredPage = page;
return viewer.goToSlide(page - 1);
}).then(function () {
setStatus('');
updateChrome();
}).catch(function (err) {
fail(err && err.kind ? err.kind : 'broken');
});
} catch (err) {
fail('player');
}
buttons.prev.forEach(function (btn) {
if (btn) btn.addEventListener('click', function () { go(desiredPage - 1); });
});
buttons.next.forEach(function (btn) {
if (btn) btn.addEventListener('click', function () { go(desiredPage + 1); });
});
document.addEventListener('keydown', function (event) {
if (event.key === 'ArrowLeft') {
event.preventDefault();
go(desiredPage - 1);
}
if (event.key === 'ArrowRight' || event.key === ' ') {
event.preventDefault();
go(desiredPage + 1);
}
if (event.key === 'Escape') window.location.href = 'courseware.php';
});
document.getElementById('cw-frame').addEventListener('click', function (event) {
var rect = this.getBoundingClientRect();
var x = event.clientX - rect.left;
if (x < rect.width * 0.4) go(desiredPage - 1);
if (x > rect.width * 0.6) go(desiredPage + 1);
});
window.addEventListener('resize', function () {
sizeCanvas();
if (viewer && ready && !failed) viewer.render();
});
updateChrome();
})();
</script>
<?php } else { ?>
<div class="cw-hero">
<h1><?php echo htmlspecialchars($MSG_COURSEWARE, ENT_QUOTES, 'UTF-8'); ?></h1>
</div>
<?php if (empty($decks)) { ?>
<div class="cw-empty"><?php echo htmlspecialchars($MSG_COURSEWARE_EMPTY, ENT_QUOTES, 'UTF-8'); ?></div>
<?php } else { ?>
<div class="cw-grid">
<?php foreach ($decks as $index => $deck) {
$bar = $caption_colors[$index % count($caption_colors)];
?>
<a class="cw-card" href="courseware.php?deck=<?php echo rawurlencode($deck['slug']); ?>">
<div class="cw-card-cover" aria-hidden="true"></div>
<div class="cw-card-meta" style="background:<?php echo $bar; ?>">
<div class="cw-card-title"><?php echo htmlspecialchars($deck['title'], ENT_QUOTES, 'UTF-8'); ?></div>
</div>
</a>
<?php } ?>
</div>
<?php } ?>
<?php } ?>
<?php include('template/' . $OJ_TEMPLATE . '/footer.php'); ?>
-193
View File
@@ -1,193 +0,0 @@
<?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.docs-content-header-compact {
justify-content: flex-end;
}
.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;
}
@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>
</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<?php if (!$show_selected_title) echo ' docs-content-header-compact'; ?>">
<?php if ($show_selected_title) { ?>
<h1><?php echo htmlspecialchars($selected_title, ENT_QUOTES, 'UTF-8'); ?></h1>
<?php } ?>
<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'); ?>
+2
View File
@@ -1,7 +1,9 @@
</div>
</div>
<script src="<?php echo $OJ_CDN_URL.$path_fix."template/$OJ_TEMPLATE"?>/css/semantic.min.js"></script>
<?php if (empty($OJ_COURSEWARE_PAGE)) { ?>
<script src="<?php echo $path_fix."template/$OJ_TEMPLATE"?>/css/Chart.min.js"></script>
<?php } ?>
<style>
.footer {
line-height: 1.4285em;
+5
View File
@@ -192,6 +192,9 @@ a.label:hover {
console.log('\n %c Theme By %c Baoshuo ( @renbaoshuo ) %c https://baoshuo.ren %c\n', 'color: #fadfa3; background: #000000; padding:5px 0;', 'background: #fadfa3; padding:5px 0;', 'background: #ffbf33; padding:5px 0;', '');
console.log('\n GitHub Homepage: https://github.com/zhblue/hustoj \n Document: https://zhblue.github.io/hustoj \n Bug report URL: https://github.com/zhblue/hustoj/issues \n \n%c ★ Please give us a star on GitHub! ★ %c \n', 'color: red;', '')
</script>
<?php if (!empty($OJ_COURSEWARE_PAGE)) { ?>
<script src="<?php echo $path_fix."template/$OJ_TEMPLATE"?>/css/Chart.min.js"></script>
<?php } ?>
</head>
<?php
@@ -216,6 +219,8 @@ a.label:hover {
<!-- 问题 -->
<a class="item <?php if ($url=="problemset.php") echo "active";?>"
href="<?php echo $path_fix?>problemset.php"><i class="list icon"></i><?php echo $MSG_PROBLEMS?> </a>
<a class="item <?php if ($url=="courseware.php") echo "active";?>"
href="<?php echo $path_fix?>courseware.php"><i class="clone icon"></i><?php echo $MSG_COURSEWARE; ?></a>
<!-- 竞赛/作业 -->
<a class="item <?php if ($url=="contest.php") echo "active";?>" href="<?php echo $path_fix?>contest.php<?php if(isset($_SESSION[$OJ_NAME."_user_id"])) echo "?my" ?>" ><i
class="trophy icon"></i> <?php echo $MSG_CONTEST?></a>