Initial commit: C++ courseware

This commit is contained in:
xiaolinquan committed 2026-11-05 22:51:02 +08:00
commit 4a4ee7c287
1789 files changed
+373489

No files matched your search

@@ -0,0 +1,299 @@
# C++ `while` 循环 讲义
## 1. 课程引言
### 1.1 问题引入
**任务**:让计算机在屏幕上换行输出 1、2、3、4、5。
很简单,我们利用**顺序结构**很快就能写出这样的代码:
```cpp
int main() {
cout << 1 << endl;
cout << 2 << endl;
/* ... 略 ... */
cout << 5 << endl;
return 0;
}
```
“So easy!” 很多同学可能会这么想。但是,请看下一个任务:
**升级版任务**:让计算机换行输出 1 到 100 之间的每一个整数。
这时候,如果继续使用顺序结构,我们需要把 `cout` 语句复制粘贴 100 遍。如果要求是输出 1 到 10000 呢?这种简单、重复、有规律的工作,人类来做既枯燥又易错。
**核心观点:计算机非常适合且极其擅长做大规模重复的事情!** 为了“解放”我们,我们需要一种新的程序结构——**循环结构**。
---
## 2. 核心思想:什么是 while 循环?
循环,顾名思义,就是让一段代码能够“转着圈”地反复执行。`while` 循环是其中最基础、最直观的一种。
### 2.1 生活中的 while 逻辑
大家对这句话一定不陌生,妈妈在你写作业前说:
**“只要(while)你还没完成所有作业(循环条件),你就必须一直写(循环体)。”**
这句话包含了 `while` 循环的核心要素:
* **循环条件**:还没完成所有作业。
* **循环体(重复的动作)**:一直写作业。
你每写完一门作业,都会检查一次条件(作业写完了吗?)。如果没有,继续写;直到条件不再成立(作业写完了),循环结束,你可以出去玩了。
### 2.2 if 与 while 的区别
* **`if` 语句(分支)**:像一个岔路口,判断一次条件,满足就做,做完就接着往下走。
* **`while` 语句(循环)**:在执行完“做某事”之后,会**返回到原点再次判断条件**,形成一个圈。只要条件为真,这个圈就会一直转下去。
### 2.3 while 循环的执行流程(重点)
```cpp
while (条件表达式) {
// 循环体:重复执行的代码
}
```
1. **第一步(判断)**:计算 `条件表达式` 的值。
2. **第二步(决策)**:若为 `true`(真),进入大括号执行代码;若为 `false`(假),直接跳过整个 `while` 结构。
3. **第三步(执行)**:从上到下执行循环体内的所有语句。
4. **第四步(返回)**:执行完毕后,**自动返回第一步**,重新判断条件。
---
## 3. 代码实战:编写 while 循环的模板
如何编写一个正确的 `while` 循环:
* **第一步:初始化循环控制变量。** 在循环开始前,设立一个“标记变量”(如计数器)。
* **第二步:设置循环继续的条件。** 写下让循环“继续下去”的规则。
* **第三步:更新控制变量。** 在循环体内部改变“标记变量”的值,使其朝着“不满足条件”的方向发展。
### 3.1 解决 输出 1 到 100 的任务
```cpp
int main() {
// 初始化:创建一个循环控制变量 i,让它从 1 开始
int i = 1;
// 条件:只要 i 小于或等于 100,就继续循环
while (i <= 100) {
// 循环体
cout << i << endl; // 重复的任务:打印当前数字
// 更新:让 i 的值加 1,为下一轮判断做准备
i++;
}
return 0;
}
```
---
## 4. 案例剖析
### 案例一:乘地铁回家
**场景**:小六乘坐地铁回家,世纪大道站上车,梦站下车。他想知道途经了多少站。
**逻辑梳理**:
* **初始化**:上车,计数器设为 0。
* **条件**:`while (还没有到梦站)`
* **循环体**:再坐一站路;计数器 +1。
* **结束**:下车,输出计数器。
**代码思路**:
```cpp
上车;
计数器设置为0;
while( 没有到梦站 ){
再做一站路;
计数器+1;
}
下车;
输出计数器;
```
### 案例二:一尺之棰(数学计算 - 书本 P51 例 4-4)
**题目**:《庄子》曰:“一尺之棰,日取其半,万世不竭”。输入木棍初始长度 $a$,每天锯掉一半(向下取整),求第几天木棍长度会变为 1?
**分析**:这题很适合 `while` 循环,因为我们**不知道具体要循环多少次**,我们只知道**循环结束的条件**。
```cpp
int a;
cin >> a;
int day = 1; // 第一步:初始化天数
while (a > 1) { // 第二步:循环条件,只要长度还大于1就继续砍
a = a / 2; // 第三步:更新变量1(木棍变短)
day++; // 第三步:更新变量2(天数增加)
}
cout << day << endl; // 循环结束后输出结果
```
**如果缺了 `a = a / 2;`,木棍永远那么长,永远砍不完;如果缺了 `day++;`,时间就静止了天数永远都是1。**
### **案例三:累加**
#### **3.1 题目描述**
编写一个程序,让用户输入一个正整数 `n`,然后计算 `s = 1 + 2 + 3 + … + n` 的值,并输出最终结果 `s`。
**输入/输出样例:**
* 如果用户输入 `3`,程序应该计算 `1 + 2 + 3`,最终输出 `6`。
* 如果用户输入 `5`,程序应该计算 `1 + 2 + 3 + 4 + 5`,最终输出 `15`。
* 如果用户输入 `100`,程序应该计算 `1 + 2 + ... + 100`,最终输出 `5050`。
#### **3.2 思路分析**
这个问题我们无法用一行代码直接得出答案。我们先想一想,如果不用程序,我们的大脑是如何计算 `1 + 2 + 3 + 4 + 5` 的?
过程很可能是这样的:
1. 先算 `1 + 2`,在脑中记下结果 `3`。
2. 再用刚才记下的 `3`,去加上 `3`,得到 `6`,并用这个新结果 `6` 更新我们脑中的记忆。
3. 接着,用记下的 `6`,去加上 `4`,得到 `10`,再次更新记忆。
4. 最后,用记下的 `10`,去加上 `5`,得到最终答案 `15`。
在这个过程中关键的一点是:我们的大脑里始终**“记着”**一个**“当前的和”**。这个“当前的和”在计算过程中是不断变化的、滚雪球式增长的。
程序也需要这样一个能“记事儿”的角色。在编程中,我们给它起了一个非常形象的名字——**累加器** 。
#### **3.3 累加器的运作流程**
基于上面的分析,我们可以把核心思路拆解成以下几个步骤:
1. **准备一个“累加器”**:我们需要一个整型变量,专门用来存放累加的和。我们叫它 `sum`。在计算开始前,这个“和”是空的,所以 `sum` 的初始值必须是 **0**。
2. **准备一个“计数器”**:我们需要另一个变量,依次代表我们要加的数:1, 2, 3, ..., n。这个我们很熟悉了,就是循环控制变量,我们叫它 `i`。它应该从 `1` 开始。
3. **建立循环**:我们需要一个 `while` 循环,来驱动计数器 `i` 从 `1` 稳定地增长到 `n`。循环的条件自然就是 `i <= n`。
4. **执行累加动作**:在循环的每一次中,我们都执行最核心的累加动作:`sum = sum + i;`。这行代码的含义是:取出 `sum` 的当前值,和 `i` 的当前值相加,得到的**新和**再存回给 `sum` 变量自己,完成一次自我更新。
5. **更新计数器**:在累加动作之后,要让 `i` 的值加1 (`i++;`),这样下次循环才能累加下一个数。
这个过程就像滚雪球,`sum` 是那个雪球,`i` 是每一捧新雪。循环一次,雪球就裹上一层新雪,变得更大。当 `i` 遍历完所有数,雪球的最终大小就是我们所求的总和。
#### **3.4 代码实现与解读**
```cpp
int main() {
// 让用户输入目标数字 n
int n;
cin >> n;
// 初始化
int sum = 0; // 累加器 sum 初始值为 0
int i = 1; // 计数器 i 从第一个要加的数 1 开始
// 循环条件:只要 i 还没有超过 n,就继续累加
while (i <= n) {
// 将当前的数 i 累加到 sum 中
sum = sum + i; // 也可以简写为: sum += i;
// 让 i 增加 1,准备加下一个数
i++;
}
// 循环结束后,sum 中存放的就是 1 到 n 的最终累加结果
cout << sum << endl;
return 0;
}
```
**代码解读:**
* `int sum = 0;`:如果 `sum` 没有被初始化,它的值将是随机的,最终结果自然也是错误的。而`0` 是加法运算的起点。
* `while (i <= n)`:循环的边界条件是 `i <= n`,确保从 `1` 到 `n`(包括 `n` 本身)的每一个数都被加进了 `sum` 中。
* `sum = sum + i;`:它读取 `sum` 的旧值,进行计算,然后用新值覆盖旧值。
* `cout << sum << ...;`:这行代码位于 `while` 循环的**外部**。因为我们只关心最终的那个总和,而不是中间过程的每一个“当前和”。如果你把这行代码错误地放进了循环体内部,会发生什么呢?可以动手试一试,区别一下“循环内外”到底有什么不同。
---
## 5. 深化理解
### 5.1 死循环
如果我们在输出 1 到 100 的代码中漏掉了 `i++;` 会怎样?
```cpp
int i = 1;
while (i <= 100) {
cout << "救命!" << endl;
}
```
**运行结果**:`i` 的值永远是 1,`1 <= 100` 永远成立。屏幕会被“救命!”一直刷屏,程序永不停止。
**所以务必确保循环体内部有代码能让循环条件最终变为 `false`!**
### 5.2 C++ 中的“真”与“假”
在 C++ 中,**0 为假(`false`),其他所有非 0 数都为真(`true`)**。
* `if (0)` 或 `while (0)`:条件为假,里面的代码**绝对不会**执行。
* `while (1)` 或 `while (999)`:条件永远为真,这是一个标准的无限循环(死循环)写法,通常需要配合内部的 `break` 语句来跳出(后续课程学习)。
### 5.3 如何实现循环 $n$ 次?
我们要输出 $n$ 个 `*`,有许多种写法(例如从 $n$ 递减到 1),但最通用、最标准的“约定”是:**从 0 开始,循环 $n$ 次**。
```cpp
int n;
cin >> n;
int i = 0; // 从 0 开始
while (i < n) { // 条件为小于 n (注意不是小于等于)
cout << "*" << endl;
i++;
}
```
**为什么推荐这种写法?**
1. **与数组契合**:C++ 中的数组索引是从 0 开始的,习惯 `0` 到 `n-1` 的模式,未来学习数组时可以进行衔接。
2. **逻辑清晰**:`[0, n)` 是一个左闭右开的区间(从 `0~n-1` ),区间内元素的个数正好是 $n$,不多也不少。
---
## 6. 遍历 + 筛选 模式
掌握了基础循环,我们来看一个解决大量编程问题的核心基础框架:**遍历与筛选**。
**任务**:输入区间起点 $n1$ 和终点 $n2$,打印出这个区间内的**所有偶数**。
用一个生活中的例子想象一下,你现在推着购物车在超市的货架通道里走。你的任务是:**“在第 1 排到第 10 排货架之间,把所有打半价的商品挑出来。”**
在这个场景中:
* **货架通道的范围(第 1 排到第 10 排)**:这就是我们的**区间**($n1$ 到 $n2$)。
* **你顺着通道一排排走过,目光扫过每一件商品**:这个动作就叫做【遍历】。不管买不买,你都必须逐一“看”过去,不能漏掉。在代码中,这就是 `while (i <= n2)` 和不断向前看的 `i++`,而你当前正在看的这个物品,就相当于是循环变量 `i`。
* **你拿起一件商品,看一眼标签上的价格**:这个动作叫做【筛选】。如果是半价,放进购物车(执行操作);如果不是半价,放回原处(什么都不做)。在代码中,这就是 `if` 判断。
```cpp
int n1, n2;
cin >> n1 >> n2;
int i = n1; // 初始化循环变量 i,因为 n1 是起点,故初始化为 n1 让循环变量从起点开始
while (i <= n2) { // 步骤1:【遍历】只要循环变量 i 没越过终点,就挨个检查
if (i % 2 == 0) { // 步骤2:【筛选】判断当前数字是否为偶数
cout << i << endl; // 满足条件才处理(输出)
}
i++; // 步骤3:更新,无论是不是偶数,都要前进到下一个数字
}
```
为什么说它是“解决大量编程问题的核心基础框架”?因为掌握了这个模式,你只需要修改 `if` 里面的**鉴定条件**,就能解决成百上千种不同的问题,而外层的 `while` 遍历框架**完全不需要改动**!
如果把刚才代码里的 `if` 条件换一下,我们可以实现:
* **找奇数**:`if (i % 2 != 0)` (除以 2 余数不为 0)
* **找 5 的倍数**:`if (i % 5 == 0)`
* **找个位数是 7 的数字**:`if (i % 10 == 7)` (任何正整数对 10 取余,结果就是它的个位数)
### 遍历 + 筛选 模式思路步骤:
1. **遍历**:使用 `while` 循环,借助自增循环变量 `i`,挨个访问想要查找区间内的每一个元素。
2. **筛选**:在循环体内部,嵌套一个 `if` 条件语句,提取出我们感兴趣的数据(如偶数、素数、最大值等)。
@@ -0,0 +1,35 @@
### C++ while 循环 课后作业(一) - **答案**
#### **一、 填空题**
1. 循环
2. 循环条件,循环体
3. 条件表达式,循环体
4. 初始化循环控制变量,继续,更新控制变量
5. 死循环
#### **二、 单项选择题**
1. C 2. B 3. B 4. B 5. B 6. D 7. B 8. C 9. C 10. B
**选择题解析:**
4. 当 `a` 为 8:
第一次循环:`a > 1` (8>1) 成立,`a` 变为 `8/2=4`。
第二次循环:`a > 1` (4>1) 成立,`a` 变为 `4/2=2`。
第三次循环:`a > 1` (2>1) 成立,`a` 变为 `2/2=1`。
此时 `a` 为 1,`a > 1` 不再成立,循环结束。共执行了 3 次。
6. 循环开始前 `i` 的值为 `10`,循环条件 `i < 10` (10 < 10) 直接为假,所以循环体一次都不会执行。
8. 选项 C 中,`i` 的值恒为 `1`,循环条件 `i <= 5` 永远成立,但循环体内没有改变 `i` 的值的语句,导致死循环。
#### **三、 判断题**
1. × (如果条件初始即为假,一次也不执行)
2. √
3. √ (在 `i` 是循环条件变量的前提下)
4. √
5. √
6. × (如果删除,`day` 将永远是初始值 1)
7. √
8. × (`while(0)` 条件为假,循环体一次都不会执行)
9. × (变量名可自定义,但 `i` 是常用惯例)
10. × (判断发生在执行之前)
@@ -0,0 +1,95 @@
### C++ while 循环 课后作业(一)
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. 当我们需要计算机做大量、重复、有规律的工作时,应该使用 \_\_\_\_\_\_\_\_\_\_ 结构。
2. `while` 循环的两个核心要素是 \_\_\_\_\_\_\_\_\_\_ 和 \_\_\_\_\_\_\_\_\_\_。
3. `while` 循环的语法结构是 `while` (\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_) {\_\_\_\_\_\_\_\_\_\_\_\_}。
4. 编写一个正确的 `while` 循环,通常需要三个步骤(填写 **更新循环控制变量、初始化循环控制变量、继续** ):
- 循环开始前,______\_\_\_\_\_\_\_\_\_\_______。
- 在 `while()` 中设置让循环 _________\_\_\_\_\_\_\_\_\_\____ 的条件表达式。
- 在循环体内部 ______\_\_\_\_\_\_\_\_\_\_______,使其最终能让循环条件变为假。
5. 如果 `while` 循环的条件判断语句一直为真,程序将会陷入 ______\_\_\_\_\_\_\_\_\_\______。
#### **二、 单项选择题**
( ) 1. 下面哪段描述最适合用 `while` 循环来表达?
A. 如果今天下雨,我就带伞出门
B. 当我的闹钟响起,我就起床
C. 只要我的游戏币还大于0,我就要一直玩游戏
D. 我的年龄是18岁
( ) 2. `if` 语句和 `while` 语句最主要的区别在于?
A. `if` 能做判断,`while` 不能
B. `while` 语句结束后,可能会回头重新判断条件,而 `if` 不会
C. `if` 的执行效率比 `while` 高
D. `while` 必须有大括号 `{}`,`if` 可以没有
( ) 3. 观察下面的代码,它一共会输出多少个 `*`?
```cpp
int i = 1;
while (i <= 5) {
cout << "*" << endl;
i++;
}
```
​ A. 4 个 B. 5 个 C. 6 个 D. 无限个
( ) 4. 对于 “一尺之棰,日取其半” 的问题,如果木棍初始长度 `a` 为 `8`,`day` 初始为 `1`,下面代码的循环体会被执行几次?
```cpp
while (a > 1) {
a = a / 2;
day++;
}
```
​ A. 2 次 B. 3 次 C. 4 次 D. 8 次
( ) 5. 在 `while (条件表达式)` 结构中,`条件表达式` 的作用是?
A. 声明一个新变量
B. 决定循环是否继续执行
C. 定义循环需要执行的具体操作
D. 表示循环体执行结束
( ) 6. 考虑代码 `int i = 10; while(i < 10) { cout << "Hello" << endl; }`,屏幕上会输出什么?
A. 输出 1 个 "Hello"
B. 输出 9 个 "Hello"
C. 输出无限个 "Hello"
D. 什么也不输出
( ) 7. 要让 `while` 循环最终能停下来,最关键的操作是?
A. 在循环前初始化变量
B. 在循环体内有让循环条件最终变为 `false` 的操作
C. 在循环体里有输出语句
D. 把循环条件写得足够简单
( ) 8. 以下哪个 `while` 循环会导致死循环?
A. `int i=0; while(i<5){ i++; }`
B. `int i=5; while(i>0){ i--; }`
C. `int i=1; while(i<=5){ cout << i; }`
D. `int i=5; while(i>5){ i++; }`
( ) 9. 在 `while` 循环的执行流程中,第一步是?
A. 执行循环体内的代码
B. 更新循环控制变量
C. 计算条件表达式的值
D. 返回程序开头
( ) 10. 对于讲义中“乘地铁回家”的案例,我们需要把“再坐一站路”这个动作放在 `while` 循环里,体现了循环的什么特点?
A. 必须有初始值 B. 重复执行的动作 C. 循环必须有出口 D. 循环的判断条件
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. `while` 循环的循环体代码至少会被执行一次。
( ) 2. `while (i <= 100)` 和 `while (100 >= i)` 这两个循环条件在逻辑上是等价的。
( ) 3. 如果缺少类似于 `i++` 这样的用来更新循环变量的语句,`while` 循环肯定会变成死循环。
( ) 4. `if` 语句是选择一个分支执行,而 `while` 语句是只要条件满足就一直重复执行。
( ) 5. 计算机非常擅长处理简单、重复、有规律的工作。
( ) 6. 在“一尺之棰”问题中,`day++` 这个语句即使被删除,程序也能计算出正确的天数。
( ) 7. `while` 循环的大括号 `{}` 内的所有语句,在一次循环中都会从上到下被执行。
( ) 8. `while(1)` 和 `while(0)` 都会导致死循环。
( ) 9. 循环控制变量只能使用 `i` 这个名字。
( ) 10. `while` 循环结构中,循环条件的判断发生在循环体执行之后。
@@ -0,0 +1,33 @@
### C++ while 循环 课后作业(二) - **答案**
#### **一、 填空题**
1. 累加器,0
2. `sum += i;`
3. 0,非 0
4. 遍历与筛选
5. `i < n`
#### **二、 单项选择题**
1. C 2. C 3. D 4. C 5. B 6. B 7. C 8. C 9. D 10. B
**选择题解析:**
1. `sum` 的变化过程: `0+1=1` -> `1+2=3` -> `3+3=6` -> `6+4=10`。
2. `cout` 在循环体内,每次 `sum` 的值更新后都会立即被输出。
6. 计算 `2 + 4 + 6 + 8 + 10`,结果是 `30`。
8. `count` 变化过程:`10` -> `9` -> `8` -> `7` -> `6` -> `5`。当 `count` 为 `5` 时,`5 > 5` 为假,循环停止。
9. 当 `i` 是一个奇数时(例如 `i=1`),`if` 条件不满足,循环体内的 `i++` 无法被执行,`i` 的值永远停留在 `1`,导致死循环。
#### **三、 判断题**
1. √
2. √ (未初始化的变量值是随机的,结果自然是无意义的)
3. × (应该用逻辑与 `&&`,`||` 是或者的意思)
4. √
5. √
6. √
7. × (`while`尤其适用于不知道具体次数,只知道结束条件的场景)
8. × (两者在功能和现代编译器的优化下没有性能差异)
9. × (是为了逻辑正确性,避免使用一个随机的初始值进行计算)
10. √
@@ -0,0 +1,99 @@
### C++ while 循环 课后作业(二)
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. 在编程中,我们用来存放滚雪球式增长的和的变量,通常被称为 \_\_\_\_\_\_\_\_\_\_\_\_\_,它的初始值在进行加法运算时通常设为 \_\_\_\_\_\_\_\_\_\_。
2. `sum = sum + i;` 这行累加代码可以简写为 \_\_\_\_\_\_\_\_\_\_\_\_\_\_。
3. 在 C++ 中,数值 \_\_\_\_\_\_\_ 被视为“假”(false),而所有 \_\_\_\_\_\_\_ 的数值都被视为“真”(true)。
4. 使用 `while` 循环挨个访问某个区间内的每一个元素,并在循环内部用 `if` 语句挑出符合特定条件的元素,这种编程模式被称为\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_。
5. 想要实现“循环n次”的效果,那么当循环控制变量 `i` 从 `0` 开始时,`while()`中的循环条件则应该写为\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_。
#### **二、 单项选择题**
( ) 1. 分析以下代码,最终输出的结果是什么?
```cpp
int sum = 0, i = 1;
while (i <= 4) {
sum = sum + i;
i++;
}
cout << sum << endl;
```
​ A. 4 B. 5 C. 10 D. 15
( ) 2. 如果将上一题中的 `cout << sum << endl;` 语句放到 `while` 循环体的内部,程序的输出会变成?
A. 只输出 10 B. 什么都不输出 C. 依次换行输出 1, 3, 6, 10 D. 程序会报错
( ) 3. 要打印出 1 到 100 之间所有“个位数是7”的数字,循环体内部的 `if` 判断条件应该写成?
A. `if (i == 7)` B. `if (i % 7 == 0)` C. `if (i / 10 == 7)` D. `if (i % 10 == 7)`
( ) 4. 运行 `int n=5; int i=0; while(i<n){ cout << "*"; i++; }` 这段代码,会输出多少个 `*`?
A. 0 个 B. 4 个 C. 5 个 D. 6 个
( ) 5. 关于 `while(-1)` 这个循环条件,下列说法正确的是?
A. 条件为假,循环一次都不会执行
B. 条件为真,会形成死循环
C. 程序会因为负数而出错
D. 循环只会执行一次
( ) 6. 以下代码执行后最终 `sum` 的值是?
```cpp
int sum = 0;
int i = 1;
while (i <= 10) {
if (i % 2 == 0) { sum = sum + i; }
i++;
}
```
​ A. 55 B. 30 C. 25 D. 20
( ) 7. 在“遍历+筛选”模式中,`if` 语句扮演的角色是?
A. 确保循环能够停止 B. 驱动循环变量从起点到达终点
C. 定义筛选规则,挑出所需要的数据 D. 初始化累加器
( ) 8. 观察下面的代码,当循环结束后 `count` 的值是多少?
```cpp
int count = 10;
while (count > 5) { count--; }
```
​ A. 10 B. 6 C. 5 D. 4
( ) 9. 考虑以下代码,它的问题是什么?
```cpp
int i = 1;
while (i <= 100) {
if (i % 2 == 0) {
cout << i << endl;
i++;
}
}
```
​ A. 这是一个死循环 B. 它会漏掉一些偶数
​ C. 它只能打印出奇数 D. 它在遇到奇数时会陷入死循环
( ) 10. 要计算 1 到 100 以内所有 3 的倍数之和,下面代码片段的 `???` 处应填写?
```cpp
int sum = 0;
int i = 1;
while (i <= 100) {
if (???) { sum += i; }
i++;
}
```
​ A. `i / 3 == 0` B. `i % 3 == 0`
​ C. `i % 3 != 0` D. `i * 3 == 0`
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. `while(x)` 和 `while(x != 0)` 这两个循环条件是等价的。
( ) 2. 在累加求和的程序中,如果累加器变量 `sum` 没有被初始化为 0,程序最终的计算结果一定是错误的。
( ) 3. 要筛选出 1 到 100 之间能同时被 2 和 3 整除的数,`if` 条件应写为 `if (i % 2 == 0 || i % 3 == 0)`。
( ) 4. 执行代码 `int i = 0; while(i < 5) { cout << i; i++; }` 会在屏幕上打印出 `01234`。
( ) 5. `while(1)` 会创建一个无限循环,是因为 C++ 认为 `1` 是一个“真”值。
( ) 6. 在“遍历+筛选”模式中,`i++` 必须放在 `if` 语句的外面(但在循环体内),这样才能保证无论当前数字是否被选中,都能继续检查下一个数字。
( ) 7. `while` 循环只适用于我们提前知道循环次数的场景。
( ) 8. `sum += i;` 的执行效率比 `sum = sum + i;` 更高。
( ) 9. 初始化累加器 `sum` 为 0 的主要目的是为了让代码能够通过编译。
( ) 10. `i = 0; while(i < n)` 和 `i = 1; while(i <= n)` 都能实现循环 n 次的效果。
@@ -0,0 +1,31 @@
### C++ do-while 循环 课后作业 - **答案**
#### **一、 填空题**
1. 执行,判断
2. 1 (或一)
3. 分号 (`;`)
4. `do-while`
5. 种子,头文件
#### **二、 单项选择题**
1. C 2. B 3. C 4. C 5. B 6. B 7. C 8. B 9. D 10. D
**选择题解析:**
2. `i` 初始值为10。先执行循环体,输出1个"Hello",然后`i`变为11。此时判断条件`i < 10` (11 < 10) 为假,循环结束。
6. `n` 初始值为5。先执行循环体,输出 "5 ",然后`n`变为4。此时判断条件`n > 5` (4 > 5) 为假,循环结束。
7. `srand` 只需要在程序开始时调用一次,为整个随机数序列播下种子。如果放在循环内部,可能会因为 `time(0)` 在一秒内返回值相同而导致短时间内生成的随机数都一样。
#### **三、 判断题**
1. √
2. × (当 `while` 循环条件初始为假,一次都不执行,`do-while` 无法模拟这种情况)
3. × (范围是 `[0, 99]`)
4. √
5. √
6. ×
7. √
8. √
9. √
10. √
@@ -0,0 +1,89 @@
### C++ do-while 循环 课后作业
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. `do-while` 循环的核心思想是“先 \_\_\_\_\_\_\_\_\_\_\_,后 \_\_\_\_\_\_\_\_\_\_\_”。
2. 与 `while` 循环不同,`do-while` 循环的循环体代码保证至少会被执行 ______ 次。
3. `do-while` 循环的语法结构在末尾的 `while(条件)` 之后必须有一个 ________。
4. 在猜数字游戏中,因为玩家无论如何都需要先输入一次猜测的数字,所以使用 \_\_\_\_\_\_\_\_\_\_\_ 循环比 `while` 循环更合适。
5. 在 C++ 中,为了生成每次运行程序时都不同的随机数,我们需要使用 `time(0)` 函数来获取当前的\_\_\_\_\_\_\_\_\_\_\_,并通过`srand()`函数设置随机数 \_\_\_\_\_\_\_\_\_\_\_,为了使用这两个函数工具,需要包含 `<ctime>` 和 `<cstdlib>` 两个头文件。
#### **二、 单项选择题**
( ) 1. 关于 `do-while` 和 `while` 循环,下列说法正确的是?
A. 两者没有任何区别
B. `while` 循环的循环体至少执行一次
C. `do-while` 循环的循环体至少执行一次
D. `do-while` 循环先判断条件再执行
( ) 2. 观察下面的代码,它会输出多少个 "Hello"?
```cpp
int i = 10;
do {
cout << "Hello" << endl;
i++;
} while (i < 10);
```
A. 0 个 B. 1 个 C. 10 个 D. 无限个
( ) 3. 哪个场景最适合使用 `do-while` 循环?
A. 遍历一个已知长度的数组
B. 计算 1 到 100 的累加和
C. 程序启动后,先显示菜单让用户选择,如果选择无效则重新显示
D. 从 100 倒数到 1
( ) 4. 要生成一个 `[1, 50]` 范围内的随机整数,正确的表达式是?
A. `rand() % 49` B. `rand() % 50`
C. `rand() % 50 + 1` D. `rand() % 51`
( ) 5. 在猜数字游戏中,`do-while` 循环的循环条件通常是?
A. `guess == ans` B. `guess != ans`
C. `guess > ans` D. `guess < ans`
( ) 6. 分析以下代码,最终输出结果是什么?
```cpp
int n = 5;
do {
cout << n << " ";
n--;
} while (n > 5);
```
A. 什么都不输出 B. 5 C. 5 4 3 2 1 D. 导致死循环
( ) 7. 关于 `srand(time(0));` 这行代码,下列描述错误的是?
A. 它的作用是初始化随机数种子 B. `time(0)` 提供一个不断变化的值
C. 它应该被放在 `do-while` 循环的内部,以保证每次循环都足够随机
D. 它能确保程序每次运行时,生成的随机数序列都不同
( ) 8. `do-while` 循环的条件判断发生在哪个时间点?
A. 每次循环体执行之前
B. 每次循环体执行之后
C. 仅在整个循环开始前判断一次
D. 仅在循环体执行到一半时
( ) 9. 以下哪个 `do-while` 的语法是正确的?
A. `do while(x<5) { ... }`
B. `do { ... } while x<5;`
C. `do { ... } while(x<5)`
D. `do { ... } while(x<5);`
( ) 10. 如果有一个程序需要反复验证用户输入的密码,直到输入正确为止,最合适的循环结构是?
A. `if-else` 结构
B. `while` 循环,并在循环外先获取一次输入
C. `do-while` 循环
D. 选项 B 和 C 都可以,但 C 更简洁优雅
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. `do-while` 循环的循环条件即使初始就为 `false`,循环体也依然会执行一次。
( ) 2. 在任何情况下,一个 `while` 循环都可以被等价地改写为 `do-while` 循环。
( ) 3. `rand() % 100` 生成的随机数范围是 `[0, 100]`,即包含0和100。
( ) 4. 在 `do { ... } while(条件表达式);` 结构中,在执行完循环体后,`条件表达式` 变为 `false` 时循环终止。
( ) 5. 因为 `do-while` 循环体至少执行一次,所以它永远不会导致“一次都不执行”的情况。
( ) 6. `srand(time(0));` 这行代码的作用是生成一个随机数。
( ) 7. `do-while` 循环适合于需要先获取用户输入再进行判断的逻辑。
( ) 8. 如果忘记了 `do-while` 语句末尾的分号,程序将无法通过编译。
( ) 9. 在一个 `do-while` 循环体内,必须有能够影响到循环条件的语句,否则可能导致死循环。
( ) 10. `while(guess != ans)` 和 `while(!(guess == ans))` 这两个循环条件是完全等价的。
@@ -0,0 +1,193 @@
## C++ `do-while` 循环 讲义
### **1. 引言**
#### **1.1 问题引入:书本例 4-5 猜数游戏**
想象一下,让机器人在心里想一个1到100之间的整数,然后让大家来猜。
* 你每猜一个数,会给你提示:“太大了 (Too large)” 或 “太小了 (Too small)”。
* 如果你猜中了,它会说:“猜对了 (You are right!!)”。
* 只要没猜对,你就要一直猜下去。
这个“一直猜下去”的过程,在编程中应该如何实现呢?
使用我们**循环**!但用我们之前学过的 `while` 循环,会遇到一个小小的问题。
`while` 循环的特点是“**先判断,后执行**”。它的逻辑是:
`while (猜的数 != 答案) { // 让你继续猜 }`
这里问题就来了:在第一次进入循环,准备进行判断时,你**还根本没有猜过任何数字**!`猜的数` 这个变量里没有有效的值,这个判断也就失去了意义。
这就引出了我们今天要学习的 `do-while` 循环。它专门用来解决这类“**无论如何,都得先做一次**”的问题。除了猜数游戏,还有很多类似场景:
* **ATM取款**:你必须先输入一次密码,银行系统才能去验证它是否正确。
* **游戏菜单**:程序至少要先显示一次菜单(开始游戏、加载存档、退出),你才能进行选择。
* **输入验证**:程序至少要先让你输入一次,才能判断你的输入是否合规。
---
### **2. 核心思想**
#### **2.1 `while` 循环**
我们先尝试用 `while` 循环来解决猜数问题,看看会发生什么。
```cpp
int main() {
int ans = 42; // 假设答案是42
int guess; // 准备存放你猜的数
// 问题:第一次判断时,guess 是多少?
while (guess != ans) {
cout << "请输入你猜的数字: ";
cin >> guess;
// ...后续判断大小的逻辑...
}
return 0;
}
```
在进入 `while` 的那一刻,`guess` 变量没有被初始化,它里面是一个不确定的值。用一个不确定的值去和 `ans` 比较,结果是完全不可预测的。
为了让 `while` 循环能跑起来,我们需要做一些妥协,比如在循环外先写一次输入代码,像这样:
```cpp
// ...
int ans = 42;
int guess;
cout << "请输入你猜的数字: ";
cin >> guess; // 在循环外先猜一次
while (guess != ans) {
// ...判断大小的逻辑...
cout << "请再猜一次: ";
cin >> guess; // 在循环内又写了一遍
}
// ...
```
但是这样做的话, `cin >> guess;` 这句代码重复了,对于这种先执行,后判断的流程,C++有更加优雅的方式完成。
#### **2.2 `do-while` **
`do-while` 循环就是“**先执行,后判断**”。它保证循环体内的代码**至少会被执行一次**。
它的语法结构如下:
```cpp
do {
// 循环体
// 这里的代码保证,无论如何都会先被执行一次。
} while (循环条件); // 注意这里的分号
```
* **`do`**:一个关键字,代表循环体开始,并且不管三七二十一,先把 `{}` 里面的代码做一遍!
* **`{ ... }`**:循环体,和 `while` 循环一样,是需要重复执行的代码。
* **`while (循环条件);`**:在循环体执行**之后**,才在这里进行条件判断。如果条件为真,就跳回到 `do` 的位置,开始下一次循环;如果为假,则循环结束,继续执行后面的代码。
#### **2.3 步骤拆解**
`do-while` 的执行流程我们可以拆解为以下几步:
1. **无条件执行**:程序遇到 `do`,先直接进入 `{}` 执行循环体内的所有语句。
2. **事后判断**:循环体执行完毕后,来到 `while(条件)` 这一行,计算括号内条件表达式的真假。
3. **决策**:
* 如果条件为 **真 (true)**,程序跳转回 `do` 关键字处,准备开始新一轮的循环(重复步骤1和2)。
* 如果条件为 **假 (false)**,循环正式结束。程序会越过这个循环结构,去执行 `while(...);` 后面的代码。
---
### **3. 实例**
#### **3.1 具体案例**
假设小洛机器人心里想的数字是 **42**,那么我们就可以通过`do-while`写出如下的代码。
**代码片段:**
```cpp
int ans = 42;
int guess;
do {
cout << "猜一个1-100的数: ";
cin >> guess;
if (guess > ans) { cout << "太大了!\n"; }
if (guess < ans) { cout << "太小了!\n"; }
} while (guess != ans);
cout << "猜对了!!\n";
```
---
### **4. 代码实现**
现在,我们来完成一个功能完整的“猜数游戏”。输入数字,来判断玩家猜测的数字是否正确,并且给出提示。
注意在真实的游戏中,答案不应该是固定的,而应该是一个随机数。
#### **4.1 完整代码**
```cpp
#include <iostream>
#include <cstdlib> // 用于 rand() 和 srand()
#include <ctime> // 用于 time()
int main() {
// 设置随机数种子
// 使用当前时间作为种子,确保每次运行程序时随机数都不同
srand(time(0));
// 生成一个 1 到 100 之间的随机数
// rand() % 100 的范围是 [0, 99],加1后范围变为 [1, 100]
int ans = rand() % 100 + 1;
int guess; // 用来存储玩家猜测的数字
// 3. 使用 do-while 循环让玩家猜测
do {
cin >> guess;
if (guess > ans) {
cout << "太大了!\n";
} else if (guess < ans) {
cout << "太小了!\n";
}
} while (guess != ans); // 只要猜的数不等于答案,就继续循环
// 4. 循环结束,说明猜对了
cout << "恭喜你,猜对了!\n";
return 0;
}
```
#### **4.2 代码解读**
* **`#include <cstdlib>` 和 `<ctime>`**:这两个头文件是生成“随机数”的关键。`<cstdlib>` 包含了 `rand()`(生成伪随机数)和 `srand()`(种子),`<ctime>` 包含了 `time()`(获取当前时间)。
* **`srand(time(0));`**:这是非常关键的一行。计算机生成的随机数其实是“伪随机”,它由一个“种子”开始,通过一个固定算法算出一串数。如果种子不变,那串数也永远不变。`time(0)` 能获取从1970年1月1日到现在的秒数,这个数时刻在变,是作为种子的绝佳选择。**这行代码通常在程序开始时只执行一次**。
* **`rand() % 100 + 1`**:这是一个生成特定范围随机数的经典公式。
* `rand()` 生成一个从 0 到 `RAND_MAX`(一个很大的数)的整数。
* `% 100` 对100取余,结果一定是 0 到 99 之间。
* `+ 1` 将 `[0, 99]` 的范围整体平移,得到 `[1, 100]`。
* **`do { ... } while (guess != ans);`**:整个“输入-判断-提示”的逻辑被包裹在 `do` 的循环体中,确保玩家至少能猜一次。然后 `while` 在每次猜测后检查是否猜中,决定是继续还是结束。
## **5. 总结**
**在什么时候用 `do-while`,什么时候用 `while`?**
* 标准就是:**循环体是否需要无条件地至少执行一次?**
* 如果**是**,比如菜单显示、输入验证等,`do-while` 是最佳选择。
* 如果**否**,也就是可能一次都不需要执行,那么 `while` 更合适。
#### **对比:`while` vs `do-while`**
| 特性 | `while` 循环 | `do-while` 循环 |
| :----------- | :--------------------------- | :------------------------------- |
| **核心原则** | **先判断,后执行** | **先执行,后判断** |
| **执行保证** | 可能执行 **0** 次或多次 | **至少**执行 **1** 次 |
| **语法结构** | `while (条件表达式) { ... }` | `do { ... } while (条件表达式);` |
@@ -0,0 +1,31 @@
### C++ do-while 循环 课后作业 - **答案**
#### **一、 填空题**
1. 执行,判断
2. 1 (或一)
3. 分号 (`;`)
4. `do-while`
5. 种子,头文件
#### **二、 单项选择题**
1. C 2. B 3. C 4. C 5. B 6. B 7. C 8. B 9. D 10. D
**选择题解析:**
2. `i` 初始值为10。先执行循环体,输出1个"Hello",然后`i`变为11。此时判断条件`i < 10` (11 < 10) 为假,循环结束。
6. `n` 初始值为5。先执行循环体,输出 "5 ",然后`n`变为4。此时判断条件`n > 5` (4 > 5) 为假,循环结束。
7. `srand` 只需要在程序开始时调用一次,为整个随机数序列播下种子。如果放在循环内部,可能会因为 `time(0)` 在一秒内返回值相同而导致短时间内生成的随机数都一样。
#### **三、 判断题**
1. √
2. × (当 `while` 循环条件初始为假,一次都不执行,`do-while` 无法模拟这种情况)
3. × (范围是 `[0, 99]`)
4. √
5. √
6. ×
7. √
8. √
9. √
10. √
@@ -0,0 +1,89 @@
### C++ do-while 循环 课后作业
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. `do-while` 循环的核心思想是“先 \_\_\_\_\_\_\_\_\_\_\_,后 \_\_\_\_\_\_\_\_\_\_\_”。
2. 与 `while` 循环不同,`do-while` 循环的循环体代码保证至少会被执行 __\_\_\_\_\_\_\___ 次。
3. `do-while` 循环的语法结构在末尾的 `while(条件)` 之后必须有一个 \_\_\_\_\_\_\_。
4. 在猜数字游戏中,因为玩家无论如何都需要先输入一次猜测的数字,所以使用 \_\_\_\_\_\_\_\_\_\_\_ 循环比 `while` 循环更合适。
5. 在 C++ 中,为了生成每次运行程序时都不同的随机数,我们需要使用 `time(0)` 函数来获取当前的\_\_\_\_\_\_\_\_\_\_\_,并通过`srand()`函数设置随机数 \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_,为了使用这两个函数工具,需要包含 `<ctime>` 和 `<cstdlib>` 两个头文件。
#### **二、 单项选择题**
( ) 1. 关于 `do-while` 和 `while` 循环,下列说法正确的是?
A. 两者没有任何区别
B. `while` 循环的循环体至少执行一次
C. `do-while` 循环的循环体至少执行一次
D. `do-while` 循环先判断条件再执行
( ) 2. 观察下面的代码,它会输出多少个 "Hello"?
```cpp
int i = 10;
do {
cout << "Hello" << endl;
i++;
} while (i < 10);
```
A. 0 个 B. 1 个 C. 10 个 D. 无限个
( ) 3. 哪个场景最适合使用 `do-while` 循环?
A. 遍历一个已知长度的数组
B. 计算 1 到 100 的累加和
C. 程序启动后,先显示菜单让用户选择,如果选择无效则重新显示
D. 从 100 倒数到 1
( ) 4. 要生成一个 `[1, 50]` 范围内的随机整数,正确的表达式是?
A. `rand() % 49` B. `rand() % 50`
C. `rand() % 50 + 1` D. `rand() % 51`
( ) 5. 在猜数字游戏中,`do-while` 循环的循环条件通常是?
A. `guess == ans` B. `guess != ans`
C. `guess > ans` D. `guess < ans`
( ) 6. 分析以下代码,最终输出结果是什么?
```cpp
int n = 5;
do {
cout << n << " ";
n--;
} while (n > 5);
```
A. 什么都不输出 B. 5 C. 5 4 3 2 1 D. 导致死循环
( ) 7. 关于 `srand(time(0));` 这行代码,下列描述错误的是?
A. 它的作用是初始化随机数种子 B. `time(0)` 提供一个不断变化的值
C. 它应该被放在 `do-while` 循环的内部,以保证每次循环都足够随机
D. 它能确保程序每次运行时,生成的随机数序列都不同
( ) 8. `do-while` 循环的条件判断发生在哪个时间点?
A. 每次循环体执行之前
B. 每次循环体执行之后
C. 仅在整个循环开始前判断一次
D. 仅在循环体执行到一半时
( ) 9. 以下哪个 `do-while` 的语法是正确的?
A. `do while(x<5) { ... }`
B. `do { ... } while x<5;`
C. `do { ... } while(x<5)`
D. `do { ... } while(x<5);`
( ) 10. 如果有一个程序需要反复验证用户输入的密码,直到输入正确为止,最合适的循环结构是?
A. `if-else` 结构
B. `while` 循环,并在循环外先获取一次输入
C. `do-while` 循环
D. 选项 B 和 C 都可以,但 C 更简洁优雅
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. `do-while` 循环的循环条件即使初始就为 `false`,循环体也依然会执行一次。
( ) 2. 在任何情况下,一个 `while` 循环都可以被等价地改写为 `do-while` 循环。
( ) 3. `rand() % 100` 生成的随机数范围是 `[0, 100]`,即包含0和100。
( ) 4. 在 `do { ... } while(条件表达式);` 结构中,在执行完循环体后,`条件表达式` 变为 `false` 时循环终止。
( ) 5. 因为 `do-while` 循环体至少执行一次,所以它永远不会导致“一次都不执行”的情况。
( ) 6. `srand(time(0));` 这行代码的作用是生成一个随机数。
( ) 7. `do-while` 循环适合于需要先获取用户输入再进行判断的逻辑。
( ) 8. 如果忘记了 `do-while` 语句末尾的分号,程序将无法通过编译。
( ) 9. 在一个 `do-while` 循环体内,必须有能够影响到循环条件的语句,否则可能导致死循环。
( ) 10. `while(guess != ans)` 和 `while(!(guess == ans))` 这两个循环条件是完全等价的。
@@ -0,0 +1,195 @@
## C++ `for` 循环(一) 讲义
### **1. 引言**
#### **问题引入**
我们来看一个非常经典的题目:**在屏幕上打印从 1 到 10 的所有整数**。
用我们学过的 `while` 循环,可以这样实现:
```cpp
int main() {
int i = 1; // 1. 循环变量初始化(起点)
while (i <= 10) { // 2. 循环条件判断(终点)
cout << i << endl;
i++; // 3. 循环变量更新(推进)
}
return 0;
}
```
这段代码完全正确,但仔细观察一下:为了完成这个循环,我们定义了三个关键部分:**初始化 `i`、判断 `i`、更新 `i`**。但这三个兄弟却被分散在了代码的三个不同位置。如果循环体很长,更新语句 `i++` 可能会离 `while` 很远,一不小心就容易忘记写,从而导致无限循环!
**思考一下**:有没有一种方法,能把这三个控制循环的核心要素“捆绑”在一起,让我们的意图一目了然呢?
#### **价值阐述**
C++ 的设计者们也注意到了这个问题。对于这种**“次数确定”**或**“范围已知”**的循环(比如循环10次,从1循环到100等),他们专门设计了一种语法结构,将循环的“三要素”集中管理。这就是 `for` 循环。
---
### **2. 核心思想**
#### **思路铺垫**
`while` 循环的逻辑是分散的,而 `for` 循环的核心思想就是**“集中管理”**。它让我们把所有控制循环进程的操作都放在一起。
#### **`for` 循环的语法**
让我们来看看 `for` 循环的语法长什么样(非常重要!):
```cpp
for (① 循环变量初始化; ② 循环条件判断; ③ 循环变量更新) {
// 循环体...
}
```
它由三个部分组成,用**分号 `;`** 隔开:
1. **`① 循环变量初始化`**
* **作用**:设置循环的**起点**。比如 `int i = 1;`。
* **执行时机**:**只在**整个 `for` 循环开始时执行**唯一一次**。
2. **`② 循环条件判断`**
* **作用**:决定循环**是否继续**。这是一个布尔表达式,比如 `i <= 10;`。
* **执行时机**:**每一次**循环体执行**之前**,都会进行判断。如果为 `true`,就执行循环体;如果为 `false`,循环立即结束。
3. **`③ 循环变量更新`**
* **作用**:让循环**向前推进**,确保循环最终能够结束。比如 `i++`。
* **执行时机**:**每一次**循环体执行**之后**,都会执行这个更新操作。
#### **步骤拆解:`for` 循环是如何运行的?**
我们可以把 `for` 循环想象成在操场上跑步:
`for (int i= 1; i<= 5; i++)`
1. **【准备开跑】 (执行 ① 初始化)**
* `int i= 1;` (我准备从第1圈开始跑)
* 这个动作只在最开始做一次。
2. **【检查】 (执行 ② 条件判断)**
* `i<= 5?` (我跑完的圈数是否小于等于5圈?)
* **当前是第1圈**:`1 <= 5` -> `true`。好的,可以跑!
3. **【执行任务】 (执行循环体)**
* `{ ... }` (跑一圈!)
4. **【更新状态】 (执行 ③ 更新)**
* `i++` (圈数加一,我现在跑完1圈,准备跑第2圈了)
* `i` 的值现在变成了 `2`。
5. **【回到第2步,开始新一轮】**
* **跑前检查**:`i<= 5?` (当前是第2圈) -> `2 <= 5` -> `true` -> 继续跑!
* **执行任务**:跑一圈!
* **更新状态**:`i++` -> `i` 变成 `3`。
* ...这个过程不断重复...
6. **【循环终点】**
* ...当 `i` 变成 `5` 时,检查 `5 <= 5` 依然为 `true`,跑完第5圈。
* **更新状态**:`i++` -> `i` 变成 `6`。
* **跑前检查**:`i<= 5?` (当前是第6圈) -> `6 <= 5` -> `false`。
* **条件不成立,循环立即结束!** 程序跳出 `for` 循环,执行后面的代码。
**执行流程**:**`① -> (② -> 循环体 -> ③) -> (② -> 循环体 -> ③) -> ... -> ② (条件为false) -> 结束`**
---
### **3. 实例**
#### **案例:打印1到5**
用刚才学到的知识,写出通过 `for` 循环打印输出1到5的代码。
```cpp
for (int i = 1; i <= 5; i++) {
cout << i << endl;
}
```
#### **分步演示**
| 轮次 | 检查 `i <= 5`? | `i` 的值 | 循环体操作 (输出) | 更新 `i++` 后, `i` 变为 |
| :-------- | :--------------- | :--------------- | :---------------- | :---------------------- |
| **开始** | - | `1` (来自初始化) | - | - |
| **第1轮** | `1 <= 5` (True) | 1 | 输出 `1` | `2` |
| **第2轮** | `2 <= 5` (True) | 2 | 输出 `2` | `3` |
| **第3轮** | `3 <= 5` (True) | 3 | 输出 `3` | `4` |
| **第4轮** | `4 <= 5` (True) | 4 | 输出 `4` | `5` |
| **第5轮** | `5 <= 5` (True) | 5 | 输出 `5` | `6` |
| **第6轮** | `6 <= 5` (False) | 6 | **不执行** | **循环结束** |
---
### **4. 代码实现**
现在,我们来解决书本中的 `Apples Plus` 例题。
**问题简述**:输入一个数 L,程序需要重复L次,打印 "Today, I ate x apple(s).",其中x从1变到L。当x是1时,单词是`apple`,否则是`apples`。
#### **分析问题**
1. **重复性**:要打印L句话,这是典型的重复任务 -> **用循环**。
2. **规律性**:句子里的数字 `x` 从1递增到L -> 这和`for`循环的计数器`i` 的变化一模一样!-> **确定使用`for`循环**,循环范围是 `1` 到 `L`。
3. **特殊性**:第1句话和后面的话格式略有不同 (`apple` 和 `apples`) -> 在循环内部需要根据当前是第几次循环(也就是`i`的值)来做一个**判断** -> **用 `if` 语句**。
#### **完整代码**
```cpp
int main() {
int L;
cin >> L;
// 搭建一个从 1 循环到 L 的 for 循环框架
for (int i = 1; i <= L; i++) {
// 在循环的每一次,变量 i 的值都代表当前是第几个苹果
// 先打印每一句都相同的部分
cout << "Today, I ate " << i << " apple";
// 根据循环变量 i 的值,判断是否需要加 's'
if (i > 1) {
cout << "s";
}
// 打印句末的句号和换行符
cout << "." << endl;
}
return 0;
}
```
#### **代码解读**
* `for (int i = 1; i <= L; i++)`: 准备一个计数器`i`,从`1`开始,只要它没超过`L`,就要一直重复循环。每做完一次,会把`i`加一。
* `if (i > 1)`: `i` 不仅仅是控制循环次数的工具,它还携带了“当前是第几次”的信息,我们可以直接用它来参与逻辑判断。
---
### 5. 常见问题与易错点
1. **无限循环**
* **代码**: `for (int i = 1; i <= 5; i--)`
* **错误**: 更新部分 `i--` 让 `i` 的值不断变小(1, 0, -1, ...),导致 `i <= 5` 这个条件永远为真。
* **修正**: 应改为 `i++`。
2. **循环一次都不执行 (零次循环)**
* **代码**: `for (int i = 5; i < 5; i++)`
* **分析**: 初始化 `i` 为5后,第一次进行条件判断 `i < 5`,即 `5 < 5`,结果为 `false`。因此,循环体一次都不会执行。这是 `for` 循环的正常行为。
3. **分号与逗号**
* **错误**: `for (int i = 1, i <= 10, i++)`
* **正确**: `for (int i = 1; i <= 10; i++)`
* **切记**:`for` 循环的三个部分之间必须用**分号 `;`** 分隔!
---
### **6. 总结**
* **`for` 循环** 非常适合用于处理**次数确定**的循环。
* **三要素**:**初始化** (只执行1次)、**条件判断** (每次循环前)、**更新** (每次循环后)。
* **执行流程**:初始化 -> (判断 -> 循环体 -> 更新) -> (判断 -> 循环体 -> 更新) -> ...
* **循环变量 `i`** 它既可以是**计数器**,也是**当前循环了多少次的信息**,可以参与循环体内部的运算和判断。
* **循环嵌套分支**:在 `for` 循环内部使用 `if` 可以解决很多“普遍规律中存在特殊情况”的问题。
@@ -0,0 +1,195 @@
## C++ `for` 循环(二) 讲义
### **1. 引言**
#### **问题引入**
在上一节课,我们学习了 `for` 循环,并用它解决了“重复打印N次”的问题,我们知道 `for` 循环是一个强大的**计数器**。但如果只是用它来数数,那可就太小看它了!
现在,我们来看另一个问题:
老师现在要大家一个一个地报上自己的学号,但我不知道咱们班有多少同学。在我听完所有人的学号后,我需要立刻说出那个**最小**的学号。
作为老师,我应该怎么做?需要把几十个学号都记在脑子里吗?太难了!我们的大脑会自动采用一种更聪明的策略:只记住“到目前为止听到的那个最小学号”,然后用新报上来的学号和这个“记录”进行比较。
#### **价值阐述**
今天我们将要学习的,是 `for` 循环在算法中最常见的三种应用模式:
1. **打擂台模式**:用于在一堆数据中寻找“最”值(最大、最小、最长、最强...)。
2. **累加模式**:用于对一连串数据进行求和、求积等累积性计算。
3. **分类统计模式**:用于遍历数据,并根据不同条件将它们归类、分别统计。
掌握了这三大模式,就能解决诸如数据分析、统计、模拟等一大批实际问题。
---
### **2. 核心思想与模式**
#### **模式一:打擂台算法 —— 寻找“最”值**
这是解决“求最大/最小值”问题的模板。让我们用书本上**例4-2** “找最小值”的例子,来学习这个算法。
**核心思想**:**==只记住当前最优值,然后不断与新的挑战者进行比较==**。
**步骤拆解**:
1. **【搭建擂台】**
* **比喻**:比赛开始前,我们需要一个擂主。为了保证第一个挑战者一定能赢,我们故意请一个最弱的虚拟擂主站在台上。
* **代码实现**:我们需要一个变量 `min_val` 来扮演“擂主”。因为我们要找最小值(越小越强),所以最弱就意味着一个**非常大**的数。
* `int min_val = 100000000;` // 一个足够大的初始值
2. **【挑战者依次上台】**
* **比喻**:n个选手排好队,一个接一个上台挑战。
* **代码实现**:我们用 `for` 循环n次,每次在循环体内读入一个新的“挑战者”。
* `for (int i = 0; i < n; i++) { cin >> tmp; ... }`
3. **【判断胜负,更新擂主】**
* **比喻**:如果挑战者比擂主“更强”(在这里是数值更小),他就挑战成功,成为新擂主。否则,擂主守擂成功。
* **代码实现**:在循环体内,用一个 `if` 语句来判断。
* `if (tmp < min_val) { min_val = tmp; }`
4. **【比赛结束,留在最后的就是冠军】**
* **比喻**:所有人都挑战完毕后,最后还站在台上的,就是真正的冠军。
* **代码实现**:当 `for` 循环结束后,`min_val` 变量里存储的值,就是我们要找的最小值。直接输出它即可。
---
#### **模式二:累加模式 —— 求和**
**核心思想**:准备一个容器变量,在循环中不断将自身和新的东西加在一起,最终越滚越大。
**步骤拆解(往存钱罐里放硬币)**:
1. **【准备一个空的存钱罐】**
* **比喻**:在开始存钱之前,你必须有一个**空**的存钱罐。
* **代码实现**:定义一个求和变量 `sum`,并**必须**将它初始化为 `0`。这个操作**必须在循环外**完成。
* `int sum = 0;`
2. **【确定要放哪些硬币】**
* **比喻**:我们要依次放入面值为 1, 2, 3, ..., 100 的硬币。
* **代码实现**:用 `for` 循环来生成 1 到 100 这一串数字。
* `for (int i = 1; i <= 100; i++) { ... }`
3. **【放硬币】**
* **比喻**:把当前这枚硬币 `i` 丢进存钱罐 `sum`。
* **代码实现**:存钱罐里新的总数,等于它原来的数,加上当前硬币的面值。
* `sum = sum + i;` (或简写为 `sum += i;`)
**关键**:求和变量 `sum` 的初始化**必须**在循环外部。如果放在循环内部,它每一轮都会被“清空”回0,那永远也攒不下钱!
---
### **3. 分类统计模式(重要)**
例如想象一个大型的快递分拣中心需要分类统计:
* 有一个**传送带**,上面源源不断地运来包裹。
* 传送带旁边站着一个**分拣员**,他的任务是看每个包裹的目的地,然后把它放到对应的城市货车里(比如北京、上海、广州)。
* 当所有包裹都处理完后,我们就可以很轻松的统计出每辆对应的货车里装了多少包裹。
在编程中,这个模式的逻辑是完全一样的,我们通过书本上实际的案例进行讲解。
#### **案例:例4-3 分类求平均数**
**问题**:从1到n这些数中,是k的倍数分为A类,不是的分为B类。求A、B两类数各自的平均数。
**分析**:`for` 循环像一个**传送带**,负责把1到n的数字一个一个送过来;循环内部的 `if/else` 像一个**分拣员**,判断这个数字该去A队还是B队。
**总结**起来就是:**分类统计模式中 ==`for` 循环负责遍历所有的数据,而 `if/else` 判断负责对这些数据挨个进行分类并依照规则进行处理==**。
#### **分步演示**
1. **准备工作(循环外)**
* 我们需要统计A、B两队的“总分”和“人数”,才能求平均分。所以,我们需要4个“存钱罐”!
* `double Asum = 0, Bsum = 0;`
* `int Acount = 0, Bcount = 0;`
2. **循环**
* 我们需要遍历从1到n的所有数字。
* `for (int i = 1; i <= n; i++) { ... }`
3. **分类进行处理**
* 每从传送带上拿到一个数字 `i`,就要判断它是不是k的倍数 (`i % k == 0`)。
* 使用 `if-else` 结构:
* 如果为真,是A队成员。就把 `i` 累加到 `Asum`,并让 `Acount` 加1。
* 否则,是B队成员。就把 `i` 累加到 `Bsum`,并让 `Bcount` 加1。
4. **计算结果(循环后)**
* 当循环结束,我们就得到了两队各自的总分和人数。
* 分别计算平均分 `Asum / Acount` 和 `Bsum / Bcount` 并输出。
---
### **4. 代码实现**
#### **找最小值(打擂台)**
```cpp
int main() {
int n;
cin >> n; // 输入数字的总个数
int min_val = 100000000; // 储存擂主,初始化为极大值,确保第一个人能打败它
int tmp; // 用于存放当前的挑战者
for (int i = 0; i < n; i++) {
cin >> tmp; // 读入一个挑战者
// 如果挑战者更小,就更新擂主
if (tmp < min_val) {
min_val = tmp;
}
}
// 循环结束,输出最终的擂主
cout << min_val << endl;
return 0;
}
```
#### **求1到100的和(累加)**
```cpp
int main() {
// 准备一个空的存钱罐
int sum = 0;
// 循环生成 1 到 100 的硬币
for (int i = 1; i <= 100; i++) {
// 把当前的硬币 i, 放入存钱罐 sum
sum += i;
}
// 循环结束后,输出存钱罐里的总数
cout << sum;
return 0;
}
```
---
### **5. 深化**
#### **变量的==作用域==**
这是一个非常重要的概念!
* **循环内的临时工**:在 `for (int i = 0; ...)` 中定义的变量 `i`,它的“生命周期”仅限于这个 `for` 循环。一旦循环结束,变量 `i` 就会消失,在循环外部无法使用它。
* **循环外的长期工:像 `sum`, `min_val`, `Acount` 这些需要在循环结束后仍然保留其计算结果的变量,**必须**在循环外部**定义。
#### **工具箱:`min()` 和 `max()` 函数**
对于打擂台算法中的找到两个数中的最小值,C++提供了一个更便捷的工具。`min(a, b)` 函数会直接返回a和b中较小的那一个。
* **使用前提**:需要包含头文件 `#include <algorithm>`。
* **简化代码**:打擂台算法中的核心判断 `if (tmp < min_val) { min_val = tmp; }` 可以被简化为一句话:
`min_val = min(min_val, tmp);`
* 这句话的意思是:“新的擂主将会被赋值为‘旧擂主’和‘挑战者’中更小的那一个”,逻辑等价,但代码更简洁。
---
### **6. 总结**
* **打擂台模式**:`初始化最值 -> 循环遍历打擂台 -> if判断更新`,用于求最大/最小值。
* **累加模式**:`初始化累加器=0 -> 循环遍历 -> sum += i`,用于求和。
* **分类统计模式**:`for` 循环提供数据,`if-else` 进行分类处理,而累加器分别统计不同的分类数量。
* **变量定义位置**:准备工作(初始化)在**循环外**,重复性处理在**循环内**。
@@ -0,0 +1,28 @@
### C++ for 循环(一) 课后习题 - **答案**
#### **一、 填空题**
1. 循环变量初始化
2. 循环变量初始化,循环条件判断,循环变量更新
3. 循环变量初始化
4. 循环变量更新
5. 分号 (`;`)
6. 次数确定 (或 范围已知)
7. 判断,更新
#### **二、 单项选择题**
1. B 2. D 3. A 4. A 5. C 6. C 7. B 8. C 9. A 10. C
#### **三、 判断题**
1. × (只在整个循环开始时执行一次)
2. √
3. √ (正是因为 `i` 变成6,才导致 `6 <= 5` 为假,从而结束循环)
4. × (如果初始条件就为假,则一次也不执行)
5. × (循环变量可以参与循环体内部的任何逻辑,如 `Apples Plus` 案例)
6. √
7. √
8. √
9. √
10. √
@@ -0,0 +1,79 @@
### C++ for 循环(一) 课后习题
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. `while` 循环将**循环的三个关键部分**:\_\_\_\_\_\_\_\_\_\_\_\_\_、**循环条件**和**循环变量更新**分散在代码的不同位置,而 `for` 循环则将它们捆绑在一起,实现集中管理。
2. `for` 循环的语法结构为 `for (` \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ `;` \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ `;` \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ `) { ... }`。
3. 在`for`循环的三个部分中,\_\_\_\_\_\_\_\_\_\_\_\_\_部分(**填循环的三个关键部分之一**)只在整个循环开始时执行唯一一次。
4. `for`循环的\_\_\_\_\_\_\_\_\_\_\_\_\_部分在每一次循环体执行**之后**都会执行。
5. `for`循环的三个部分之间必须使用\_\_\_\_\_\_\_\_\_\_\_\_\_进行分隔。
6. `for` 循环非常适合用于处理循环次数\_\_\_\_\_\_\_\_\_\_\_\_\_(填写 **固定** 或 **不固定** )的循环。
7. `for`循环的执行流程可以概括为:循环变量初始化 -> ( \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ -> 执行循环体 -> \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ ) -> ...
#### **二、 单项选择题**
( ) 1. `for (int i = 1; i <= 5; i++) { ... }` 这个循环的循环体会被执行几次?
A. 4 次 B. 5 次 C. 6 次 D. 无限次
( ) 2. 在一个 `for` 循环的单次迭代中,最后执行的是哪一部分?
A. 循环变量初始化 B. 循环条件判断
C. 循环体 D. 循环变量更新
( ) 3. 观察下面的代码,它会在屏幕上输出什么?
```cpp
for (int i = 1; i <= 3; i++) {
cout << i << " ";
}
```
​ A. `1 2 3` B. `1 2 3 4` C. `1 2` D. `2 3 4`
( ) 4. `for (int i = 10; i < 10; i++) { ... }` 这个循环的循环体会被执行几次?
A. 0 次 B. 1 次 C. 10 次 D. 无限次
( ) 5. 下列哪个 `for` 循环会导致无限循环?
A. `for (int i = 1; i <= 5; i++)`
B. `for (int i = 5; i >= 1; i--)`
C. `for (int i = 1; i <= 5; i--)`
D. `for (int i = 1; i > 5; i++)`
( ) 6. 在书本 例4-1 的 `Apples Plus` 的案例中,我们使用 `if (i > 1)` 来判断是否要输出 "s",这里的 `i` 代表了什么?
A. 苹果的总数 B. 循环的总次数
C. 当前是第几次循环 D. 一个固定的数字
( ) 7. `for` 循环相比于 `while` 循环,对于处理次数确定的循环,其主要优点是什么?
A. 运行速度更快
B. 将循环控制的三个核心要素集中管理,代码更清晰
C. 可以处理 `while` 循环无法完成的任务
D. 占用的内存更少
( ) 8. 下列哪个 `for` 循环的写法是**错误**的?
A. `for (int i = 1; i <= 10; i++)`
B. `for (int i = 1; i <= 10; i--)`
C. `for (int i = 1, i <= 10, i++)`
D. `for (double i = 1; i <= 2; i++)`
( ) 9. 要实现一个从 10 倒数打印到 1 的循环,`for` 循环应该怎么写?
A. `for (int i = 10; i >= 1; i--)`
B. `for (int i = 10; i <= 1; i++)`
C. `for (int i = 1; i <= 10; i++)`
D. `for (int i = 10; i > 1; i++)`
( ) 10. `for` 循环的执行顺序是 ( 选项中使用`()` 的部分代表会重复得到执行,直到判断条件为`false`停止 )?
A. 初始化 -> 更新循环变量 -> 判断 -> 执行循环体
B. 初始化 -> (判断 -> 更新循环变量 -> 执行循环体)
C. 初始化 -> (判断 -> 执行循环体 -> 更新循环变量)
D. (判断 -> 执行循环体 -> 更新循环变量 -> 初始化)
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. `for` 循环的循环变量初始化部分,在每一次循环开始时都会执行一次。
( ) 2. `for` 循环的循环条件判断,在每一次循环体执行**之前**进行。
( ) 3. `for (int i = 1; i <= 5; i++)` 这个循环结束后,变量 `i` 的值会变成 6。
( ) 4. `for` 循环的循环体至少会被执行一次。
( ) 5. `for` 循环的循环变量 `i` 只能用于计数,不能在循环体内部参与**运算**或**判断**。
( ) 6. 在 `for` 循环内部可以嵌套 `if` 语句,来处理循环中出现的特殊情况。
( ) 7. 把 `while` 循环的初始化、条件判断和更新语句分别放入 `for` 循环的对应位置,两者在逻辑上是等价的。
( ) 8. 在 `for` 循环中,如果循环变量更新写成了 `i = i + 2`,那么循环变量 `i` 每次会跳过一个数。
( ) 9. `for (int i = 1; i >= 0; i++)` 这种写法会导致无限循环。
( ) 10. 讲义中的 `Apples Plus` 案例,体现了“循环中可以嵌套分支判断”的思想。
@@ -0,0 +1,118 @@
### C++ for 循环(二) 课后习题
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. `for` 循环在算法中最常见的三大应用模式是:\_\_\_\_\_\_\_\_\_\_\_\_\_模式、\_\_\_\_\_\_\_\_\_\_\_\_\_模式和分类统计模式。
2. 我们在分析**寻找最小值**的例题时,使用了打擂台算法,它的核心思想是:只记住\_\_\_\_\_\_\_\_\_\_\_\_\_,然后不断与新的挑战者进行比较。
3. 在使用“打擂台”算法寻找最小值时,我们会将扮演“擂主”的变量(如 `min_val`)初始化为一个\_\_\_\_\_\_\_\_\_\_\_\_\_的数,以确保第一个挑战者一定能赢。
4. 在使用“累加”模式对一连串数据求和时,需要准备一个“容器”变量(如 `sum`),并且必须在循环\_\_\_\_\_\_\_\_\_\_\_\_\_(填 **内** 或 **外**)将它初始化为 `0`。
5. 在“分类统计”模式中,`for` 循环像一个\_\_\_\_\_\_\_\_\_\_\_\_\_,负责把数据一个一个送过来;而循环内部的 `if/else` 像一个\_\_\_\_\_\_\_\_\_\_\_\_\_,负责判断数据该去哪个分类。
6. 在 `for (int i = 0; ...)` 中定义的变量 `i`,能在 `for` 循环内部使用,\_\_\_\_\_\_\_\_\_\_\_\_\_ (填写 **能** / **不能**) 在 `for` 循环外使用。
7. C++提供了一个便捷的工具 `min(a, b)` 函数可以直接返回a和b中较小的那一个,使用它需要包含头文件 `#include <`\_\_\_\_\_\_\_\_\_\_\_\_\_`>`。
#### **二、 单项选择题**
( ) 1. 观察下面的代码,它的功能是计算什么?
```cpp
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
cout << sum;
```
A. 1到5的乘积 B. 1到5的和 C. 5的阶乘 D. 只输出5
( ) 2. 在使用“打擂台”算法寻找一组数中的**最大值**时,擂主变量 `max_val` 的初始值应该设置成什么?
A. 一个非常大的数 B. 一个非常小的数
C. 0 D. 第一个读入的数
( ) 3. 观察下面的代码,当循环结束后,`min_val` 的值是多少?
```cpp
int min_val = 100;
int n;
for (int i = 0; i < 3; i++) {
cin >> n; // 依次输入 50, 120, 30
if (n < min_val) {
min_val = n;
}
}
```
A. 120 B. 100 C. 50 D. 30
( ) 4. 如果把累加求和代码中的 `int sum = 0;` 放到 `for` 循环内部,会发生什么?
A. 程序无法运行
B. 结果正确,但效率变低
C. 每次循环 `sum` 都会被重置为0,导致结果错误
D. 没有任何影响
( ) 5. 变量 `i` 在 `for (int i = 0; i < 5; i++)` 循环结束后,在循环外部还能使用 `cout << i;` 吗?
A. 可以,它的值是5
B. 可以,它的值是4
C. 不可以,因为变量`i`在循环结束后就消失了
D. 不可以,因为没有初始化
( ) 6. 在“分类求平均数”的案例中,我们需要几个变量来分别统计A、B两类的总和与数量?
A. 1个 B. 2个 C. 3个 D. 4个
( ) 7. 要统计从1到100之间所有偶数的个数,`for`循环内部的`if`判断应该写成?
A. `if (i % 2 == 1)` B. `if (i % 2 == 0)`
C. `if (i / 2 == 0)` D. `if (i * 2 == 0)`
( ) 8. 使用 `min()` 函数,代码 `if (tmp < min_val) { min_val = tmp; }` 可以被简化为?
A. `min_val = min(tmp);`
B. `min(min_val, tmp);`
C. `min_val = min(min_val, tmp);`
D. `tmp = min(min_val, tmp);`
( ) 9. 下面哪种问题最不适合用“累加模式”来解决?
A. 计算全班同学的总分
B. 计算1到100所有奇数的和
C. 找出全班的最高分
D. 计算1乘以2乘以3...一直乘到10的结果
( ) 10. 在“分类统计”模式中,哪个结构负责提供需要被处理的数据?
A. `if` 语句 B. `else` 语句 C. `for` 循环 D. `cout` 语句
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. “打擂台”算法只能用来寻找最小值,不能用来寻找最大值。
( ) 2. 在使用“累加模式”求和时,累加变量必须在循环外部定义和初始化。
( ) 3. `for (int i = 1; i <= n; i++)` 和 `for (int i = 0; i < n; i++)` 都能循环n次。
( ) 4. 在“分类求平均数”的例子中,`for`循环的作用是进行分类,`if-else`的作用是遍历数字。
( ) 5. 像 `sum` 和 `min_val` 这样需要在循环结束后使用的变量,必须定义在循环的外部。
( ) 6. 求1到100的和,`int sum = 1;` 作为一个错误的初始值,会导致最终结果比正确答案多1。
( ) 7. C++中的 `min(a, b)` 函数可以一次性比较三个数的最小值。
( ) 8. “分类统计”模式本质上是 `for` 循环和 `if-else` 结构的结合使用。
( ) 9. 在不知道具体要输入多少个数字的情况下,使用 `for` 循环是最好的选择。
( ) 10. `sum = sum + i;` 和 `sum += i;` 这两种写法在功能上是完全等价的。
---
### C++ for 循环(二) 课后习题 - **答案**
#### **一、 填空题**
1. 打擂台,累加
2. 当前最优值
3. 非常大
4. 外
5. 传送带,分拣机
6. 作用域
7. algorithm
#### **二、 单项选择题**
1. B 2. B 3. D 4. C 5. C 6. D 7. B 8. C 9. C 10. C
#### **三、 判断题**
1. × (同样适用于找最大值,只需改变比较逻辑和初始值)
2. √
3. √
4. × (正好相反,`for`是传送带负责遍历,`if-else`是分拣机负责分类)
5. √
6. √
7. × (`min(a, b)` 只能比较两个数,比较三个需要 `min(a, min(b, c))`)
8. √
9. × (`while` 循环更适合处理未知次数的循环)
10. √
@@ -0,0 +1,87 @@
### C++ for 循环(二) 课后习题
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. `for` 循环在算法中最常见的三大应用模式是:\_\_\_\_\_\_\_\_\_\_\_\_\_模式、\_\_\_\_\_\_\_\_\_\_\_\_\_模式和分类统计模式。
2. 我们在分析**寻找最小值**的例题时,使用了打擂台算法,它的核心思想是:只记住\_\_\_\_\_\_\_\_\_\_\_\_\_,然后不断与新的挑战者进行比较。
3. 在使用“打擂台”算法寻找最小值时,我们会将扮演“擂主”的变量(如 `min_val`)初始化为一个\_\_\_\_\_\_\_\_\_\_\_\_\_的数,以确保第一个挑战者一定能赢。
4. 在使用“累加”模式对一连串数据求和时,需要准备一个“容器”变量(如 `sum`),并且必须在循环\_\_\_\_\_\_\_\_\_\_\_\_\_(填 **内** 或 **外**)将它初始化为 `0`。
5. 在“分类统计”模式中,`for` 循环像一个\_\_\_\_\_\_\_\_\_\_\_\_\_,负责把数据一个一个送过来;而循环内部的 `if/else` 像一个\_\_\_\_\_\_\_\_\_\_\_\_\_,负责判断数据该去哪个分类。
6. 在 `for (int i = 0; ...)` 中定义的变量 `i`,能在 `for` 循环内部使用,\_\_\_\_\_\_\_\_\_\_\_\_\_ (填写 **能** / **不能**) 在 `for` 循环外使用。
7. C++提供了一个便捷的工具 `min(a, b)` 函数可以直接返回a和b中较小的那一个,使用它需要包含头文件 `#include <`\_\_\_\_\_\_\_\_\_\_\_\_\_`>`。
#### **二、 单项选择题**
( ) 1. 观察下面的代码,它的功能是计算什么?
```cpp
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
cout << sum;
```
​ A. 1到5的乘积 B. 1到5的和 C. 5的阶乘 D. 只输出5
( ) 2. 在使用“打擂台”算法寻找一组数中的**最大值**时,擂主变量 `max_val` 的初始值应该设置成什么?
A. 一个非常大的数 B. 一个非常小的数 C. 0 D. -1
( ) 3. 观察下面的代码,当循环结束后,`min_val` 的值是多少?
```cpp
int min_val = 100;
int n;
for (int i = 0; i < 3; i++) {
cin >> n; // 依次输入 50, 120, 30
if (n < min_val) {
min_val = n;
}
}
```
​ A. 120 B. 100 C. 50 D. 30
( ) 4. 如果把累加求和代码中的 `int sum = 0;` 放到 `for` 循环内部,会发生什么?
A. 程序无法运行
B. 结果正确,但效率变低
C. 每次循环 `sum` 都会被重置为0,导致结果错误
D. 没有任何影响
( ) 5. 变量 `i` 在 `for (int i = 0; i < 5; i++)` 循环结束后,在循环外部还能使用 `cout << i;` 吗?
A. 可以,它的值是5
B. 可以,它的值是4
C. 不可以,因为变量`i`在循环结束后就消失了
D. 不可以,因为没有初始化
( ) 6. 在“分类求平均数”的案例中,我们需要几个变量来分别统计A、B两类的总和与数量?
A. 1个 B. 2个 C. 3个 D. 4个
( ) 7. 要统计从1到100之间所有偶数的个数,`for`循环内部的`if`判断应该写成?
A. `if (i % 2 == 1)` B. `if (i % 2 == 0)`
C. `if (i / 2 == 0)` D. `if (i * 2 == 0)`
( ) 8. 使用 `min()` 函数,代码 `if (tmp < min_val) { min_val = tmp; }` 可以被简化为?
A. `min_val = min(tmp);`
B. `min(min_val, tmp);`
C. `min_val = min(min_val, tmp);`
D. `tmp = min(min_val, tmp);`
( ) 9. 下面哪种问题最不适合用“累加模式”来解决?
A. 计算全班同学的总分
B. 计算1到100所有奇数的和
C. 找出全班的最高分
D. 计算1乘以2乘以3...一直乘到10的结果
( ) 10. 在“分类统计”模式中,哪个结构负责提供需要被处理的数据?
A. `if` 语句 B. `else` 语句 C. `for` 循环 D. `cout` 语句
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. “打擂台”算法只能用来寻找最小值,不能用来寻找最大值。
( ) 2. 在使用“累加模式”求和时,累加变量必须在循环外部定义和初始化。
( ) 3. `for (int i = 1; i <= n; i++)` 和 `for (int i = 0; i < n; i++)` 都能循环n次。
( ) 4. 在“分类求平均数”的例子中,`for`循环的作用是进行分类,`if-else`的作用是遍历数字。
( ) 5. 像 `sum` 和 `min_val` 这样需要在循环结束后使用的变量,必须定义在循环的外部。
( ) 6. 求1到100的和,如果定义累加变量为 `int sum = 1;` 而不是`int sum = 0;`,会导致最终结果比正确答案多1。
( ) 7. C++中的 `min(a, b)` 函数可以一次性比较三个数的最小值。
( ) 8. “分类统计”模式本质上是 `for` 循环和 `if-else` 结构的结合使用。
( ) 9. 在不知道具体要输入多少个数字的情况下,使用 `for` 循环是最好的选择。
( ) 10. `sum = sum + i;` 和 `sum += i;` 这两种写法在功能上是等价的。
@@ -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>
@@ -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;
```
@@ -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`。