[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即可,效果如下:

或者直接關閉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模塊去生成隨機數,這是很多程序,尤其是游戲中經常會用到的一點。