Initial commit: C++ courseware
This commit is contained in:
commit
4a4ee7c287
1789 files changed
+373489
No files matched your search
@@ -0,0 +1,277 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>C++ 嵌套循环单步可视化</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f4f7f6;
|
||||
color: #333;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
width: 1000px; /* 稍微增加了总宽度 */
|
||||
max-width: 95%;
|
||||
}
|
||||
/* 左侧代码区 */
|
||||
.code-panel {
|
||||
flex: 1.3; /* 给代码区分配更多空间 */
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
overflow-x: auto; /* 内容超出时显示水平滚动条 */
|
||||
}
|
||||
.code-line {
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
margin: 2px 0;
|
||||
border-left: 4px solid transparent;
|
||||
transition: all 0.3s;
|
||||
white-space: pre; /* 关键修改:强制不换行,保持代码在一行显示 */
|
||||
}
|
||||
.code-line.active {
|
||||
background-color: #063f4c;
|
||||
border-left: 4px solid #4fc1ff;
|
||||
color: #fff;
|
||||
}
|
||||
.keyword { color: #569cd6; }
|
||||
.type { color: #4ec9b0; }
|
||||
.string { color: #ce9178; }
|
||||
|
||||
/* 右侧面板 */
|
||||
.side-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
/* 状态与控制台 */
|
||||
.panel-box {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
flex: 1;
|
||||
}
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #6c757d;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.vars {
|
||||
font-size: 24px;
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
.vars span {
|
||||
color: #e83e8c;
|
||||
background: #fdf3f7;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.status-msg {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #0056b3;
|
||||
background: #cce5ff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.console-output {
|
||||
background: #000;
|
||||
color: #0f0;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
height: 120px;
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.btn-prev { background: #6c757d; color: white; }
|
||||
.btn-prev:hover { background: #5a6268; }
|
||||
.btn-next { background: #007bff; color: white; }
|
||||
.btn-next:hover { background: #0056b3; }
|
||||
.btn-reset { background: #dc3545; color: white; }
|
||||
.btn-reset:hover { background: #c82333; }
|
||||
button:disabled { background: #cccccc; cursor: not-allowed; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<!-- 代码区 -->
|
||||
<div class="code-panel">
|
||||
<div id="line-1" class="code-line"><span class="keyword">for</span> (<span class="type">int</span> i = 1; i <= 3; i++) {</div>
|
||||
<div id="line-2" class="code-line"> <span class="keyword">for</span> (<span class="type">int</span> j = 1; j <= 2; j++) {</div>
|
||||
<div id="line-3" class="code-line"> cout << <span class="string">"("</span> << i << <span class="string">", "</span> << j << <span class="string">") "</span>;</div>
|
||||
<div id="line-4" class="code-line"> }</div>
|
||||
<div id="line-5" class="code-line"> cout << endl;</div>
|
||||
<div id="line-6" class="code-line">}</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态与控制区 -->
|
||||
<div class="side-panel">
|
||||
<div class="panel-box">
|
||||
<div class="panel-title">内存中的变量状态</div>
|
||||
<div class="vars">
|
||||
i = <span id="var-i">?</span><br><br>
|
||||
j = <span id="var-j">?</span>
|
||||
</div>
|
||||
<div class="status-msg" id="status-msg">点击“下一步”开始执行程序</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-box">
|
||||
<div class="panel-title">控制台输出 (Console)</div>
|
||||
<div class="console-output" id="console"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn-prev" id="btn-prev" onclick="prevStep()" disabled>上一步</button>
|
||||
<button class="btn-next" id="btn-next" onclick="nextStep()">下一步</button>
|
||||
<button class="btn-reset" onclick="reset()">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 预定义所有执行步骤的状态机
|
||||
const steps = [
|
||||
{ line: null, i: '?', j: '?', out: '', msg: '程序准备就绪,等待外层循环启动。' },
|
||||
|
||||
// i = 1
|
||||
{ line: 1, i: 1, j: '?', out: '', msg: '进入外层循环。初始化 i = 1。' },
|
||||
{ line: 2, i: 1, j: 1, out: '', msg: '外层触发内层!创建全新变量 j,初始化 j = 1。' },
|
||||
{ line: 3, i: 1, j: 1, out: '(1, 1) ', msg: '打印当前 i 和 j 的值。' },
|
||||
{ line: 4, i: 1, j: 1, out: '(1, 1) ', msg: '内层循环一轮结束,准备 j++ 回到行2判断。' },
|
||||
{ line: 2, i: 1, j: 2, out: '(1, 1) ', msg: 'j 增加到 2,满足 j<=2,继续内层。' },
|
||||
{ line: 3, i: 1, j: 2, out: '(1, 1) (1, 2) ', msg: '打印当前 i 和 j 的值。' },
|
||||
{ line: 4, i: 1, j: 2, out: '(1, 1) (1, 2) ', msg: '内层循环一轮结束,准备 j++。' },
|
||||
{ line: 2, i: 1, j: 3, out: '(1, 1) (1, 2) ', msg: 'j 增加到 3,不满足 j<=2,内层循环终止!' },
|
||||
{ line: 5, i: 1, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n', msg: '退出内层大括号,j 被彻底销毁!执行换行。' },
|
||||
{ line: 6, i: 1, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n', msg: '外层第一轮结束,准备 i++ 回到行1判断。' },
|
||||
|
||||
// i = 2
|
||||
{ line: 1, i: 2, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n', msg: 'i 增加到 2,满足 i<=3,进入外层第二轮。' },
|
||||
{ line: 2, i: 2, j: 1, out: '(1, 1) (1, 2) \n', msg: '再次遇到内层循环,重新孕育一个全新的 j = 1!' },
|
||||
{ line: 3, i: 2, j: 1, out: '(1, 1) (1, 2) \n(2, 1) ', msg: '打印当前 i 和 j 的值。' },
|
||||
{ line: 4, i: 2, j: 1, out: '(1, 1) (1, 2) \n(2, 1) ', msg: '内层循环一轮结束,准备 j++。' },
|
||||
{ line: 2, i: 2, j: 2, out: '(1, 1) (1, 2) \n(2, 1) ', msg: 'j 增加到 2,满足 j<=2。' },
|
||||
{ line: 3, i: 2, j: 2, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) ', msg: '打印当前 i 和 j 的值。' },
|
||||
{ line: 4, i: 2, j: 2, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) ', msg: '内层循环一轮结束,准备 j++。' },
|
||||
{ line: 2, i: 2, j: 3, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) ', msg: 'j 增加到 3,不满足 j<=2,内层循环终止!' },
|
||||
{ line: 5, i: 2, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n', msg: '退出内层,j 再次被彻底销毁!执行换行。' },
|
||||
{ line: 6, i: 2, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n', msg: '外层第二轮结束,准备 i++ 回到行1判断。' },
|
||||
|
||||
// i = 3
|
||||
{ line: 1, i: 3, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n', msg: 'i 增加到 3,满足 i<=3,进入外层最后一轮。' },
|
||||
{ line: 2, i: 3, j: 1, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n', msg: '第三次触发内层循环,重新初始化新 j = 1。' },
|
||||
{ line: 3, i: 3, j: 1, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) ', msg: '打印当前 i 和 j 的值。' },
|
||||
{ line: 4, i: 3, j: 1, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) ', msg: '内层循环一轮结束。' },
|
||||
{ line: 2, i: 3, j: 2, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) ', msg: 'j 增加到 2,满足 j<=2。' },
|
||||
{ line: 3, i: 3, j: 2, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) (3, 2) ', msg: '打印最后的值。' },
|
||||
{ line: 4, i: 3, j: 2, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) (3, 2) ', msg: '内层循环一轮结束。' },
|
||||
{ line: 2, i: 3, j: 3, out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) (3, 2) ', msg: 'j 增加到 3,内层循环最后一次终止!' },
|
||||
{ line: 5, i: 3, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) (3, 2) \n', msg: 'j 被销毁。执行最后一次换行。' },
|
||||
{ line: 6, i: 3, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) (3, 2) \n', msg: '外层循环体全部执行完毕。' },
|
||||
|
||||
// 结束
|
||||
{ line: 1, i: 4, j: '销毁 (死亡)', out: '(1, 1) (1, 2) \n(2, 1) (2, 2) \n(3, 1) (3, 2) \n', msg: 'i 增加到 4,不满足 i<=3。外层循环结束!程序完成。' }
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
function renderState() {
|
||||
const state = steps[currentStep];
|
||||
|
||||
// 1. 清除所有高亮,设置当前高亮
|
||||
document.querySelectorAll('.code-line').forEach(el => el.classList.remove('active'));
|
||||
if (state.line !== null) {
|
||||
document.getElementById('line-' + state.line).classList.add('active');
|
||||
}
|
||||
|
||||
// 2. 更新变量面板,带有颜色区分销毁状态
|
||||
document.getElementById('var-i').innerText = state.i;
|
||||
const varJ = document.getElementById('var-j');
|
||||
varJ.innerText = state.j;
|
||||
if (state.j === '销毁 (死亡)') {
|
||||
varJ.style.color = '#dc3545';
|
||||
varJ.style.background = '#f8d7da';
|
||||
} else {
|
||||
varJ.style.color = '#e83e8c';
|
||||
varJ.style.background = '#fdf3f7';
|
||||
}
|
||||
|
||||
// 3. 更新控制台和提示信息
|
||||
document.getElementById('console').innerText = state.out;
|
||||
document.getElementById('status-msg').innerText = state.msg;
|
||||
|
||||
// 4. 更新按钮状态
|
||||
document.getElementById('btn-prev').disabled = currentStep === 0;
|
||||
document.getElementById('btn-next').disabled = currentStep === steps.length - 1;
|
||||
|
||||
// 控制台自动滚动到底部
|
||||
const consoleEl = document.getElementById('console');
|
||||
consoleEl.scrollTop = consoleEl.scrollHeight;
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep < steps.length - 1) {
|
||||
currentStep++;
|
||||
renderState();
|
||||
}
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (currentStep > 0) {
|
||||
currentStep--;
|
||||
renderState();
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
currentStep = 0;
|
||||
renderState();
|
||||
}
|
||||
|
||||
// 初始化渲染
|
||||
renderState();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,302 @@
|
||||
# C++ 程序与算法:多重循环与循环嵌套
|
||||
|
||||
## 一、单层循环到循环嵌套
|
||||
|
||||
### 1. 从小任务开始
|
||||
|
||||
还记得怎样在同一行输出 4 个星号吗?用一个循环就可以:
|
||||
|
||||
```cpp
|
||||
for (int j = 1; j <= 4; j++) {
|
||||
cout << "*";
|
||||
}
|
||||
cout << '\n';
|
||||
```
|
||||
|
||||
循环每执行一次,就输出一个星号。重复 4 次,屏幕上就有了:
|
||||
|
||||
```text
|
||||
****
|
||||
```
|
||||
|
||||
这里的换行为什么写在循环后面?
|
||||
|
||||
因为这一行的 4 个星号要先全部输出,再换行。如果把换行放进循环,每输出一个星号就换行,它们就会排成一列。
|
||||
|
||||
如果要输出 3 行,每行仍然是 4 个星号呢?
|
||||
|
||||
```text
|
||||
****
|
||||
****
|
||||
****
|
||||
```
|
||||
|
||||
把“输出一行并换行”的代码复制 3 遍,也能完成。不过,行数变成 100 时,复制起来就麻烦了;每行的星号数量要改,还得改很多处。
|
||||
|
||||
我们可以把“输出一行并换行”看成一件事,再用一个循环重复它。这样,循环里面就有了另一个循环,这种写法叫作**循环嵌套**。
|
||||
|
||||
### 2. 学习它有什么用?
|
||||
|
||||
许多题目都能像这样拆开:先重复处理一个对象,再重复处理它里面的内容。
|
||||
|
||||
| 场景 | 第一层任务 | 第二层任务 |
|
||||
| --- | --- | --- |
|
||||
| 输出矩形或三角形 | 逐行处理 | 输出当前行的内容 |
|
||||
| 输出乘法表 | 确定当前行 | 计算这一行中的各个乘积 |
|
||||
| 求多个阶乘的和 | 逐项处理阶乘 | 计算当前这一项的乘积 |
|
||||
| 统计数字出现次数 | 枚举一个整数 | 检查这个整数的每一位 |
|
||||
| 处理二维表格 | 枚举每一行 | 枚举该行中的每一列 |
|
||||
|
||||
算法竞赛中的枚举、模拟和表格处理,经常用到循环嵌套。以后学到数组、矩阵和图像处理时,我们还会遇到它。
|
||||
|
||||
## 二、核心思想:把完整的小任务放进循环体
|
||||
|
||||
### 1. 什么是循环嵌套?
|
||||
|
||||
在一个循环的循环体里,再放入另一个循环,就是循环嵌套。
|
||||
|
||||
```cpp
|
||||
for (外层初始化; 外层条件; 外层更新) {
|
||||
// 开始处理一个新的大任务
|
||||
|
||||
for (内层初始化; 内层条件; 内层更新) {
|
||||
// 完成当前大任务中的一个小步骤
|
||||
}
|
||||
|
||||
// 当前大任务的收尾工作
|
||||
}
|
||||
```
|
||||
|
||||
这段写法用来说明两层循环的结构;实际写程序时,需要把中文占位内容换成 C++ 语句。
|
||||
|
||||
外面的叫**外层循环**,里面的叫**内层循环**。两层循环也称为二重循环,题目需要时还可以嵌套三层或更多层。
|
||||
|
||||
判断是否嵌套,要看一个循环有没有放进另一个循环的循环体里。
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
cout << i;
|
||||
}
|
||||
for (int j = 1; j <= 4; j++) {
|
||||
cout << j;
|
||||
}
|
||||
```
|
||||
|
||||
上面两个循环是先做完一个,再做另一个。它们没有互相包含,所以不属于循环嵌套。
|
||||
|
||||
### 2. 一次外层循环中,内层会执行多少次?
|
||||
|
||||
输出星号矩形时,两层循环是这样配合的:
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
for (int j = 1; j <= 4; j++) {
|
||||
cout << "*";
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
外层每一轮都要完成一整行:
|
||||
|
||||
```text
|
||||
外层第 1 轮:内层输出 4 个星号 → 换行
|
||||
外层第 2 轮:内层输出 4 个星号 → 换行
|
||||
外层第 3 轮:内层输出 4 个星号 → 换行
|
||||
```
|
||||
|
||||
这里容易看错:外层进行一轮时,内层要从初始化开始,把当前行的 4 个星号全部输出。
|
||||
|
||||
输出这一行时,`i` 保持不变,内层执行 `j++` 只会改变行内位置。等这一行输出完并换行后,才轮到外层的 `i++`。
|
||||
|
||||
### 3. 为什么新的一行,`j` 又从 1 开始?
|
||||
|
||||
内层循环写的是:
|
||||
|
||||
```cpp
|
||||
for (int j = 1; j <= 4; j++) {
|
||||
cout << "*";
|
||||
}
|
||||
```
|
||||
|
||||
因为每次执行到这条 `for` 语句,程序都会先执行 `int j = 1`。所以,新的一行总是从第一个位置开始。
|
||||
|
||||
本例中的 `j` 只在内层 `for` 的范围内有效。内层结束后,这一轮的 `j` 就不能再使用了;下一次进入内层时,会重新创建并初始化 `j`。
|
||||
|
||||
### 4. 哪些语句属于外层?看大括号
|
||||
|
||||
下面的代码中,整个内层循环和后面的换行,都在外层的大括号里面:
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
// 任务 1:输出当前行的 4 个星号
|
||||
for (int j = 1; j <= 4; j++) {
|
||||
cout << "*";
|
||||
}
|
||||
|
||||
// 任务 2:当前行结束,换行
|
||||
cout << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
C++ 根据大括号判断语句属于哪一层。初学时,建议大家把大括号写完整。即使循环体只有一条语句,也先保留它,以后添加代码时就不容易放错位置。
|
||||
|
||||
## 三、例题重点回顾
|
||||
|
||||
### 例一:输出 3 行 4 列的星号矩形
|
||||
|
||||
这道题要输出的图形是:
|
||||
|
||||
```text
|
||||
****
|
||||
****
|
||||
****
|
||||
```
|
||||
|
||||
一行的写法已经会了,把“输出 4 个星号,再换行”交给外层,重复 3 次就可以。
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 3; i++) { // 当前是第 i 行。
|
||||
for (int j = 1; j <= 4; j++) { // 当前行的第 j 个位置。
|
||||
cout << '*';
|
||||
}
|
||||
cout << endl; // 一行全部输出后再换行。
|
||||
}
|
||||
```
|
||||
|
||||
回顾这段代码时,留意变量和换行的位置:
|
||||
|
||||
- `i` 控制行号;处理一行时,`i` 保持不变。
|
||||
- `j` 控制行内位置;每进入新的一行,都重新从 1 开始。
|
||||
- 换行放在内层结束后、外层之内,每行执行一次。
|
||||
|
||||
因此,星号输出 $$3\times4=12$$ 次,换行执行 3 次。
|
||||
|
||||
### 写循环嵌套的四个步骤
|
||||
|
||||
写类似的题目时,可以按这四步想:
|
||||
|
||||
| 步骤 | 要想清楚什么? | 星号矩形中的安排 |
|
||||
| --- | --- | --- |
|
||||
| ① 拆任务 | 外层和内层分别做什么? | 外层输出一整行,内层每次输出一个星号 |
|
||||
| ② 判断循环范围 | 每层从哪里开始,到哪里结束? | 外层从 1 到 3,共 3 行;内层从 1 到 4,每行 4 个星号 |
|
||||
| ③ 设置变量 | 变量表示什么,在哪里初始化? | `i` 表示行号;`j` 表示行内位置,每行重新从 1 开始 |
|
||||
| ④ 写循环语句 | 每个动作放在哪一层才合适? | 内层输出星号,内层结束后、外层之内换行 |
|
||||
|
||||
遇到编号、总和这样的变量,再问自己一句:“下一轮还要接着用这个值吗?”要接着用,就在外层前初始化;每轮重新计算,就在外层内初始化。
|
||||
|
||||
### 例二:连续编号的数字直角三角形
|
||||
|
||||
输入整数 `n`,范围是 `1 ≤ n ≤ 13`,输出 `n` 行数字。第一行有 `n` 个数,以后每行少一个;编号从 1 开始,换行后接着往下数。每个数显示两位,不足两位补 0,数字之间不加空格。
|
||||
|
||||
`n=5` 时:
|
||||
|
||||
```text
|
||||
0102030405
|
||||
06070809
|
||||
101112
|
||||
1314
|
||||
15
|
||||
```
|
||||
|
||||
这道题要分清“这一行有几个数”和“当前输出哪个数”:
|
||||
|
||||
- 第 `i` 行比第一行少 $$i-1$$ 个数,所以第 `i` 行有 $$n-(i-1)$$ 个数,内层上界随行号变化。
|
||||
- `i` 记录行号,`j` 记录行内位置,`cnt` 才是实际输出的编号。
|
||||
- `cnt` 要在外层前设为 1,每输出一个数后加 1。换到下一行时,编号还要接着用,所以不能重置。
|
||||
- 个位数前先输出字符 `'0'`,这一行的数字全部输出后再换行。
|
||||
|
||||
```cpp
|
||||
int cnt = 1; // 保留编号
|
||||
for (int i = 1; i <= n; i++) {
|
||||
for (int j = 1; j <= n - i + 1; j++) {
|
||||
if (cnt < 10) {
|
||||
cout << '0';
|
||||
}
|
||||
cout << cnt;
|
||||
cnt++;
|
||||
}
|
||||
cout << '\n'; // 一行结束后换行。
|
||||
}
|
||||
```
|
||||
|
||||
### 例三:计算阶乘之和
|
||||
|
||||
输入正整数 `n`,范围是 `1 ≤ n ≤ 20`,求 $$S=1!+2!+3!+\cdots+n!$$。
|
||||
|
||||
重点看乘积和 和 总和各自怎么保存:
|
||||
|
||||
- 外层依次处理每一项,内层从 1 乘到 `i`,算出当前的 `i!`。
|
||||
- 总和 `ans` 从 0 开始,一直保留前面各项的结果;乘积 `factor` 从 1 开始,每算新的一项都要重新准备。
|
||||
- 等内层乘完后,再执行 `ans += factor`。放进内层的话,计算过程中的乘积也会被加进去。
|
||||
- `factor` 和 `ans` 都要用 `long long`,因为 `13!` 已经超出常见的 32 位 `int` 范围。
|
||||
|
||||
读入 `n` 后,关键循环如下:
|
||||
|
||||
```cpp
|
||||
long long ans = 0; // 保留所有已完成项的总和。
|
||||
for (int i = 1; i <= n; i++) {
|
||||
long long factor = 1; // 每一项都从头计算。
|
||||
for (int j = 1; j <= i; j++) {
|
||||
factor *= j;
|
||||
}
|
||||
ans += factor; // 当前阶乘算完后再累加。
|
||||
}
|
||||
cout << ans << '\n';
|
||||
```
|
||||
|
||||
本题 `n≤20`,用 `long long` 可以保存阶乘和总和。不过,这种类型也有上限,不能一直用它计算更大的阶乘。
|
||||
|
||||
#### 简单改进:利用已经算出的阶乘
|
||||
|
||||
上一项的阶乘已经算好了,下一项可以接着乘。利用 $$i!=(i-1)!\times i$$,就能省去内层的重复计算:
|
||||
|
||||
```cpp
|
||||
long long ans = 0, factor = 1;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
factor *= i;
|
||||
ans += factor;
|
||||
}
|
||||
cout << ans << '\n';
|
||||
```
|
||||
|
||||
这时要把 `factor` 放到循环外,因为下一轮还要接着用上一次的乘积。变量放在哪里,要看当前算法怎样使用它。
|
||||
|
||||
### 例四:统计数字出现的次数
|
||||
|
||||
输入 `n` 和 `x`,统计 1 到 `n` 的所有整数中,数字 `x` 一共出现了多少次。输入范围为 `1≤n≤1000000`、`0≤x≤9`。
|
||||
|
||||
这里要把“取出一个整数”和“检查它的每一位”分开:
|
||||
|
||||
- 外层 `for` 逐个取出从 `1~n` 的每一个整数,内层 `while` 检查这个整数的全部数位。
|
||||
- 先用 `int tmp = i` 复制一份,再对副本拆位,因为外层变量 `i` 需要记住处理到哪个整数,所以不能把 `i` 给拆掉。
|
||||
- 然后用 `tmp % 10` 取个位,检查完后用 `tmp /= 10` 去掉个位。等 `tmp` 变成 0,就检查完了。
|
||||
- 每找到一个符合条件的数位,就计数一次。例如 `11` 里面有两个 1,要计两次。
|
||||
|
||||
读入 `n` 和 `x` 后,关键循环如下:
|
||||
|
||||
```cpp
|
||||
int ans = 0;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
int tmp = i; // 用副本去拆位,保留外层进度。
|
||||
while (tmp != 0) {
|
||||
int digit = tmp % 10;
|
||||
if (digit == x) {
|
||||
ans++;
|
||||
}
|
||||
tmp /= 10; // 无论是否匹配,都继续处理下一位。
|
||||
}
|
||||
}
|
||||
cout << ans << '\n';
|
||||
```
|
||||
|
||||
这里容易写错的是 `tmp /= 10` 的位置:无论当前数位是否匹配,都要继续处理下一位,所以它应放在 `if` 外面。注意不能直接对 `i` 拆位,外层的进度就会被改掉,反复回到 1。
|
||||
|
||||
## 四、要点总结
|
||||
|
||||
- 外层每轮完成一个完整任务,内层处理其中的重复动作。`for` 和 `while` 可以根据题目需要搭配使用。
|
||||
- 写代码时,先拆任务,再判断循环范围、设置变量,最后安排循环里的语句。
|
||||
- 变量要跨轮保留,就在外层前初始化;每轮要重新准备,就在外层内、内层前初始化。
|
||||
- 每个元素都要做的操作放内层,一轮结束后的收尾放在内层后、外层内,最终结果等外层结束后再处理。
|
||||
- 内层每轮次数相同时,可以用乘法算总次数;次数随外层变化时,就把每轮的次数加起来。
|
||||
- 算过的结果有时还能接着用。像阶乘之和,就可以保留上一项,减少重复计算。
|
||||
@@ -0,0 +1,165 @@
|
||||
### C++ 多重循环与循环嵌套 课后习题
|
||||
|
||||
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
|
||||
#### **一、 填空题**
|
||||
|
||||
1. 在一个循环的循环体里再放入另一个循环,这种写法叫作\_\_\_\_\_\_\_\_。外面的循环叫作\_\_\_\_\_\_\_\_循环,里面的循环叫作\_\_\_\_\_\_\_\_循环。
|
||||
|
||||
2. 下面的程序共输出\_\_\_\_\_\_\_\_行,每一行的输出内容为\_\_\_\_\_\_\_\_。
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
for (int j = 1; j <= 5; j++) { cout << '*'; }
|
||||
cout << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
3. 在下面的程序中,每进入新的一行,内层变量 `j` 都会重新从\_\_\_\_\_\_\_\_开始;换行语句一共执行\_\_\_\_\_\_\_\_次。
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
for (int j = 1; j <= 4; j++) { cout << '*'; }
|
||||
cout << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
4. 数字直角三角形的第一行有 `n` 个数,以后每行少一个。第 `i` 行有\_\_\_\_\_\_\_\_个数(用含 `n`、`i` 的式子表示)。当 `n=7`、`i=3` 时,这一行有\_\_\_\_\_\_\_\_个数。
|
||||
|
||||
5. 编号从 1 开始、换行后接着增加时,应将 `int cnt = 1;` 写在外层循环\_\_\_\_\_\_\_\_(填“之前”或“之内”)。每输出一个编号后,`cnt` 的值应增加\_\_\_\_\_\_\_\_。
|
||||
|
||||
6. 下面的代码执行后,输出结果为\_\_\_\_\_\_\_\_。
|
||||
|
||||
```cpp
|
||||
long long factor = 1;
|
||||
for (int j = 1; j <= 3; j++) { factor *= j; }
|
||||
cout << factor;
|
||||
```
|
||||
|
||||
7. 阶乘之和 $$1!+2!+3!$$ 的值为\_\_\_\_\_\_\_\_。
|
||||
|
||||
8. 已知 `int tmp = 1020;`,则 `tmp % 10` 的结果为\_\_\_\_\_\_\_\_;执行 `tmp /= 10;` 后,`tmp` 的值为\_\_\_\_\_\_\_\_。
|
||||
|
||||
9. 下面的程序在第一行输出 `010203`。第二行的输出内容为\_\_\_\_\_\_\_\_,第三行的输出内容为\_\_\_\_\_\_\_\_;全部循环结束后,`cnt` 的值为\_\_\_\_\_\_\_\_。
|
||||
|
||||
```cpp
|
||||
int n = 3, cnt = 1;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
for (int j = 1; j <= n - i + 1; j++) {
|
||||
if (cnt < 10) { cout << '0'; }
|
||||
cout << cnt; cnt++;
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
10. 下面的代码保留上一项阶乘,避免从头计算。循环结束后,`factor` 的值为\_\_\_\_\_\_\_\_,`ans` 的值为\_\_\_\_\_\_\_\_。
|
||||
|
||||
```cpp
|
||||
long long ans = 0, factor = 1;
|
||||
for (int i = 1; i <= 4; i++) { factor *= i; ans += factor; }
|
||||
```
|
||||
|
||||
#### **二、 单项选择题**
|
||||
|
||||
( ) 1. 下列哪种情况属于循环嵌套?
|
||||
|
||||
A. 先执行一个 `for`,结束后再执行另一个 `for`
|
||||
B. 在一个 `if` 的分支中放入一个 `for`
|
||||
C. 在一个 `for` 的循环体中放入一个 `while`
|
||||
D. 在一个 `for` 的循环体中放入两条输出语句
|
||||
|
||||
( ) 2. 下面输出星号的语句一共执行多少次?
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
for (int j = 1; j <= 3; j++) { cout << '*'; }
|
||||
}
|
||||
```
|
||||
|
||||
<pre>A. 2 次 B. 6 次 C. 3 次 D. 5 次</pre>
|
||||
|
||||
( ) 3. 要输出 3 行、每行 4 个星号,换行语句应放在哪里?
|
||||
|
||||
<pre>A. 内层循环体中 B. 外层循环开始之前
|
||||
C. 外层循环结束之后 D. 内层结束后、外层循环体内</pre>
|
||||
|
||||
( ) 4. 下面的程序输出什么?选项中“/”表示换行,实际输出中没有“/”。
|
||||
|
||||
```cpp
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
for (int j = 1; j <= 3; j++) { cout << j; }
|
||||
cout << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
<pre>A. 123/123 B. 111/222
|
||||
C. 123/456 D. 12/12/12</pre>
|
||||
|
||||
( ) 5. 如果将第 4 题中的 `cout << j;` 改为 `cout << i;`,输出是什么?选项中“/”表示换行。
|
||||
|
||||
<pre>A. 123/123 B. 12/12/12
|
||||
C. 111/222 D. 111/111</pre>
|
||||
|
||||
( ) 6. 数字直角三角形的第一行有 `n` 个数,以后每行少一个。当 `n=6` 时,第 4 行有几个数?
|
||||
|
||||
<pre>A. 2 个 B. 3 个 C. 4 个 D. 6 个</pre>
|
||||
|
||||
( ) 7. 求 $$1!+2!+\cdots+n!$$,且 `1≤n≤20`。为了保存较大的乘积和总和,`factor` 和 `ans` 应使用下列哪种类型?
|
||||
|
||||
<pre>A. char B. bool C. int D. long long</pre>
|
||||
|
||||
( ) 8. 执行下面的代码后,`digit` 和 `tmp` 的值分别是多少?
|
||||
|
||||
```cpp
|
||||
int tmp = 92, digit = tmp % 10;
|
||||
tmp /= 10;
|
||||
```
|
||||
|
||||
<pre>A. 2 和 9 B. 9 和 2 C. 2 和 92 D. 9 和 92</pre>
|
||||
|
||||
( ) 9. 下面的代码中,`cnt++` 一共执行多少次?
|
||||
|
||||
```cpp
|
||||
int cnt = 0;
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
for (int j = 1; j <= i; j++) { cnt++; }
|
||||
}
|
||||
```
|
||||
|
||||
<pre>A. 4 次 B. 8 次 C. 10 次 D. 16 次</pre>
|
||||
|
||||
( ) 10. 统计从 1 到 20 的所有整数中,数字 1 出现的总次数。每个数位都要统计,答案是多少?
|
||||
|
||||
<pre>A. 12 B. 11 C. 10 D. 13</pre>
|
||||
|
||||
#### **三、 判断题 (对的打“√”,错的打“×”)**
|
||||
|
||||
( ) 1. 两个 `for` 循环只要出现在同一个程序里,就构成循环嵌套。
|
||||
|
||||
( ) 2. 外层循环执行一轮时,内层循环的循环体一定只执行一次。
|
||||
|
||||
( ) 3. C++ 根据大括号判断语句属于哪一层循环;把缩进改了,不会改变语句的归属。
|
||||
|
||||
( ) 4. 在星号矩形的写法中,内层为 `for (int j = 1; j <= 4; j++)`,每次外层执行到这条语句时,都会重新执行 `int j = 1`。
|
||||
|
||||
( ) 5. 数字三角形要求编号跨行连续,因此应在每一轮外层循环开始时,将 `cnt` 重新设为 1。
|
||||
|
||||
( ) 6. 用累乘的方法计算阶乘时,将累乘器的初值设为 0,也能得到正确的阶乘。
|
||||
|
||||
( ) 7. 对于本课 `n≤20` 的阶乘之和,乘积和总和可以用 `long long` 保存,但 `long long` 也有上限。
|
||||
|
||||
( ) 8. 逐位统计数字出现次数时,无论当前数位是否等于目标数字,都需要执行 `tmp /= 10;`,继续处理下一位。
|
||||
|
||||
( ) 9. 使用 `printf("%02d", 123)` 输出整数时,只会显示最后两位,结果为 `23`。
|
||||
|
||||
( ) 10. 下面的代码没有在每一项开始时重置 `factor`,最终输出的是 15。
|
||||
|
||||
```cpp
|
||||
long long ans = 0, factor = 1;
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
for (int j = 1; j <= i; j++) { factor *= j; }
|
||||
ans += factor;
|
||||
}
|
||||
cout << ans;
|
||||
```
|
||||
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
### C++ 多重循环与循环嵌套 课后习题参考答案
|
||||
|
||||
本卷共 30 题:填空、选择、判断各 10 题。其中基础题 24 题,占 80%;基础应用与迁移题 6 题,占 20%。每类题的第 1~8 题为基础题,第 9~10 题为应用与迁移题。
|
||||
|
||||
考查范围限于本课的循环嵌套、循环范围、变量初始化、星号矩形、数字三角形、阶乘之和及数位计数。判断题第 9 题的 `%02d` 来自所提供的 PPT 内容。
|
||||
|
||||
#### **一、 填空题**
|
||||
|
||||
| 题号 | 参考答案 | 简要说明 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 循环嵌套(或“多重循环”);外层;内层 | 循环之间存在包含关系 |
|
||||
| 2 | 2;`*****` | 外层执行 2 轮,每轮在同一行输出 5 个星号 |
|
||||
| 3 | 1;3 | 每次进入内层都重新初始化;每轮外层换行一次 |
|
||||
| 4 | `n - i + 1`(或 `n - (i - 1)`);5 | 第 3 行比第一行少 2 个 |
|
||||
| 5 | 之前;1 | 每输出一个编号就增加 1,编号跨行保留 |
|
||||
| 6 | 6 | 累乘结果为 $$1\times1\times2\times3=6$$ |
|
||||
| 7 | 9 | $$1!+2!+3!=1+2+6=9$$ |
|
||||
| 8 | 0;102 | 取出个位 0,再通过整数除法去掉个位 |
|
||||
| 9 | `0405`;`06`;7 | 各行数量为 3、2、1,共输出 6 个编号;最后的变量记录下一个编号 |
|
||||
| 10 | 24;33 | 四轮的阶乘依次为 1、2、6、24,总和为 33 |
|
||||
|
||||
第 4 题中,与参考答案等价的表达式也可判为正确。填写输出内容时,前导零应保留。
|
||||
|
||||
#### **二、 单项选择题**
|
||||
|
||||
| 题号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 答案 | C | B | D | A | C | B | D | A | C | A |
|
||||
|
||||
1. **C。** `while` 放在 `for` 的循环体内,形成包含关系。前后执行的两个循环不属于嵌套。
|
||||
2. **B。** 外层执行 2 轮,每轮内层执行 3 次,共 $$2\times3=6$$ 次。
|
||||
3. **D。** 先输出当前行的全部星号,再换行;换行仍属于外层的任务。
|
||||
4. **A。** 每一行的 `j` 都从 1 开始,依次输出 1、2、3。
|
||||
5. **C。** 第一行中 `i` 始终为 1,输出 `111`;第二行中 `i` 始终为 2,输出 `222`。
|
||||
6. **B。** 第 4 行有 $$6-4+1=3$$ 个数。
|
||||
7. **D。** 常见的 32 位 `int` 无法保存 `13!`。本题范围内的乘积和总和可以用 `long long` 保存。
|
||||
8. **A。** `92 % 10` 得到 2,整数除法 `92 / 10` 得到 9。
|
||||
9. **C。** 每轮内层分别执行 1、2、3、4 次,总共 $$1+2+3+4=10$$ 次,不能直接算成 4×4。
|
||||
10. **A。** 整数 1 贡献一次;10~19 的十位共贡献 10 次;11 的个位再贡献一次;20 不贡献,总共 $$1+10+1=12$$ 次。
|
||||
|
||||
#### **三、 判断题**
|
||||
|
||||
| 题号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 答案 | × | × | √ | √ | × | × | √ | √ | × | √ |
|
||||
|
||||
1. **×。** 要有包含关系:一个循环位于另一个循环的循环体内。
|
||||
2. **×。** 内层执行多少次由自己的初始化、条件、更新及循环体决定。本课矩形例子中,一轮外层对应内层的多次执行。
|
||||
3. **√。** 大括号决定归属,缩进帮助阅读。
|
||||
4. **√。** 每次执行到内层 `for`,都先执行其初始化部分。
|
||||
5. **×。** `cnt` 应在外层之前初始化;放进外层会让每行编号重新从 1 开始。
|
||||
6. **×。** 从 0 开始累乘,之后乘任何数仍然为 0。累乘器应从 1 开始。
|
||||
7. **√。** `long long` 足以保存本题范围内的结果,但不能保存任意大的整数。
|
||||
8. **√。** 不匹配的数位也已经检查完毕,应去掉它并继续。把去位语句放进匹配成功的分支,可能导致程序停留在同一数位。
|
||||
9. **×。** 2 表示最小宽度,不会截断超过两位的整数,实际输出为 `123`。
|
||||
10. **√。** 这段程序的结果不是正确的阶乘之和,变量变化如下。
|
||||
|
||||
| 外层 `i` | 进入本轮时的 `factor` | 内层计算后的 `factor` | 累加后的 `ans` |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 1 | `1 × 1 = 1` | 1 |
|
||||
| 2 | 1 | `1 × 1 × 2 = 2` | 3 |
|
||||
| 3 | 2 | `2 × 1 × 2 × 3 = 12` | 15 |
|
||||
|
||||
这里沿用了“每项从头乘 1 到 i”的内层循环,所以每项开始时应把 `factor` 重置为 1。若改用保留上一项阶乘的方法,则应删除内层循环,每轮只执行 `factor *= i`。
|
||||
Reference in new issue
Block a user