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