[Toc]
# 第4章 循环结构
今天小墨首先学习了第三种程序结构:循环结构,这是一种神奇的结构,通过它能完成快速完成大量重复的事情,比如一万以内数字的相加。然后学习了一种特殊的循环:死循环。最后小墨学习了随机数的生成,并编写了猜数字的小游戏,一起来看看吧。
# [插画:早上九点、风和日丽的墨馨书屋]
时间:早上9:00
天气:风和日丽
墨博士:小墨,今天我们来做一个很经典的小游戏:猜数字,就像这样:
```
输入猜的数字:50
小了
输入猜的数字:75
小了
输入猜的数字:88
大了
输入猜的数字:82
大了
输入猜的数字:78
大了
输入猜的数字:76
小了
输入猜的数字:77
猜对了
```
小墨:太好了!我哥哥就是游戏工程师,今后也能让他玩玩我做的游戏啦。
墨博士:想要做出这款游戏,我们需要学习循环结构、死循环、随机数等重要的基础语法,并且和前面的分支结构、关系运算符等内容相结合。
小墨:我准备好了。
## 4.1 为什么需要循环
墨博士:好,小墨,如果现在让你输出5遍HelloWorld,你会怎么做?
小墨:太简单了,先写一行输出,然后复制粘贴4行出来,如下:
```
print('Hello World !')
print('Hello World !')
print('Hello World !')
print('Hello World !')
print('Hello World !')
```
墨博士:那如果输出10遍、100遍、9527遍呢?
小墨:……
墨博士:计算机擅长帮助人们处理重复的事情,这在python等编程语言中就是使用循环结构。
下面我们来学习两种循环结构:while循环和for循环。
## 4.2 while循环
墨博士:小墨你知道while在英语中是什么意思?
小墨:for a while,一会儿的意思。
墨博士:while还有一个意思是当……的时候,类似于when,在Python中while取的就是这个意思。它的结构如下:
```
while 条件:
语句
```
程序运行流程是:
先执行第1行,判断条件是否满足
如果条件满足,则执行后面的语句
然后再次判断条件,看是否满足
如果仍然满足,则继续执行后面的语句
然后再次判断条件,看是否满足
如果仍然满足,则继续执行后面的语句
……(不停的运行)
然后再次判断条件,看是否满足
如果仍然满足,则继续执行后面的语句
然后再次判断条件,看是否满足
如果条件不再满足了,则不再继续执行后面的语句
整个while结束。
也就是说,上述语句会一直执行,直到while后面的条件不再满足为止。这个过程就称为**循环**。这里的语句,称为**循环体**。
小墨:这个条件什么时候会不满足呢?
墨博士:问的好!while循环后跟的语句,通常不会是一句,而是多句(多个语句一起称为语句块)。其中一般就会有一句代码来控制判断条件的变化。
我们以输出10次HelloWorld的例来看:
```
n = 10
while n > 0:
print('Hello World !')
n = n - 1
```
第1行定义一个变量n,并赋值为10。
第2行中while后跟的“n > 0”是一个判断条件,判断n是不是比0大,“while n > 0”连在一起就表示当n大于0的时候,就会一直执行后面跟的代码块,也就是第3、4行内容。
第3行输出“Hello World !”
第4行是很有意思的一行,前面我们说过,“=”号是赋值运算符,它把后面的值赋给前面的变量。本句的意思是说n-1的值重新赋值给变量n。比如n为10,则10-1=9,然后把9赋值给n,此时n就变为9了。
这一句就是控制循环条件的关键一句。
整个程序的运行流程是:
```
先执行第1行
然后执行第2行,此时判断n是不是大于0,因为此时n的值是10,10大于0,条件满足。
第一轮循环开始:
然后执行第3行,输出“Hello World !”。
然后是第4行,n是10,n = n -1之后n变为9。
此时第一轮循环结束
接着再次执行第2行,判断n是否大于0,因为n此时等于9,所以大于0,条件满足
开始第二轮循环
执行第3行,输出第3行输出“Hello World !”
然后执行第4行,n是9,n = n -1之后n变为8。
此时第二轮循环结束
接着再次执行第2行,判断n是否大于0,因为n此时等于8,所以大于0,条件满足
开始第三轮循环
执行第3行,输出第3行输出“Hello World !”
然后执行第4行,n是8,n = n -1之后n变为7。
此时第三轮循环结束
接着是第四、五、六、七、八、九轮循环
n的值分别变为了6、5、4、3、2、1
接着再次执行第2行,判断n是否大于0,因为n此时等于1,所以大于0,条件满足
开始第十轮循环
执行第3行,输出第3行输出“Hello World !”
然后执行第4行,n是1,n = n -1之后n变为0。
接着再次执行第2行,判断n是否大于0,因为n此时等于0,0不再大于0,条件不再满足,整个循环结束。
```
一共循环了十轮,也即第3、4行一共运行了10次,所以输出了10次的“Hello World !”。
小墨:哇啊,厉害!果然比写10遍print输出厉害太多了!这样我想输出100遍HelloWorld只需要把10改成100就可以了。
墨博士:这里的n被称为**循环变量**,你可以打印一下n的值,更能直观的看到程序的运行过程:
```
n = 10
while n > 0:
print(n)
n = n - 1
```
输出结果为:
```
10
9
8
7
6
5
4
3
2
1
```
小墨:这个每次看的都是变化前的n的值,我觉得打印下变化后的n值更直观一些。可以把第3行和第4行换一下:
```
n = 10
while n > 0:
n = n - 1
print('n的值为:'+str(n))
```
输出结果为:
```
n的值为:0
```
咦!怎么就输出了一个0?
墨博士:呵呵,这个正是我想告诉你的。在使用while循环的时候,需要注意后面跟的语句的缩进问题。有同样缩进的才被当成是一个语句块。这个和if后面跟的语句块的缩进是一个道理,要把它们放到while的覆盖范围中。
小墨:哦我懂了,我的第4行代码忘了添加4个空格作为缩进了。改进如下:
```
n = 10
while n > 0:
n = n - 1
print('n的值为:'+str(n))
```
运行输出结果为:
```
n的值为:9
n的值为:8
n的值为:7
n的值为:6
n的值为:5
n的值为:4
n的值为:3
n的值为:2
n的值为:1
n的值为:0
```
墨博士:嗯,while循环使用简单,可读性强,用处还是很多的,为了巩固你的理解,一起来做个练习吧:
> 计算1到100的数字相加之和
# [插画:数学老师说:计算1-100相加之和,高斯说:1+100=101=2+99=3+98·······,结果是5050,小墨说:使用循环就可以了]
小墨:这不是数学家高斯小时候做的那道题嘛,1+100=101=2+99=3+98·······,最后就相当于101乘以50,结果是5050。
墨博士:嗯,如果使用Python来计算,思路要回到普通的1+2+3+4+……上来。你想一下怎么做。
小墨:普通的1+2+3的思路,嗯……可以把上面的程序改一改,让n的初始值是100,然后利用while循环,让它循环个100次,然后每次都让循环变量减去1。
在循环**外**定义一个变量,用于将所有的n的值都加起来。
```
# 定义循环变量并初始化为100
n = 100
# 定义相加的结果变量,用于接收所有n相加的结果
sum = 0
while n > 0:
# 将每次循环的n都加起来
sum = sum + n
n = n - 1
print('计算结果为:'+str(sum))
```
墨博士:嗯,不错嘛。这里还有一个小技巧,你看sum = sum + n这句,是将sum的值和n的值相加后再次赋值给sum本身,它有个简单的写法,是sum += n,就相当于sum = sum + n。这里的+=也是个赋值运算符,我们可以称它为累加赋值运算符,表示累加上某些东西之后再赋值(=)。另外需要注意这个+=是一个运算符(不是两个),使用的时候中间不能加空格。
> 墨博士提醒;除了+=,还有-=、*=、/=、%=等,表示减等、乘等、除等、余等。Python中的完整运算符及用法请参考附录B。
小墨:好的我记住了。
## 4.3 for 循环
墨博士:接下来我们再来说另一种循环:for循环。它的完整结构为:
```
for x in m:
语句
```
其中x是一个变量,m需要是一个list类型或tuple类型的数据,这个我们后面单独再说,你可以简单理解为是一堆数据,x就从这一堆数据中逐个取值,如果能取到值,则执行语句,然后取下个值,直到m中的值全被取完了,则循环结束。比如
```
for x in [1, 2, 3, 4, 5]:
print(x)
```
x会从[1, 2, 3, 4, 5]中依次取出值,然后执行后面缩进的print语句。
输出结果为:
```
1
2
3
4
5
```
小墨:这个似乎比while更简洁一些,但是可读性差一些。
墨博士:实际使用时根据情况选择用哪种循环就行。来做个练习吧:
> 使用for循环,计算1到10的相加之和。
小墨:结合这上面的while循环中的累加的案例,可以这样写
```
# 定义变量用于接收累加的结果
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
# 累加
sum += x
print(sum)
```
墨博士:不错。不过问题也来了,如果想要计算1到100的相加之和呢,难道要从1写到100吗?太麻烦了!这时候我们可以使用range(n)。
range(n)是一个函数(函数也称为方法)。一个函数就是一个功能,Python提供了很多函数供我们使用,方便我们编写代码。如果把编程比作盖房子,那么函数就是已经做好的门啊窗啊钢筋啊之类的,我们可以直接拿来盖自己的房子,而不需要自己去森林里伐木做门做窗、去矿山上挖矿炼钢。
# [插画:森林里伐木、矿山上挖矿]
range(n)中的n是你可以指定的数,这个函数的功能是用于生成0到n-1的整数序列,比如range(5),就可以得到0 1 2 3 4这五个数。
```
for x in range(5):
print(x)
```
输出
```
0
1
2
3
4
```
和使用for x in [0, 1, 2, 3, 4]效果是一样的。
接下来,就交给你了小墨,编写下使用range函数配合for循环计算0到100(包括100)相加的程序吧。
小墨:好的,我试试:
```
sum = 0
# 因为要包括100,这里使用range(101)生成0-100的数,for循环依次取出这些数
for x in range(101):
# 累加
sum += x
print(sum)
```
墨博士:嗯,range还有个用法就是传入两个数range(m, n),可以得到从m到n-1间所有的数,比如range(60,81)就会得到60到80间的所有数,包括60,包括80,但是不包括81。
小墨:所以计算60-80(含)间数字相加的结果的程序可以这样:
```
sum = 0
for x in range(60, 81):
# 累加
sum += x
print(sum)
```
墨博士:是的。除了range()函数外,其实我们也已经学了其他的函数了。比如str()用于将整数转为字符串、int()用于将字符串转为整数、甚至print()用于将内容输出到屏幕上、input()用于接收用户的输入等都是函数。你发现什么共同点了吗?
小墨:共同点……,他们后面都跟着个小括号?
墨博士:是的!所有的函数后面都跟着有小括号,小括号中会根据需要要求我们传入相应的内容。这个我们会在后面的函数章节详细的讲解。
关于循环的使用我们就先说这么多了。
小墨:博士,我听墨哥哥曾经说过死循环,那是什么?
墨博士:墨哥哥当时怎么说的?
小墨:那天墨哥哥手机欠费停机,用网上银行充值,然后需要手机验证码,他就嘟囔了那么一句。
# [插画:手机欠费,请充值]
墨博士:……
## 4.4 死循环
### 4.4.1 什么是死循环
墨博士:如果一个循环的循环条件一直被满足,或者说循环体没有结束循环的可能,那么循环体就会一直执行,这种情况称为死循环,也称为无限循环。
小墨:墨哥哥那个就是结束不了了。
墨博士:是的。Python中的死循环一般用while循环来写,写法如下:
```
while True:
print("Hello")
```
因为在Python中,非0都可以用于表示True,0则表示False,所以上面的死循环也可以写成:
```
while 1:
print("Hello")
```
小墨:我来试试。编写代码,F5运行,哇好厉害!IDLE中一直在不停的打印Hello,这个打印要是丰富多彩一些,就有点黑客电影的意思啦。
# [插画补充:黑客电影电脑屏幕]
### 4.4.2 死循环的结束
小墨:诶!不对呀博士,这个我要想结束怎么办?
墨博士:死循环就是那种没法自己结束的循环,只能我们手动去结束它。在IDLE中想结束死循环使用快捷键Ctrl + C即可,效果如下:
![](https://img.kancloud.cn/b6/a1/b6a1fd0918134ecb44b61e21bd2db229_812x525.png)
或者直接关闭IDLE,会提示你是否要结束程序,选择确定就把程序结束掉了。
小墨:终于关掉了!这个死循环还挺吓人。它一般用来干什么呢?
墨博士:后面我们做的很多程序中你都能看到它的身影,另外,死循环还可以与break和continue相结合使用,来做成不定次数的循环。
## 4.5 break
墨博士:break是打破、中断的意思,在Python中可以用于结束循环
```
n = 5
while n >= 0:
if n == 2:
break
print(n)
n = n - 1
```
输出结果为:
```
5
4
3
```
当n为2时,进入if分支,break结束了整个循环,开始执行后面的代码,后面没有代码则整个程序就结束了。
## 4.6 continue
墨博士:continue是继续的意思,在Python中也用于结束循环,但是区别于break,continue是结束本轮循环并开始下轮循环,break是结束整个循环。比如:
```
for x in range(5):
if x == 2:
continue
print(x)
```
输出结果为:
```
0
1
3
4
```
当x等于2的时候,进入if x == 2的分支中,执行continue这句,表示本次循环就此结束,不再执行print(x)这句。然后循环继续,x取出range(5)中的3,打印,依次类推。
小墨,这些你都记住了吗?
小墨:记住了。break用于结束整个循环,continue用于结束本轮循环但不结束整个循环。
## 4.7 小墨的猜数字游戏
墨博士:好了,接下来就来完成猜数字游戏,规则如下:
> 给定一个100以内的数字,然后让用户输入数字去猜,程序判断用户输入的数字是猜大了、小了还是对了,如果大了或小了,则继续猜,如果猜对了则结束程序。
小墨,接下来就看你的啦。
小墨:我先来分析一下:整个过程不知道会猜多少次,所以我没法给固定的猜的次数,应该使用一个死循环让用户能一直猜。而如果猜对了,则使用break结束循环从而结束程序,如果猜错了,就结束本轮循环,让他继续猜,是这样的吧博士。
墨博士:没错,继续说吧。
小墨:我的程序是这样的
```
n = 65
while True:
ni = int(input('输入猜的数字:'))
if ni > n:
print('大了')
continue
elif ni < n:
print('小了')
continue
else:
print('猜对了')
break
```
博士,不准看我的代码,我已经运行起来了,你来猜一猜吧。
博士:好,我来猜一下:
```
输入猜的数字:50
小了
输入猜的数字:75
大了
输入猜的数字:63
小了
输入猜的数字:69
大了
输入猜的数字:66
大了
输入猜的数字:65
猜对了
```
小墨:你怎么猜这么快?
墨博士:其实我还可以更快,因为我看到你代码中的n=65了。
小墨:……
墨博士:哈哈,你看这个游戏,如果事先已经知道了要猜的数字,就毫无可玩性了。如果我们的程序每次运行能自己生成一个随机的数字,连程序的编写者都不知道会是什么的一个数字就好了。
小墨:这样就不能作弊了。那该如何实现呢?
墨博士:这个用random模块来解决。
## 4.8 random
墨博士:在python中,一个.py文件就是一个模块,可以看作一堆功能的整体。random模块用来生成随机数。这是Python官方给我们提供的,方便我们编写代码。
小墨:官方还真是周到啊,不光提供给了我们很多函数,还提供给了我们很多模块。
墨博士:是的。函数包含在模块中,一个模块由很多的函数(和其他的东西)组成,一个函数是一个功能,所以说它是一堆功能的整体。
小墨,你来新建一个Python文件,输入以下代码:
```
import random
print(random.randint(0, 1))
```
注意保存的时候不要把自己新建的这个Python文件起名叫random,会和官方提供的random模块冲突。
**多次**运行,看看程序会输出什么?
小墨:有时候输出0,有时候输出1,这就是随机吧。
墨博士:嗯第1句import random用于将random模块引入进来,为什么需要引入呢?我用你的课桌来说一下。你看现在你课桌上只有笔记本、水杯和你的仙人球,为什么你不把书包、文具盒、雨伞之类的全放你课桌上,而是把这些东西放在了旁边的架子上呢?
# [插画:小墨的书桌,书桌上有电脑、水杯和仙人球,书桌旁边有饮水机等]
小墨:都放课桌上也放不下呀,即使放的下也乱七八糟的。
墨博士:Python中对于函数的设计也是这样的,它把最常用的放在你手边,你直接就可以用,比如print()函数,str()函数等,而把不常用的放在了另外的模块中搁在了其他的地方,如果你想使用,需要先使用import把模块引入进来,就像你想拿文具盒中的铅笔时才会把文具盒拿过来一样。
小墨:哦我懂了,常用的放手边直接就能用,不常用的放一边先拿过来再用。
墨博士:是这个意思。再来看第2行,print后面括号中的内容是random.randint(0, 1),前面的random就是我们引入的模块,想使用模块中的函数,需要使用模块后跟点.,点后面的randint就是函数了。
小墨:这个函数和range一样,可以写两个数进去呢。
墨博士:是的,random.randint(a,b)函数会**随机**得到数字 n ,n 为 a 到 b 之间的数字(a <= n <= b),包含 a 和 b本身。这里我们传入0,1就表示,就随机得到0或者1了。
小墨:哦,我明白了,我使用这个random模块把我的程序给改造改造。
墨博士:注意把import那句放到最前面,先引进来,再使用,就像先把文具盒拿过来,再取出其中的铅笔用一个道理。
小墨:知道啦。代码如下:
```
import random
n = random.randint(0, 100)
while True:
ni = int(input('输入猜的数字:'))
if ni > n:
print('大了')
continue
elif ni < n:
print('小了')
continue
else:
print('猜对了')
break
```
改好了博士,这次你再来猜一猜吧。
博士:好,我来猜一猜:
```
输入猜的数字:50
小了
输入猜的数字:75
小了
输入猜的数字:88
大了
输入猜的数字:82
大了
输入猜的数字:78
大了
输入猜的数字:76
小了
输入猜的数字:77
猜对了
```
小墨:猜的还是挺快的嘛!我要再研究研究,做出一款更好玩的猜数字游戏出来。
## 4.9 本章小结
墨博士总结:关于循环结构到这里就结束了。
在本章中,你首先学习了两种循环结构的用法,并使用循环完成了100内数字的累加。程序如下:
```
sum = 0
for x in range(60, 81):
sum += x
print(sum)
```
这个程序运行结果没问题,但还是有两个地方需要我们注意:
一个就是这个sum,在某些IDE下可能会报警告,这是因为Python中有个自带的sum,他们重名了。所以最好改一下名字,比如叫my_sum
第二个问题是关于变量的,变量需要先初始化才能使用,比如
```
my_sum
print(my_sum)
```
运行就会报错:
```
NameError: name 'my_sum' is not defined
```
所以这么这里给sum指定了个0,这个称为变量的初始化。
除了两种循环结构,你还学习了循环条件永远被满足时的特殊循环:死循环,以及break和continue在循环语句中的使用。最后我们一起做了个猜数字的游戏,这个游戏中除了用到了我们学习到的分支结构和循环结构,还用到了random模块去生成随机数,这是很多程序,尤其是游戏中经常会用到的一点。