feat(courseware): add protected PPTX viewer
This commit is contained in:
@@ -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.
@@ -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");
|
||||||
@@ -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);
|
||||||
@@ -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');
|
||||||
@@ -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. 想一想
|
|
||||||
|
|
||||||
- 为什么插入和删除都可能需要移动很多个元素?
|
|
||||||
- 如果总是在数组最前面插入,会不会很麻烦?
|
|
||||||
|
|
||||||
如果你能清楚地回答这两个问题,就说明你已经真正理解数组的插入和删除了。
|
|
||||||
@@ -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$)。
|
|
||||||
@@ -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 变 1,1 变 0。
|
|
||||||
|
|
||||||
| a | ~a |
|
|
||||||
|:---:|:---:|
|
|
||||||
| 0 | 1 |
|
|
||||||
| 1 | 0 |
|
|
||||||
|
|
||||||
### 4. 按位异或 XOR(符号 `^`)
|
|
||||||
|
|
||||||
规则:两位不同为 1,相同为 0。
|
|
||||||
|
|
||||||
| a | b | a ^ b |
|
|
||||||
|:---:|:---:|:-----:|
|
|
||||||
| 0 | 0 | 0 |
|
|
||||||
| 0 | 1 | 1 |
|
|
||||||
| 1 | 0 | 1 |
|
|
||||||
| 1 | 1 | 0 |
|
|
||||||
|
|
||||||
可以理解为“不同就亮灯”。
|
|
||||||
|
|
||||||
### 5. 左移(符号 `<<`)
|
|
||||||
|
|
||||||
规则:`x << k` 表示所有位向左移动 $k$ 位,右侧补 0。
|
|
||||||
|
|
||||||
- 在不溢出的情况下,数值相当于乘 $2^k$。
|
|
||||||
- 固定 32 位时,左边移出去的高位会被丢弃。
|
|
||||||
|
|
||||||
例:8 位下 `00101101 << 2 = 10110100`。
|
|
||||||
|
|
||||||
### 6. 右移(符号 `>>`)
|
|
||||||
|
|
||||||
规则:`x >> k` 表示所有位向右移动 $k$ 位。
|
|
||||||
|
|
||||||
- 对无符号数,左侧补 0。
|
|
||||||
- 对有符号数,很多语言会做“算术右移”(补符号位),所以课堂里先按无符号来理解更稳妥。
|
|
||||||
|
|
||||||
例:8 位下 `00101101 >> 3 = 00000101`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、C++ 里位运算的优先级(一定要加括号)
|
|
||||||
|
|
||||||
下面按 cppreference 的顺序,只保留“和计算最相关”的部分(第 5-7、9-15 级):
|
|
||||||
|
|
||||||
| 优先级(高 -> 低) | 运算符 | 含义 | |
|
|
||||||
|:-----------:|:----------------- |:----- |:---:|
|
|
||||||
| 5 | `*` `/` `%` | 乘、除、模 | |
|
|
||||||
| 6 | `+` `-` | 加、减 | |
|
|
||||||
| 7 | `<<` `>>` | 左移、右移 | |
|
|
||||||
| 9 | `<` `<=` `>` `>=` | 关系比较 | |
|
|
||||||
| 10 | `==` `!=` | 相等比较 | |
|
|
||||||
| 11 | `&` | 按位与 | |
|
|
||||||
| 12 | `^` | 按位异或 | |
|
|
||||||
| 13 | `\|` | 按位或 | |
|
|
||||||
| 14 | `&&` | 逻辑与 | |
|
|
||||||
| 15 | `\|\|` | 逻辑或 | |
|
|
||||||
|
|
||||||
看 5 个典型例子:
|
|
||||||
|
|
||||||
1) `a + b << 1`
|
|
||||||
- 实际等价于 `(a + b) << 1`
|
|
||||||
- 不是 `a + (b << 1)`
|
|
||||||
2) `x | y & z`
|
|
||||||
- 实际等价于 `x | (y & z)`
|
|
||||||
- 不是 `(x | y) & z`
|
|
||||||
3) `a ^ b & c`
|
|
||||||
- 实际等价于 `a ^ (b & c)`
|
|
||||||
- 不是 `(a ^ b) & c`
|
|
||||||
4) `x + y > z`
|
|
||||||
- 实际等价于 `(x + y) > z`
|
|
||||||
- 不是 `x + (y > z)`
|
|
||||||
5) `a & b == 0`
|
|
||||||
- 实际等价于 `a & (b == 0)`(因为 `==` 高于 `&`)
|
|
||||||
- 想判断“按位与结果是否为 0”,应写成 `(a & b) == 0`
|
|
||||||
|
|
||||||
课堂建议:只要一个表达式里混用了两类以上运算符,就主动加括号,别赌记忆。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、位串上的运算:逐位独立进行
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
```
|
|
||||||
a = 00101101
|
|
||||||
b = 00010111
|
|
||||||
```
|
|
||||||
|
|
||||||
逐位计算:
|
|
||||||
|
|
||||||
```
|
|
||||||
a & b = 00000101
|
|
||||||
a | b = 00111111
|
|
||||||
a ^ b = 00111010
|
|
||||||
~a = 11010010
|
|
||||||
```
|
|
||||||
|
|
||||||
注意:`~a` 的结果长度和机器位数有关。课堂里我们常固定成 8 位或 32 位来讨论。
|
|
||||||
|
|
||||||
### 竖式写法示范(像小学列竖式)
|
|
||||||
|
|
||||||
把高位写在左边、低位写在右边,同一列对齐后逐列运算。
|
|
||||||
|
|
||||||
1) 按位与 `&`
|
|
||||||
|
|
||||||
```text
|
|
||||||
0 0 1 0 1 1 0 1 (a)
|
|
||||||
& 0 0 0 1 0 1 1 1 (b)
|
|
||||||
----------------------
|
|
||||||
0 0 0 0 0 1 0 1 (a & b)
|
|
||||||
```
|
|
||||||
|
|
||||||
2) 按位或 `|`
|
|
||||||
|
|
||||||
```text
|
|
||||||
0 0 1 0 1 1 0 1 (a)
|
|
||||||
| 0 0 0 1 0 1 1 1 (b)
|
|
||||||
----------------------
|
|
||||||
0 0 1 1 1 1 1 1 (a | b)
|
|
||||||
```
|
|
||||||
|
|
||||||
3) 按位异或 `^`
|
|
||||||
|
|
||||||
```text
|
|
||||||
0 0 1 0 1 1 0 1 (a)
|
|
||||||
^ 0 0 0 1 0 1 1 1 (b)
|
|
||||||
----------------------
|
|
||||||
0 0 1 1 1 0 1 0 (a ^ b)
|
|
||||||
```
|
|
||||||
|
|
||||||
4) 左移与右移也可按“横向挪位”理解
|
|
||||||
|
|
||||||
```text
|
|
||||||
a : 0 0 1 0 1 1 0 1
|
|
||||||
a << 2 : 1 0 1 1 0 1 0 0 (左移两格,右侧补 0)
|
|
||||||
a >> 3 : 0 0 0 0 0 1 0 1 (右移三格,左侧补 0)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、二进制加法与减法
|
|
||||||
|
|
||||||
### 1. 二进制加法
|
|
||||||
|
|
||||||
和十进制竖式一样,也是“本位求和 + 进位”。
|
|
||||||
|
|
||||||
单个位相加规则:
|
|
||||||
|
|
||||||
- $0+0=0$,进位 0
|
|
||||||
- $0+1=1$,进位 0
|
|
||||||
- $1+0=1$,进位 0
|
|
||||||
- $1+1=0$,进位 1
|
|
||||||
|
|
||||||
如果再加上原来的进位,就变成三数相加。
|
|
||||||
|
|
||||||
竖式例子(8 位):`00101101 + 00010111`
|
|
||||||
|
|
||||||
```text
|
|
||||||
进位: 0 0 1 1 1 1 1
|
|
||||||
0 0 1 0 1 1 0 1
|
|
||||||
+ 0 0 0 1 0 1 1 1
|
|
||||||
----------------------
|
|
||||||
0 1 0 0 0 1 0 0
|
|
||||||
```
|
|
||||||
|
|
||||||
可让学生从最右列开始,逐列写“本列结果位”和“向左进位”。
|
|
||||||
|
|
||||||
### 2. 二进制减法
|
|
||||||
|
|
||||||
可以按“借位法”逐位减,也可以用补码思想把减法变加法。
|
|
||||||
|
|
||||||
在本章练习里,统一按 **32 位无符号整数** 处理:
|
|
||||||
|
|
||||||
- 超过 $2^{32}-1$ 的高位进位丢弃
|
|
||||||
- 不足 0 时按 32 位环绕(相当于加上 $2^{32}$)
|
|
||||||
|
|
||||||
竖式例子(8 位):`00101101 - 00010111`
|
|
||||||
|
|
||||||
```text
|
|
||||||
借位: 0 0 1 1 1 1 0
|
|
||||||
0 0 1 0 1 1 0 1
|
|
||||||
- 0 0 0 1 0 1 1 1
|
|
||||||
----------------------
|
|
||||||
0 0 0 1 0 1 1 0
|
|
||||||
```
|
|
||||||
|
|
||||||
从最右列开始,若不够减就向左借 1(借 1 相当于当前位加 2)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、C++:用数组模拟按位运算与加减
|
|
||||||
|
|
||||||
下面给一份课堂版代码:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <bits/stdc++.h>
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
const int LEN = 32;
|
|
||||||
|
|
||||||
// 把无符号整数转成二进制数组:bits[0] 是最低位
|
|
||||||
void toBits(unsigned int x, int bits[]) {
|
|
||||||
for (int i = 0; i < LEN; i++) {
|
|
||||||
bits[i] = x % 2;
|
|
||||||
x /= 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 把二进制数组转回无符号整数
|
|
||||||
unsigned int toUInt(const int bits[]) {
|
|
||||||
unsigned int x = 0;
|
|
||||||
for (int i = LEN - 1; i >= 0; i--) {
|
|
||||||
x = x * 2 + bits[i];
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印 32 位二进制串(高位在前)
|
|
||||||
void printBits(const int bits[]) {
|
|
||||||
for (int i = LEN - 1; i >= 0; i--) {
|
|
||||||
cout << bits[i];
|
|
||||||
}
|
|
||||||
cout << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
void bitAnd(const int a[], const int b[], int c[]) {
|
|
||||||
for (int i = 0; i < LEN; i++) c[i] = a[i] & b[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
void bitOr(const int a[], const int b[], int c[]) {
|
|
||||||
for (int i = 0; i < LEN; i++) c[i] = a[i] | b[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
void bitXor(const int a[], const int b[], int c[]) {
|
|
||||||
for (int i = 0; i < LEN; i++) c[i] = a[i] ^ b[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
void bitNot(const int a[], int c[]) {
|
|
||||||
for (int i = 0; i < LEN; i++) c[i] = 1 - a[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 32 位无符号加法:溢出进位自动丢弃
|
|
||||||
void addBits(const int a[], const int b[], int c[]) {
|
|
||||||
int carry = 0;
|
|
||||||
for (int i = 0; i < LEN; i++) {
|
|
||||||
int sum = a[i] + b[i] + carry;
|
|
||||||
c[i] = sum % 2;
|
|
||||||
carry = sum / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 32 位无符号减法:逐位借位
|
|
||||||
void subBits(const int a[], const int b[], int c[]) {
|
|
||||||
int borrow = 0;
|
|
||||||
for (int i = 0; i < LEN; i++) {
|
|
||||||
int cur = a[i] - b[i] - borrow;
|
|
||||||
if (cur >= 0) {
|
|
||||||
c[i] = cur;
|
|
||||||
borrow = 0;
|
|
||||||
} else {
|
|
||||||
c[i] = cur + 2;
|
|
||||||
borrow = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
unsigned int A, B;
|
|
||||||
cin >> A >> B;
|
|
||||||
|
|
||||||
int a[LEN], b[LEN], c[LEN];
|
|
||||||
toBits(A, a);
|
|
||||||
toBits(B, b);
|
|
||||||
|
|
||||||
cout << "A = "; printBits(a);
|
|
||||||
cout << "B = "; printBits(b);
|
|
||||||
|
|
||||||
bitAnd(a, b, c);
|
|
||||||
cout << "A AND B= "; printBits(c);
|
|
||||||
|
|
||||||
bitOr(a, b, c);
|
|
||||||
cout << "A OR B= "; printBits(c);
|
|
||||||
|
|
||||||
bitXor(a, b, c);
|
|
||||||
cout << "A XOR B= "; printBits(c);
|
|
||||||
|
|
||||||
bitNot(a, c);
|
|
||||||
cout << "NOT A = "; printBits(c);
|
|
||||||
|
|
||||||
addBits(a, b, c);
|
|
||||||
cout << "A + B = " << toUInt(c) << '\n';
|
|
||||||
|
|
||||||
subBits(a, b, c);
|
|
||||||
cout << "A - B = " << toUInt(c) << '\n';
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 七、练习题
|
|
||||||
|
|
||||||
### 【第一组】按位运算热身(进阶版,8 位)
|
|
||||||
|
|
||||||
已知:
|
|
||||||
|
|
||||||
- `a = 00101101`
|
|
||||||
- `b = 00010111`
|
|
||||||
1. 求 `a & b`。
|
|
||||||
2. 求 `a | b`。
|
|
||||||
3. 求 `a ^ b`。
|
|
||||||
4. 求 `~a`。
|
|
||||||
5. 求 `~b`。
|
|
||||||
6. 求 `(a & b) ^ a`。
|
|
||||||
7. 求 `(a | b) ^ b`。
|
|
||||||
8. 求 `(a ^ b) & a`。
|
|
||||||
9. 求 `(a ^ b) | b`。
|
|
||||||
10. 求 `(~a) & b`。
|
|
||||||
11. 求 `(~b) | a`。
|
|
||||||
12. 求 `a & (~b)`。
|
|
||||||
13. 求 `(a | b) & (a ^ b)`。
|
|
||||||
|
|
||||||
再设:
|
|
||||||
|
|
||||||
- `c = 01011000`
|
|
||||||
- `d = 00110101`
|
|
||||||
14. 求 `c & d`。
|
|
||||||
15. 求 `c | d`。
|
|
||||||
16. 求 `c ^ d`。
|
|
||||||
17. 求 `(c ^ d) ^ c`。
|
|
||||||
18. 求 `(c & d) | (c ^ d)`。
|
|
||||||
19. 求 `~(c ^ d)`。
|
|
||||||
20. 求 `(~c) ^ d`。
|
|
||||||
|
|
||||||
再做移位:
|
|
||||||
|
|
||||||
21. 求 `a << 1`(8 位)。
|
|
||||||
22. 求 `a << 3`(8 位)。
|
|
||||||
23. 求 `a >> 2`(8 位)。
|
|
||||||
24. 求 `b >> 1`(8 位)。
|
|
||||||
25. 求 `(a << 2) & 0b11111111`(8 位掩码保留)。
|
|
||||||
26. 求 `(b << 1) ^ (a >> 2)`(8 位)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 【第二组】真值与规律(先判断,再写一句理由)
|
|
||||||
|
|
||||||
27. 对任意位串 `x`,是否总有 `x ^ x == 0`?
|
|
||||||
28. 对任意位串 `x`,是否总有 `x & x == x`?
|
|
||||||
29. 对任意位串 `x`,是否总有 `x | x == x`?
|
|
||||||
30. 对任意位串 `x`,是否总有 `x ^ 0 == x`?
|
|
||||||
31. 对任意位串 `x`,是否总有 `x ^ FULL_MASK == ~x`?(其中 `FULL_MASK` 指整个位宽全 1)
|
|
||||||
32. 对任意位串 `x`,是否总有 `(~x) & x == 0`?
|
|
||||||
33. 对任意位串 `x`,是否总有 `(~x) | x == FULL_MASK`?
|
|
||||||
34. 对任意非负整数 `x`,是否总有 `(x << 1) == 2 * x`?
|
|
||||||
35. 对任意非负整数 `x`,是否总有 `(x >> 1) == x / 2`(整除)?
|
|
||||||
36. 若 `x` 是 2 的幂,是否总有 `x > 0 && (x & (x - 1)) == 0`?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 【第三组】加减法进阶(8 位,丢弃溢出位)
|
|
||||||
|
|
||||||
37. 计算:`00000101 + 00000110`
|
|
||||||
38. 计算:`11111111 + 00000001`
|
|
||||||
39. 计算:`00001010 - 00000011`
|
|
||||||
40. 计算:`00000000 - 00000001`
|
|
||||||
41. 计算:`10000000 + 10000000`
|
|
||||||
42. 计算:`01111111 + 00000001`
|
|
||||||
43. 计算:`01010101 + 00110011`
|
|
||||||
44. 计算:`00100000 - 00011111`
|
|
||||||
45. 计算:`00010000 - 00100000`
|
|
||||||
46. 计算:`10101010 - 01010101`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 【第四组】逆向构造题(更像算法题)
|
|
||||||
|
|
||||||
设 `a = 11001010`,求一个 8 位 `x`,使得:
|
|
||||||
|
|
||||||
47. `a & x == 10001000`(若有多解,写出任意一个)
|
|
||||||
48. `a | x == 11101110`(若有多解,写出任意一个)
|
|
||||||
49. `a ^ x == 01100111`(写唯一解)
|
|
||||||
|
|
||||||
再设 `u = 00110110`,`v = 00000110`:
|
|
||||||
|
|
||||||
50. 构造一个 8 位 `y`,使得 `y & u == v`。
|
|
||||||
51. 判断是否存在 8 位 `z`,使得 `z | u == v`,若存在给出一个,若不存在说明原因。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 【第五组】常用位技巧(探究题,重点)
|
|
||||||
|
|
||||||
这一组不要先背公式,先按题目把例子算出来,再归纳。
|
|
||||||
|
|
||||||
#### A. 探究“最低位 1”(lowbit)
|
|
||||||
|
|
||||||
对下面每个 `x`,先写出二进制,再计算 `-x`(按补码),最后算出 `x & (-x)`:
|
|
||||||
|
|
||||||
52. `x = 12`
|
|
||||||
|
|
||||||
53. `x = 40`
|
|
||||||
|
|
||||||
54. `x = 44`
|
|
||||||
|
|
||||||
55. `x = 72`
|
|
||||||
|
|
||||||
56. 观察 52-55 的结果,归纳一句话:`x & (-x)` 保留了 `x` 的哪一部分?
|
|
||||||
|
|
||||||
#### B. 探究“去掉最低位 1”
|
|
||||||
|
|
||||||
对下面每个 `x`,计算 `x - 1`,再算 `x & (x - 1)`:
|
|
||||||
|
|
||||||
57. `x = 12`
|
|
||||||
|
|
||||||
58. `x = 40`
|
|
||||||
|
|
||||||
59. `x = 44`
|
|
||||||
|
|
||||||
60. `x = 72`
|
|
||||||
|
|
||||||
61. 观察 57-60 的结果,归纳一句话:`x & (x-1)` 对二进制位做了什么变化?
|
|
||||||
|
|
||||||
#### C. 由例子得到“2 的幂”判定
|
|
||||||
|
|
||||||
分别计算 `x & (x - 1)`,并记录是否为 0:
|
|
||||||
|
|
||||||
62. `x = 1`
|
|
||||||
|
|
||||||
63. `x = 2`
|
|
||||||
|
|
||||||
64. `x = 4`
|
|
||||||
|
|
||||||
65. `x = 8`
|
|
||||||
|
|
||||||
66. `x = 3`
|
|
||||||
|
|
||||||
67. `x = 6`
|
|
||||||
|
|
||||||
68. 根据 62-67,总结:当 `x > 0` 时,什么条件等价于“`x` 是 2 的幂”?
|
|
||||||
|
|
||||||
#### D. 应用题(把结论用起来)
|
|
||||||
|
|
||||||
69. 不用循环,判断 `64` 是否是 2 的幂,并写出关键表达式值。
|
|
||||||
70. 不用循环,判断 `72` 是否是 2 的幂,并写出关键表达式值。
|
|
||||||
71. 设 `x = 01011000`(8 位),先求 `x & (-x)`,再给出最低位 1 的位置(最低位记第 0 位)。
|
|
||||||
72. 设 `x = 00101000`(8 位),先求 `x & (-x)`,再给出最低位 1 的位置(最低位记第 0 位)。
|
|
||||||
73. 设 `x = 10000000`(8 位),先求 `x & (-x)`,再给出最低位 1 的位置(最低位记第 0 位)。
|
|
||||||
74. 从 `x = 90` 开始,反复执行 `x = x & (x - 1)` 直到变成 0,需要几步?由此得到 `90` 的二进制中有几个 1?
|
|
||||||
75. 给定偶数 `n`,写出一个表达式快速判断它是否能被 4 整除,并说明对应的“二进制末尾特征”。
|
|
||||||
|
|
||||||
## 参考答案
|
|
||||||
|
|
||||||
**第一组**
|
|
||||||
|
|
||||||
1. `00000101`
|
|
||||||
2. `00111111`
|
|
||||||
3. `00111010`
|
|
||||||
4. `11010010`
|
|
||||||
5. `11101000`
|
|
||||||
6. `00101000`
|
|
||||||
7. `00101000`
|
|
||||||
8. `00101000`
|
|
||||||
9. `00111111`
|
|
||||||
10. `00010010`
|
|
||||||
11. `11101101`
|
|
||||||
12. `00101000`
|
|
||||||
13. `00111010`
|
|
||||||
14. `00010000`
|
|
||||||
15. `01111101`
|
|
||||||
16. `01101101`
|
|
||||||
17. `00110101`
|
|
||||||
18. `01111101`
|
|
||||||
19. `10010010`
|
|
||||||
20. `10010010`
|
|
||||||
21. `01011010`
|
|
||||||
22. `01101000`
|
|
||||||
23. `00001011`
|
|
||||||
24. `00001011`
|
|
||||||
25. `10110100`
|
|
||||||
26. `00100101`
|
|
||||||
|
|
||||||
**第二组**
|
|
||||||
|
|
||||||
27. 是。
|
|
||||||
28. 是。
|
|
||||||
29. 是。
|
|
||||||
30. 是。
|
|
||||||
31. 是(全 1 掩码下逐位翻转)。
|
|
||||||
32. 是。
|
|
||||||
33. 是。
|
|
||||||
34. 在不溢出的前提下是。
|
|
||||||
35. 是(无符号或非负整数语境下)。
|
|
||||||
36. 是。
|
|
||||||
|
|
||||||
**第三组**
|
|
||||||
|
|
||||||
37. `00001011`(十进制 11)
|
|
||||||
38. `00000000`(十进制 0)
|
|
||||||
39. `00000111`(十进制 7)
|
|
||||||
40. `11111111`(十进制 255)
|
|
||||||
41. `00000000`(十进制 0)
|
|
||||||
42. `10000000`(十进制 128)
|
|
||||||
43. `10001000`(十进制 136)
|
|
||||||
44. `00000001`(十进制 1)
|
|
||||||
45. `11110000`(十进制 240)
|
|
||||||
46. `01010101`(十进制 85)
|
|
||||||
|
|
||||||
**第四组(给一种可行解)**
|
|
||||||
|
|
||||||
47. 可取 `x = 10001100`。
|
|
||||||
48. 可取 `x = 01100100`。
|
|
||||||
49. 唯一解 `x = 10101101`。
|
|
||||||
50. 可取 `y = 11100110`(只要在 `u` 为 1 的位上与 `v` 对齐即可,`u` 为 0 的位任意)。
|
|
||||||
51. 不存在,因为按位或不会把 `u` 中的 1 变成 0,而 `u = 00110110` 在第 5、4、2、1 位已有 1,但 `v = 00000110` 在第 5、4 位是 0。
|
|
||||||
|
|
||||||
**第五组**
|
|
||||||
|
|
||||||
52. `12 = 1100`,`-12`(8 位)是 `11110100`,`12 & (-12) = 0100`(十进制 4)
|
|
||||||
53. `40 = 101000`,`40 & (-40) = 001000`(十进制 8)
|
|
||||||
54. `44 = 101100`,`44 & (-44) = 000100`(十进制 4)
|
|
||||||
55. `72 = 1001000`,`72 & (-72) = 0001000`(十进制 8)
|
|
||||||
56. 结论:`x & (-x)` 只保留最低位的 1,其余位清零(即 lowbit)
|
|
||||||
57. `12 & 11 = 1100 & 1011 = 1000`(十进制 8)
|
|
||||||
58. `40 & 39 = 101000 & 100111 = 100000`(十进制 32)
|
|
||||||
59. `44 & 43 = 101100 & 101011 = 101000`(十进制 40)
|
|
||||||
60. `72 & 71 = 1001000 & 1000111 = 1000000`(十进制 64)
|
|
||||||
61. 结论:`x & (x-1)` 会把最低位 1 消掉,且更低位保持为 0
|
|
||||||
62. `1 & 0 = 0`
|
|
||||||
63. `2 & 1 = 0`
|
|
||||||
64. `4 & 3 = 0`
|
|
||||||
65. `8 & 7 = 0`
|
|
||||||
66. `3 & 2 = 2`(非 0)
|
|
||||||
67. `6 & 5 = 4`(非 0)
|
|
||||||
68. 结论:`x > 0 && (x & (x - 1)) == 0` 当且仅当 `x` 是 2 的幂
|
|
||||||
69. `64 & 63 = 0`,所以是 2 的幂
|
|
||||||
70. `72 & 71 = 64`,非 0,所以不是 2 的幂
|
|
||||||
71. lowbit 是 `00001000`(8),最低位 1 在第 3 位
|
|
||||||
72. lowbit 是 `00001000`(8),最低位 1 在第 3 位
|
|
||||||
73. lowbit 是 `10000000`(128),最低位 1 在第 7 位
|
|
||||||
74. `90 -> 88 -> 80 -> 64 -> 0`,共 4 步,所以有 4 个 1
|
|
||||||
75. 可用 `(n & 3) == 0`,对应二进制末两位为 `00`
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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。
|
|
||||||
- 再加 6,sum 变成 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 = 0,i = 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 条,流程图这一关就算真正入门了。
|
|
||||||
@@ -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
@@ -1,71 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once('./include/db_info.inc.php');
|
header('Location: courseware.php', true, 302);
|
||||||
require_once('./include/setlang.php');
|
exit;
|
||||||
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');
|
|
||||||
?>
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
+1
File diff suppressed because one or more lines are too long
@@ -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
|
||||||
Vendored
+13
File diff suppressed because one or more lines are too long
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS="讨论版";
|
$MSG_BBS="讨论版";
|
||||||
$MSG_HOME="主页";
|
$MSG_HOME="主页";
|
||||||
$MSG_PROBLEMS="问题";
|
$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_DEMO="演示";
|
||||||
$MSG_STATUS="状态";
|
$MSG_STATUS="状态";
|
||||||
$MSG_PYTHON_DOC="python文档";
|
$MSG_PYTHON_DOC="python文档";
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS ="討論版";
|
$MSG_BBS ="討論版";
|
||||||
$MSG_HOME ="主頁";
|
$MSG_HOME ="主頁";
|
||||||
$MSG_PROBLEMS ="問題";
|
$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_STATUS ="狀態";
|
||||||
$MSG_RANKLIST ="排名";
|
$MSG_RANKLIST ="排名";
|
||||||
$MSG_CONTEST ="競賽&作業";
|
$MSG_CONTEST ="競賽&作業";
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS="Web Board";
|
$MSG_BBS="Web Board";
|
||||||
$MSG_HOME="Home";
|
$MSG_HOME="Home";
|
||||||
$MSG_PROBLEMS="ProblemSet";
|
$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_STATUS="Status";
|
||||||
$MSG_RANKLIST="Ranklist";
|
$MSG_RANKLIST="Ranklist";
|
||||||
$MSG_CONTEST="Contest";
|
$MSG_CONTEST="Contest";
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS="برد";
|
$MSG_BBS="برد";
|
||||||
$MSG_HOME="خانه";
|
$MSG_HOME="خانه";
|
||||||
$MSG_PROBLEMS="مسئله ها";
|
$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_STATUS="وضعیت ها";
|
||||||
$MSG_RANKLIST="رتبه بندی";
|
$MSG_RANKLIST="رتبه بندی";
|
||||||
$MSG_CONTEST="مسابقه ها";
|
$MSG_CONTEST="مسابقه ها";
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS="묻고답하기";
|
$MSG_BBS="묻고답하기";
|
||||||
$MSG_HOME="처음화면";
|
$MSG_HOME="처음화면";
|
||||||
$MSG_PROBLEMS="문제";
|
$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_STATUS="채점기록";
|
||||||
$MSG_RANKLIST="순위";
|
$MSG_RANKLIST="순위";
|
||||||
$MSG_CONTEST="대회";
|
$MSG_CONTEST="대회";
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS="กระดานข่าว";
|
$MSG_BBS="กระดานข่าว";
|
||||||
$MSG_HOME="หน้าแรก";
|
$MSG_HOME="หน้าแรก";
|
||||||
$MSG_PROBLEMS="โจทย์";
|
$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_STATUS="สถานะ";
|
||||||
$MSG_RANKLIST="จัดอันดับ";
|
$MSG_RANKLIST="จัดอันดับ";
|
||||||
$MSG_CONTEST="การแข่งขัน";
|
$MSG_CONTEST="การแข่งขัน";
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
$MSG_BBS="تور بەت";
|
$MSG_BBS="تور بەت";
|
||||||
$MSG_HOME="باش بەت";
|
$MSG_HOME="باش بەت";
|
||||||
$MSG_PROBLEMS="مەسىلىلەر";
|
$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_STATUS="ھالەت";
|
||||||
$MSG_RANKLIST="دەرىجە";
|
$MSG_RANKLIST="دەرىجە";
|
||||||
$MSG_CONTEST="مۇسابىقە";
|
$MSG_CONTEST="مۇسابىقە";
|
||||||
|
|||||||
@@ -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'); ?>
|
||||||
@@ -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'); ?>
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="<?php echo $OJ_CDN_URL.$path_fix."template/$OJ_TEMPLATE"?>/css/semantic.min.js"></script>
|
<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>
|
<script src="<?php echo $path_fix."template/$OJ_TEMPLATE"?>/css/Chart.min.js"></script>
|
||||||
|
<?php } ?>
|
||||||
<style>
|
<style>
|
||||||
.footer {
|
.footer {
|
||||||
line-height: 1.4285em;
|
line-height: 1.4285em;
|
||||||
|
|||||||
@@ -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 %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;', '')
|
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>
|
</script>
|
||||||
|
<?php if (!empty($OJ_COURSEWARE_PAGE)) { ?>
|
||||||
|
<script src="<?php echo $path_fix."template/$OJ_TEMPLATE"?>/css/Chart.min.js"></script>
|
||||||
|
<?php } ?>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
@@ -216,6 +219,8 @@ a.label:hover {
|
|||||||
<!-- 问题 -->
|
<!-- 问题 -->
|
||||||
<a class="item <?php if ($url=="problemset.php") echo "active";?>"
|
<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>
|
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
|
<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>
|
class="trophy icon"></i> <?php echo $MSG_CONTEST?></a>
|
||||||
|
|||||||
Reference in New Issue
Block a user