循环
在编写程序时,程序的逻辑构成,通常有三种基本的流程结构,包括:
- 顺序(Sequence)
- 分支(Condition)
- 循环(Loop)
顺序结构是代码从上到下,依次执行;分支结构往往与条件判断结构结合起来使用,代码在执行的过程中会遇到岔路,需要根据条件来选择去走哪一条路;而循环结构是在执行的过程中,当满足循环的条件判断后,就会重复执行指定的某一段代码,直到条件不满足退出循环为止。
对于多次重复做同一件事情这类问题,都可以用循环结构去完成。
前面我们已经讲过顺序结构与分支结构,下面我们来看看循环结构。
循环结构
下面是循环结构的一个简单流程示意图。
很多时候,我们需要多次重复做一件事情时,就需要用到循环结构。例如,我们要将 1 ~ 100 的所有整数打印出来,我们总不能像下面这样一行一行的打印:
cout << 1 << endl;cout << 2 << endl;cout << 3 << endl;...cout << 100 << endl;
这样要写 100 次输出语句 ,那得累死了,循环结构就是为了解决这种麻烦而设计的。
在生活中,也有许多与循环执行相关的现象。比如说在操场跑步,一圈 400 米,如果要跑 10 公里,那就要绕着操场重复跑 25 圈,也就是重复跑 25 次 400 米。
在数学中,有一些运算符号最初也是为了解决重复运算的麻烦而引入的。比如说乘法,最简单的理解,乘法就是重复的加法,在引入乘法之前,4 + 4 + 4 + 4 + 4 就要写很多遍,而引入乘法符号可以直接写成 4 × 5,其实是代表 5 个 4 相加(或 4 个 5 相加),用重复来表述,就是重复加 5 次 4 。
在 C++ 中,有下面三种方式来实现循环结构。
while
循环do while
循环for
循环
这三种方式都有自己的适用场景,在学习时注意区分他们相同和不同的地方。
while 循环
循环的初始条件while (满足循环的条件判断): 需要重复的逻辑功能代码 每一轮循环条件的变动
例如,像前面要输出 1 ~ 100 之间的所有整数,可以像下面这样写:
int i = 1; // 循环的初始条件while (i <= 100) { // 满足循环的条件判断 cout << i << endl; // 需要重复的逻辑代码 i = i + 1; // 每一轮循环条件的变动}
如果把上面的 100
改成 1000
,那就是打印 1 ~ 1000 的所有整数了。是不是简单太多了?
写 while
循环的代码时,下面这三个条件需要注意:
- 初值,循环的初始条件,也就是这里的
int i = 1
- 条件,满足并进入循环的条件,也就是这里的
i <= 100
, 在这里表示只有当满足i
小于等于100
这个条件时,才会执行循环体内的代码;如果不满足这个条件,则退出循环。 - 步进,每轮循环条件变动,也就是这里的
i = i + 1
,大部分情况下,都需要在每一轮循环中更改循环的条件,直接某一轮条件不满足,就退出循环。
下面列表显示了打印 1 ~ 100 的代码执行过程中的数据变化。
实际上,上面的三个条件缺一不可。刚开始学习编程的同学经常会忘记掉某一个条件,造成程序执行的错误。大家可以试试,将某一个条件去掉,例如去掉第 3 个条件,比如去掉 i = i + 1
,执行时会出现什么情况呢?
int i = 1;while (i <= 100) { cout << i << endl;}
很可能你会发现程序卡死没反应了,这是因为初始条件 int i = 1
,而1
永远小于 100
,也就是进入循环的条件永远是满足的,这个程序会无限执行下去,程序进入所谓的**「死循环」**状态。也这是为什么必须要有第 3 个条件的原因。
下面我们尝试更改这三个条件中的一个或多个,来完成一些小练习。
上面演示了将 1 ~ 100 内的整数数字依次打印出来,稍微改动,如果要将 1 ~ 100 内的所有奇数依次打印出来,程序该怎么写呢?
实际上,只需要更改第 3 个条件,将 i = i + 1
更改为 i = i + 2
就行了,代码如下:
# 依次打印出 1 ~ 100 内的所有奇数int i = 1;while (i <= 100) { cout << i << endl; i = i + 2;}
想一想是不是对的,i
的初始值是 1,第 1 次执行则打印出 1,紧跟着 i = i + 2
,这句执行后 i
就变成 3 了,再进入循环则打印出 3,依次下去,就会将 1 ~ 100 中所有的奇数打印出来。
那如果要将 1 ~ 100 内的所有偶数依次打印出来呢?只需要在打印奇数的程序基础上,再更改第 1 个条件,将初始值更改为 2 就可以了。
# 依次打印出 1 ~ 100 内的所有偶数int i = 2;while (i <= 100) { cout << i << endl; i = i + 2;}
有的同学说,我们前面学过如何判断偶数和奇数,我们可以在循环中用一个条件判断来控制打印奇数还是偶数,可以吗?
当然可以,用这种方法来打印偶数的代码如下:
// 方法二:依次打印出 1 ~ 100 内的所有偶数int i = 1;while (i <= 100) { if (i % 2 == 0) { cout << i << endl; } i = i + 1;}
活学活用,非常棒!给大家留一个小问题:仅仅从这个题目的要求来说,你觉得方法一更好一点,还是方法二更好一点呢?说说你的理由。
好吧,虽然有点无聊,但还是要「刁难」一下大家,在上面几个程序的的基础上,再稍微变化一点,要求按倒序来打印出结果,例如,同样是打印 1 ~ 100 内的所有整数,但倒着来,从 100 开始输出,一直到 1 ,程序应该怎么修改呢?
// 倒序打印出 1 ~ 100 内的所有整数int i = 100;while (i >= 1) { cout << i << endl; i = i - 1;}
来看一个例子。
示例:奇偶数判断
题目描述
输入 个整数,如果是整数则输出 “Y”;如果是奇数则输出 “N”
输入描述
输入为两行。
- 第一行为一个正整数 ,代表接下来要输入的数字个数
- 第二行为 个正整数,代表要判断的数字
输出描述
输出为一行,根据题目要求输出 “Y” 或 “N”
输入输出样例
输入数据
58 9 2 5 1
输出数据
Y N Y N N
代码实现
#include <iostream>using namespace std;
int main() { int k; cin >> k;
while (k--) { int t; cin >> t; if (t % 2 == 0) cout << "Y" << " "; else cout << "N" << " "; }
return 0;}
代码说明
注意这里 k--
的用法,等价于下面的写法:
while (k) { // 代码逻辑 k--;}
直接写在 while
条件里代码更简洁一些。
另外,在 C++ 中,任何非零的数如果直接作为条件,相当于 true
,0 相当于 false
,这一点需要注意。
练习: 整数数字之和
题目描述
输入一个正整数 ,求这个数的所有位上的数字之和
输入描述
输入为一行,一个正整数
输出描述
输出为一行,一个正整数,代表输入数字的所有位上的数字之和
输入输出样例
输入数据1
1234
输出数据1
10
输入数据2
234729
输出数据2
27
代码实现
#include <iostream>using namespace std;
int main() { int n, s = 0; cin >> n;
while (n > 0) { s = s + n % 10; n = n / 10; }
cout << s << endl; return 0;}
练习:整除几次2
题目描述
请问一个正整数 能够整除几次 2?
比如: 4 可以整除 2 次 2 ,100 可以整除 2 次 2 , 9 可以整除 0 次 2 。
输入
从键盘读入一个正整数 。
输出
输出一个整数,代表 能够整除 2 的次数。
输入输出样例
输入数据
8
输出数据
3
代码实现
#include <iostream>using namespace std;
int main() { int n, cnt = 0; cin >> n;
while (n % 2 == 0) { n = n / 2; cnt++; }
cout << cnt << endl; return 0;}
do while 循环
do ... while
循环是 while
循环的变形,形式如下:
do { // 循环体} while (条件);
与 while
循环最大的不同是:
while
是先判断条件再执行循环体的内容do...while
是先执行后判断条件
也就是说,使用 do...while
,无论是否满足条件, 都一定会执行一次循环。
来看一个简单的例子:
int i = 1;do { i++;} while (i > 5);cout << i << endl;
结果为 2,尽管 i
不大于 5,但也会执行一次循环,从而导致 i
增加 1 。
对比一下 while
循环:
int i = 1;while (i > 5) { i++;}cout << i << endl;
输出结果为 1,由于条件不满足,永远不会进入循环。
for 循环
for
循环是另一种常见的循环结构形式,它的语法格式为:
for (初值; 条件; 步进) { // 循环的功能逻辑}
这里的 初值
、条件
、步进
分别对应于 while
循环中的三个条件。
不同的是 while
里这三个条件是分开给出的,而 for...in
是放在一起的,在写法上比较简练,更不容易出错。
while
与 for
的适用场景
while
与 for
都是循环,大多时候都能做同样的事,那它们之间有什么差异呢?
- 当明确知道循环执行的次数时,用
for
循环 - 当不知道明确的循环执行次数时,用
while
循环
通过下面这个纸张对折的问题,大家也可以体会一下这两者的不同。
示例: 纸张对折问题
题目要求,如果给你一张无限大的白纸,白纸的厚度为 0.0001 米,请问,将对纸对折 20 次后,对折后的纸有多厚呢(以米为单位,结果保留到小数后两位)?
建议大家可以拿一张普通 A4 白纸,尽最大可能试一试,看看你能将这张纸对折多少次呢?
这个问题的核心逻辑是,每对折一次,其厚度翻一倍,通过循环可以完成,实现代码如下:
#include <iostream>#include <cstdio>using namespace std;
int main() { int n; cin >> n; double h = 0.0001;
for (int i = 1; i <= n; i++) { h = h * 2; }
printf("%.2f", h); return 0;}
输入数据
20
输出数据
104.86
有点吓人,竟然有一百多米厚呢。
同样的对折问题,换个角度呢,我们知道,位于中国与尼泊尔边境的珠穆朗玛峰是世界最高峰,海拔高度是8848.86米。请编写程序计算一下,将纸张对折多少次后,厚度就会超过珠穆朗玛峰呢?
#include <iostream>using namespace std;
int main() { double k; cin >> k;
double h = 0.0001; int i = 0;
while (h < k) { h = h * 2; i = i + 1; }
cout << i << endl; return 0;}
输入数据
8848.86
输出数据
27
编程对于我们理解特别大的数,以及理解数的增长是非常有用的,因为在现实生活中,我们很难去模拟出增长所带来的变化,而且编程给我们提供了这样一个无限潜能的模拟工具!
练习: 求 1 + 2 + … + n 的和
题目描述
输入一个正整数 n,求 1 + 2 + 3 + … + n 的和
输入输出样例
输入数据1
100
输出数据1
5050
输入数据2
1000
输出数据2
500500
实现代码
#include <iostream>using namespace std;
int main() { int n, sum = 0; cin >> n;
for (int i = 1; i <= n; i++) { sum = sum + i; }
cout << sum << endl; return 0;}
注意,这里的最终求和的结果
sum
应该放在循环外,循环是用来累加,累加完成得到最终结果再输出。
当然,也还是可以配合条件判断来完成求 1 ~ n 之间所有偶数的和,代码如下:
#include <iostream>using namespace std;
int main() { int n, sum = 0; cin >> n;
for (int i = 1; i <= n; i++) { if (i % 2 == 0) { sum = sum + i; } }
cout << sum << endl; return 0;}
练习:求 1 + 1/2 + … + 1/n 的和
【补充内容】
练习:整除几次2
题目描述
请问一个正整数 能够整除几次 2?
比如: 4 可以整除 2 次 2 ,100 可以整除 2 次 2 , 9 可以整除 0 次 2 。
输入
从键盘读入一个正整数 。
输出
输出一个整数,代表 能够整除 2 的次数。
输入输出样例
输入数据
8
输出数据
3
代码实现
#include <iostream>using namespace std;
int main() { int n, cnt = 0; cin >> n;
while (n % 2 == 0) { n = n / 2; cnt++; }
cout << cnt << endl; return 0;}
练习: Fibonacci 数列的第 n 项
题目描述
输入正整数 n,求斐波那契数列的第 n 项,并打印出来
什么是斐波那契数列:
斐波那契数列(Fibonacci sequence),又称黄金分割数列,这个数列是由意大利数学家莱昂纳多·斐波那契(Leonardo Fibonacci)在他的《算盘书》中提出而得名。
在数学上,斐波那契数是以递归的方法来定义,数学表达如下所示:
用文字来说,就是斐波那契数列是由 0 和 1 开始,之后的斐波那契数是由之前的两数相加而得。
前面的几个斐波那契数是:
1、1、2、3、5、8 …
需要注意的是,0 是第零项,而不是第一项
输入输出样例
输入数据:
8
输出数据:
21
代码实现
#include <iostream>using namespace std;
int main() { int n, f; cin >> n;
int f0 = 0; int f1 = 1; for (int i = 2; i <= n; i++) { f = f0 + f1; // 每项等于前两项的和 f0 = f1; // 接力往后传递 f0 f1 = f; // 接力往后传递 f1 } if (n == 0) f = f0; // 第 0 项特殊处理 else if (n == 1) f = f1; // 第 1 项特殊处理
cout << f << endl; return 0;}
多层循环
多层循环通常也叫嵌套循环,也就是循环里还有循环。在几乎所有编程语言中,无论是条件分支结构,还是循环结构,都是可以多层嵌套使用的。
嵌套循环在有的时候是非常有用的,但为了便于阅读和理解代码,循环嵌套的层数不宜过多,一般来说不超过三层。
下面来看一个例子。
示例: 打印九九乘法表
题目描述
利用嵌套循环打印输出九九乘法表
代码实现
这里为了显示方便,只打印了其中一部分乘法表
#include <iostream>using namespace std;
int main() { for (int i = 1; i <= 5; i++) { for (int j = 1; j <= i; j++) { printf("%d*%d=%d\t", j, i, i*j); } printf("\n"); } return 0;}
输出结果
1*1=11*2=2 2*2=41*3=3 2*3=6 3*3=91*4=4 2*4=8 3*4=12 4*4=161*5=5 2*5=10 3*5=15 4*5=20 5*5=25
代码说明
本题中使用 printf()
输出更加方便一些,其中 \t
是每输出一个乘法算式后输出一个制表符(tab),制表符宽度根据不同的环境会有所不同,一般来说是 4 个或 2 个空格。
示例: 打印数字方阵
题目描述
输入一个正整数 n,输出 n 行,每行都是从1到 n 的所有正整数。
输入输出样例
输入数据:
5
输出数据:
1 2 3 4 51 2 3 4 51 2 3 4 51 2 3 4 51 2 3 4 5
实现代码
#include <iostream>using namespace std;
int main() { int n; cin >> n;
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { cout << j << " "; } cout << endl; // 每一行输出完后,换行 }
return 0;}
代码说明
在双层循环中,通常外层循环用于控制有多少行,内层循环用于控制每一行应该输出什么。
参考「模式与编程」章节,可以看到更多利用双层循环输入数字或符号图案的练习。
练习: 三色球问题
题目描述
一个口袋里有 12 个球,其中有三个红球,三个白球,六个黑球 从其中取 8 个球,问有多少种颜色搭配?
实现代码
#include <iostream>using namespace std;
int main() { int cnt = 0; // r 代表红球 // w 代表白球 // b 代表黑球 for (int r = 0; r <= 3; r++) { for (int w = 0; w <= 3; w++) { for (int b = 0; b <= 6; b++) { if (r + w + b == 8) { cnt++; } } } } cout << cnt << endl; return 0;}
运行结果
13
代码说明
这里实际上是一种**枚举(暴力求解)**的方法,就是让程序挨着去试,只要满足 8 个球的搭配方式就计数一次,人要这样去枚举是很难的,而对于计算机来说,最适合这样「笨」的重复计算了。
有些细心同学会发现,要取 8 个球的话,至少都会取 2 个黑球,因此哪怕把红球和白球全取了,也只有 6 个球,所以最里层的循环 b
从 2 开始就行了。
for (int r = 0; r <= 3; r++) { for (int w = 0; w <= 3; w++) { for (int b = 2; b <= 6; b++) { if (r + w + b == 8) { cnt++; } } }}
确实是这样的,这样循环的次数就会少一些,对于数据量比较大的组合问题来说,改后的程序效率肯定会好一些,不过对于这样简单的组合问题来说,我们直接全量枚举即可,影响不大,反而可以避免你在考虑从多少开始时计算错误。
排列与组合问题,实际上会涉及到高中的数学知识,但有了编程这个厉害的工具,小学的孩子也可以很好的理解这类问题。
退出循环
前面都是讲的循环,但在有的时候,在循环内部,当满足一定条件时,我们希望直接退出循环,该怎么办呢?
在 C++ 中,有两种退出循环的方式:
break
,直接终止循环,继续执行循环以后的代码continue
,跳过当前这一轮循环(continue
之后的代码都不会执行了),继续下一轮
1. break
终止循环
来看一个简单的例子
for(int i = 1; i <= 10; i++) { if (i % 3 == 0) break; cout << i << endl;}
程序运行结果:
12
如果没有第 2、3 行,这段代码运行的结果是直接输出 1 ~ 10,但加上第 2、3 行, 表示一旦遇到 i
是 3 的倍数 ,循环就终止了。而第 1 次遇到 i
是 3 的倍数也就是当 i
等于 3 的时候,因此程序的运行结果只会输出 1 和 2 。
2. continue
退出本轮循环
continue
也是一种退出循环的方式,用于跳过当前一轮循环,跳过后继续进行后面的循环。
将上面示例中的 break
更改为 continue
,来看看两个程序的输出有什么不同。
for(int i = 1; i <= 10; i++) { if (i % 3 == 0) continue; cout << i << endl;}
程序运行结果:
12457810
下面再来看一个比较典型的退出循环相关的例子。
3. return
退出
严格意义上 return
并不算标准退出循环方式(但它在某些时候能起到退出循环的作用),回想一下,我们在写第 1 个 C++ 程序的时候讲过,return 0
代表程序正常结束。
也就是说,一旦遇到 return 0
中,整个程序就结束了,当然任何循环也就强制结束了。
看下面这个程序:
#include <iostream>using namespace std;
int main() { int n; cin >> n;
for (int i = 1; i <= n; i++) { if (i % 3 == 0) { return 0; } cout << i << endl; } cout << "bye" << endl; return 0;}
程序运行结果:
12
注意,我们程序里多加了一个,输出字符串 “bye”,但最终的结果是没有的,也正好说明了 return 0
的作用,是结束整个程序,在 return 0
后的所有代码都不会再执行了。
示例: 质数判断
题目描述
判断一个大于 1 的数是否是质数,如果是质数则输出 “Y”,否则输出 “N”。
题目分析
回顾一下质数的定义,质数(Prime number),又称素数,是指在大于 1 的自然数中,除了 1 和该自数自身外,无法被其它自然数整除的数。也就是说,一个质数,只能被 1 和它本身整除。
代码实现
#include <iostream>using namespace std;
int main() { int n; cin >> n;
bool is_prime = true;
for (int i = 2; i < n; i++) { if (n % i == 0) { is_prime = false; break; } }
if (is_prime) { cout << "Y" << endl; } else { cout << "N" << endl; }
return 0;}
这里的判断的核心逻辑在于代码中的 for
循环中的代码。
for
循环中,i 取值从 2 ~ n-1,也就是除 1 和 本身以外的数的范围,一旦有任何一次被这个范围内的数整除的情况出现,那这个数一定不是质数,剩下的就不用再判断了,直接使用 break
直接退出循环。
标志位 is_prime
是为了后面根据该标志位来输出最终判断的结果。
想一想: 如果这里把程序中的 break
这条语句去掉,程序运行的结果会有不同吗?程序的执行过程与去掉前有什么不同呢?
示例:含 0 的数有多少个
题目描述
请求出 1 ~ 中含有数字 0 的数,共有多少个?
输入描述
一个整数 n()
输出描述
一个整数,代表 1 ~ 中含有数字 0 的数的个数。
输入输出样例
输入数据 1
80
输出数据 1
8
输入数据 2
999
输出数据 2
180
题目解析
[待补充]
代码实现
#include <iostream>using namespace std;
int main() { int n, cnt = 0; cin >> n; for (int i = 1; i <= n; i++) { int j = i; while (j > 0) { if (j % 10 == 0) { cnt++; break; } j = j / 10; } } cout << cnt << endl; return 0;}
小结
循环结构是编程中最常用的控制结构之一,使用循环结构可以简化代码,避免重复编写相同的代码段,还可以提高代码的可读性和可维护性。同时,合理使用循环结构可以实现迭代、遍历、计数等各种功能,为程序的实现提供灵活性和效率。