### 3.4.2 while 循環
for 循環要求預先確定循環的次數,但有很多問題難以預先確定循環次數,只知道在什么 條件下需要循環,這時可以使用 while 語句。Python 語言中 while 語句的常用格式是:
```
while <布爾表達式>:
<循環體>
```
其語義是:當布爾表達式計算為 True 時,執行一遍循環體,執行完畢控制轉回 while 語句的開始處重新測試布爾表達式;當布爾表達式計算為 False 時,控制轉向下一條語句。注意, 循環體部分相對于 while 部分要左縮進。while 語句的流程圖如圖 3.9 所示。

圖 3.9 while 循環的流程圖
顯然,while 語句的循環次數取決于布爾表達式何時變成 False,而不是預先確定循環次 數。稍微深入思考一下就會發現一個問題:萬一布爾表達式永遠不會變成 False 怎么辦?如 果進入循環語句時布爾表達式計算為 True,而循環體又不會影響布爾表達式的值,那么執行 完循環體后控制回到循環入口處時,布爾表達式仍然為 True。這種情況下 while 循環永遠不 會停下來,稱為無窮循環。程序中如果有一個無窮循環就意味著程序無法終止,我們常說程 序進入了“死循環”,這是程序設計中經常出現的錯誤。要想避免無窮循環,必須使循環體對 布爾表達式的值產生影響,例如改變布爾表達式中所用到的變量的值。
在實際編程中,while 循環有一些常用的套路或稱模式,值得讀者熟記于心。下面通過“對 一批數據求和”的例子來介紹這些循環模式。
交互式循環
考慮這樣的應用:用戶不斷輸入數據,程序得到數據后不斷累加,最后算出輸入數據的 總和。顯然,這是一個“輸入——累加”的反復循環的過程。由于用戶輸入數據的個數不是 預先給定的,故無法用 for 循環來實現,而 while 語句則能輕松解決這個問題。為了對循環進 行控制,我們每次循環前都詢問用戶是否還有新數據。這種通過與用戶進行交互來決定是否 需要循環的模式稱為交互式循環,可以用偽代碼表達如下:
```
將循環控制變量 moredata 初始化為"yes"
while moredata 值為"yes":
獲得用戶輸入的下一個數據
處理該數據
詢問用戶是否還有輸入數據,為變量 moredata 賦值
```
下面是利用交互式循環實現的完整程序。
【程序 3.10】eg3_10.py
```
sum = 0 moredata = "yes"
while moredata[0] == "y":
x = input("Input a number: ")
sum = sum + x
moredata = raw_input("More numbers? (yes/no) ")
print "The sum is", sum
```
下面是這個程序的執行情況:
```
Input a number: 2
More numbers? (yes/no) yes
Input a number: 5
More numbers? (yes/no) yeah
Input a number: 8
More numbers? (yes/no) no
The sum is 15
```
要說明的是,這個程序通過檢查 moredata[0]來控制是否循環,因此只要用戶輸入的首字 母是"y"就能進入循環體執行,而任何非 y 開頭的輸入都導致停止循環。
此版本有個不好的地方:用戶需要不停地先回答是否還有數據,有的話再輸入數據。這 對用戶來說有點煩瑣。也就是說,交互式循環其實并不適合“輸入 n 個數據求和”的問題。
哨兵循環
解決“輸入數據求和”問題的更好方法是使用哨兵循環,即不斷執行“輸入——累加” 這個循環體,直至遇到一個稱為“哨兵”的特殊數據值。任何值都可以當作哨兵,關鍵是它 必須與正常數據值相互區別。哨兵循環的一般模式如下:
```
前導輸入
while 不是哨兵:
處理數據
循環尾輸入
```
首先,在循環開始之前需要利用“前導輸入”獲取第一個數據。如果該數據是哨兵,則 不會進入循環,控制轉向 while 的下一條語句;如果是正常數據,則進入循環處理之。在循 環體的末尾讀取下一個數據,并轉到循環開始處的哨兵測試。如此周而復始,直至遇見哨兵 循環才終止。
注意,哨兵循環用到了兩條一模一樣的輸入語句:一條是位于循環體之前的“前導輸入”, 另一條是位于循環體末尾的“循環尾輸入”。這兩條輸入語句缺一不可:少了前導輸入則無法 進入循環;少了循環尾輸入則無法讀取下一數據,從而循環一直在對第一個數據進行處理, 導致無窮循環。
使用哨兵循環,需要選擇一個特殊數據作為哨兵。這個特殊數據一般和正常數據屬于同 樣的類型,以便能被 while 語句統一檢測。如果哨兵數據和正常數據屬于不同類型,那么 while 語句的條件表達式就會變復雜,因為需要處理兩種類型的數據。具體選擇什么數據作為哨兵,要看具體的應用場景。例如,假設我們輸入的數據是考試成績(非負數),那么可以選-1 作 為哨兵,因為負數是不可能成為合法考試成績的。又假如我們輸入的數據是人名(字符串), 那么可以選空串作為哨兵。一個很常用的場景是文件處理,即輸入的數據來自一個文件,這 時可以很自然地在文件末尾存放一個特殊數據作為哨兵,很多語言甚至提供專門的測試文件 尾的手段(如常量 EOF①或函數 eof()之類)。關于文件處理詳見第 6 章。
下面我們用哨兵循環來實現求和程序。
【程序 3.11】eg3_11.py
```
sum = 0
x = input("Input a number (-1 to quit): ")
while x >= 0:
sum = sum + x
x = input("Input a number (-1 to quit): ")
print "The sum is", sum
```
可見哨兵循環與交互式循環不同,不需要用戶不停地回答 yes 來處理數據。下面是此程序的執行情況:
```
Input a number (-1 to quit): 2
Input a number (-1 to quit): 5
Input a number (-1 to quit): 8
Input a number (-1 to quit): -1
The sum is 15
```
如果程序 3.11 中待求和的數據是任意實數的話,那么-1 可能是正常數據,不能作為哨
兵。事實上,所有實數都不能作為哨兵。這時可以采用字符串類型來解決問題,因為正常的 輸入數據(正數、負數和 0)都可以表示為由阿拉伯數字組成的非空字符串,從而包含非數 字字符的字符串都可以用作哨兵。最簡單最常用的特殊字符串是空字符串""(注意兩個引號 之間沒有東西),當用戶在輸入時直接鍵入回車,Python 即返回空串。當然,這種做法中對 數據的處理要麻煩一點,需要將字符串轉換為數值類型以便進行求和計算。下面是以字符串 方式輸入數值數據的求和程序版本:
【程序 3.12】eg3_12.py
```
sum = 0
x = raw_input("Input a number (<Enter> to quit): ")
while x != "":
sum = sum + eval(x)
x = raw_input("Input a number (<Enter> to quit): ")
print "The sum is", sum
```
執行示例如下:
```
Input a number (<Enter> to quit): 2
Input a number (<Enter> to quit): 5
Input a number (<Enter> to quit): -8
Input a number (<Enter> to quit):
The sum is -1
```
此運行示例的第四行中,輸入時直接按回車鍵,導致 Python 將空串賦值給了 x,從而使循環 終止。
> ① 意為 End-Of-File
在哨兵循環模式中有一個容易犯錯誤的地方:當用戶的前導輸入本身就是哨兵,從而導致循環一次也不執行時,while 后面的語句可能沒有預料這種情況,導致無法正確執行。例如, 我們將程序 3.11 改成計算平均值,算法基本不變:反復輸入數據,在循環中累加數據總和 sum 及數據計數 count,當用戶輸入哨兵-1 時退出循環并計算平均值。代碼如下:
```
sum = 0
count = 0
x = input("Input a number (-1 to quit): ")
while x >= 0:
sum = sum + x count = count + 1
x = input("Input a number (-1 to quit): ")
print "The average is", sum / count
```
運行此程序,并且首先輸入-1,看看會發生什么?是的,由于未進入循環,sum 和 count 都保持為初始值 0,while 的下一條語句在計算平均值的時候發生了除數為 0 的錯誤!
后測試循環
前面介紹的 while 循環例子都是“前測試”循環,即先檢測循環條件,再進入循環。顯 然,如果首次測試條件得到 False,則循環體就會一次也不執行。有些實際應用問題要求循環 體必須至少執行一次,例如“輸入合法性檢查”問題。用戶輸入數據,程序檢查用戶的輸入 是否合法:如果合法則程序繼續向后執行,否則就回到前面要求用戶重新輸入,直至輸入合 法為止。這種輸入合法性檢查在程序設計中是非常普遍的,好的程序員應該盡量對用戶的輸 入進行合法性檢查。為了實現輸入合法性檢查,顯然需要先獲得用戶的輸入,然后進入循環 語句,即循環至少會執行一次,最后再測試條件決定是否繼續循環。因此,我們需要一種“后 測試”循環結構。
有一些語言提供了 repeat-until 或者 do-while 循環結構來實現后測試循環,其語義分別是 “重復做某事,直至滿足某條件”和“做某事,當滿足條件時重復”。這種循環結構的流程圖 如圖 3.10 所示①。

圖 3.10 后測試循環控制結構
從圖中可見循環體至少執行一次,而循環條件檢測位于循環體的最后,這正是“后測試”名稱的由來。
> ① 細微差別:這個流程圖其實是 repeat-until 結構。將 True 和 False 互換位置,才是 do-while 結構。
Python 語言沒有提供類似 repeat-until 結構的語句,但我們不難用 while 來實現后測試循環:只要保證循環條件初始為 True,自然就會執行一次循環體,而后續循環由條件測試決定。 例如,假設程序要求用戶輸入一個正數,則可用下面的代碼片段來檢查輸入合法性:
```
x = -1
while x < 0:
x = input("Please input a positive number: ")
```
其中第一行的作用是使首次條件檢測為真,因此循環體至少執行一次;接下去的條件檢測就 相當于位于循環體最后,整個語句也就等價于后測試循環。
不難看出,程序 3.10 中的交互式循環將 moredata 初始化為 True,因此實際上是后測試 循環的一種,效果是使用戶至少輸入一個數據。
while 計數器循環
雖然一般來說 for 語句用于固定次數的循環,while 語句用于不定次數的循環,但兩者之 間并無本質不同,完全可以用 while 來實現固定次數的循環。為了循環 n 次,用 while 實現的 計數器循環模式如下:
```
計數器 count 置為 0
while count < n:
處理代碼
count = count + 1
```
可見,用 while 語句實現計數器循環時需要手動控制循環控制變量 count 的變化,而 for 語句是自動處理的。使用時具體要注意兩點:第一,必須為 count 賦初值,否則 while 后的布 爾表達式無法計算;第二,必須在循環體中改變 count 的值,否則會導致無窮循環。
例如,前面罰寫 10 遍“煩”字的計數器循環,可以用 while 語句實現如下:
```
>>> i = 0
>>> while i < 10:
print "煩",
i = i + 1
煩 煩 煩 煩 煩 煩 煩 煩 煩 煩
```
在此例中,i 初值為 0,以后每次循環都加 1,由于從 0 到 9 都是滿足循環條件的,所以 總共執行 10 次循環。第 10 次循環后,i 值為 10,不滿足循環條件,故退出循環,控制轉到 下一條語句。
上面的 while 計數器循環模式是讓計數器從小到大變化,同樣常用的還有讓計數器從大 到小變化的模式:
```
計數器 count 置為 n
while count > 0
處理代碼
count = count - 1
```
使用這種模式實現上面的例子,代碼如下:
```
>>> i = 10
>>> while i > 0:
print "煩", i = i - 1
煩 煩 煩 煩 煩 煩 煩 煩 煩 煩
```
值得一提的是,計算機編程中我們經常是從 0 開始計數的,而不是日常生活中的從 1 開始。上例中的 while 語句都是對從 0 到 9 的 10 個值進行循環。如果讀者更習慣從 1 開始計數, 也可以先為 count 賦予初值 1,然后在循環體中不斷加 1,從而實現對從 1 到 10 的 10 個值進 行循環,這樣能與日常說的第 1 次、第 2 次、…、第 10 次在序數上對應起來。但是要注意的 是,這時需要將循環條件改成 count<=10 或 count<11。循環控制變量的邊界值是初學者容易 犯錯的地方,經常導致循環次數多 1 次或少 1 次。
- 前言
- 第 1 章 計算與計算思維
- 1.1 什么是計算?
- 1.1.1 計算機與計算
- 1.1.2 計算機語言
- 1.1.3 算法
- 1.1.4 實現
- 1.2 什么是計算思維?
- 1.2.1 計算思維的基本原則
- 1.2.2 計算思維的具體例子
- 1.2.3 日常生活中的計算思維
- 1.2.4 計算思維對其他學科的影響
- 1.3 初識 Python
- 1.3.1 Python 簡介
- 1.3.2 第一個程序
- 1.3.3 程序的執行方式
- 1.3.4 Python 語言的基本成分
- 1.4 程序排錯
- 1.5 練習
- 第 2 章 用數據表示現實世界
- 2.1 數據和數據類型
- 2.1.1 數據是對現實的抽象
- 2.1.1 常量與變量
- 2.1.2 數據類型
- 2.1.3 Python 的動態類型*
- 2.2 數值類型
- 2.2.1 整數類型 int
- 2.2.2 長整數類型 long
- 2.2.3 浮點數類型 float
- 2.2.4 數學庫模塊 math
- 2.2.5 復數類型 complex*
- 2.3 字符串類型 str
- 2.3.1 字符串類型的字面值形式
- 2.3.2 字符串類型的操作
- 2.3.3 字符的機內表示
- 2.3.4 字符串類型與其他類型的轉換
- 2.3.5 字符串庫 string
- 2.4 布爾類型 bool
- 2.4.1 關系運算
- 2.4.2 邏輯運算
- 2.4.3 布爾代數運算定律*
- 2.4.4 Python 中真假的表示與計算*
- 2.5 列表和元組類型
- 2.5.1 列表類型 list
- 2.5.2 元組類型 tuple
- 2.6 數據的輸入和輸出
- 2.6.1 數據的輸入
- 2.6.2 數據的輸出
- 2.6.3 格式化輸出
- 2.7 編程案例:查找問題
- 2.8 練習
- 第 3 章 數據處理的流程控制
- 3.1 順序控制結構
- 3.2 分支控制結構
- 3.2.1 單分支結構
- 3.2.2 兩路分支結構
- 3.2.3 多路分支結構
- 3.3 異常處理
- 3.3.1 傳統的錯誤檢測方法
- 3.3.2 傳統錯誤檢測方法的缺點
- 3.3.3 異常處理機制
- 3.4 循環控制結構
- 3.4.1 for 循環
- 3.4.2 while 循環
- 3.4.3 循環的非正常中斷
- 3.4.4 嵌套循環
- 3.5 結構化程序設計
- 3.5.1 程序開發過程
- 3.5.2 結構化程序設計的基本內容
- 3.6 編程案例:如何求 n 個數據的最大值?
- 3.6.1 幾種解題策略
- 3.6.2 經驗總結
- 3.7 Python 布爾表達式用作控制結構*
- 3.8 練習
- 第 4 章 模塊化編程
- 4.1 模塊化編程基本概念
- 4.1.1 模塊化設計概述
- 4.1.2 模塊化編程
- 4.1.3 編程語言對模塊化編程的支持
- 4.2 Python 語言中的函數
- 4.2.1 用函數減少重復代碼 首先看一個簡單的用字符畫一棵樹的程序:
- 4.2.2 用函數改善程序結構
- 4.2.3 用函數增強程序的通用性
- 4.2.4 小結:函數的定義與調用
- 4.2.5 變量的作用域
- 4.2.6 函數的返回值
- 4.3 自頂向下設計
- 4.3.1 頂層設計
- 4.3.2 第二層設計
- 4.3.3 第三層設計
- 4.3.4 第四層設計
- 4.3.5 自底向上實現與單元測試
- 4.3.6 開發過程小結
- 4.4 Python 模塊*
- 4.4.1 模塊的創建和使用
- 4.4.2 Python 程序架構
- 4.4.3 標準庫模塊
- 4.4.4 模塊的有條件執行
- 4.5 練習
- 第 5 章 圖形編程
- 5.1 概述
- 5.1.1 計算可視化
- 5.1.2 圖形是復雜數據
- 5.1.3 用對象表示復雜數據
- 5.2 Tkinter 圖形編程
- 5.2.1 導入模塊及創建根窗口
- 5.2.2 創建畫布
- 5.2.3 在畫布上繪圖
- 5.2.4 圖形的事件處理
- 5.3 編程案例
- 5.3.1 統計圖表
- 5.3.2 計算機動畫
- 5.4 軟件的層次化設計:一個案例
- 5.4.1 層次化體系結構
- 5.4.2 案例:圖形庫 graphics
- 5.4.3 graphics 與面向對象
- 5.5 練習
- 第 6 章 大量數據的表示和處理
- 6.1 概述
- 6.2 有序的數據集合體
- 6.2.1 字符串
- 6.2.2 列表
- 6.2.3 元組
- 6.3 無序的數據集合體
- 6.3.1 集合
- 6.3.2 字典
- 6.4 文件
- 6.4.1 文件的基本概念
- 6.4.2 文件操作
- 6.4.3 編程案例:文本文件分析
- 6.4.4 緩沖
- 6.4.5 二進制文件與隨機存取*
- 6.5 幾種高級數據結構*
- 6.5.1 鏈表
- 6.5.2 堆棧
- 6.5.3 隊列
- 6.6 練習
- 第 7 章 面向對象思想與編程
- 7.1 數據與操作:兩種觀點
- 7.1.1 面向過程觀點
- 7.1.2 面向對象觀點
- 7.1.3 類是類型概念的發展
- 7.2 面向對象編程
- 7.2.1 類的定義
- 7.2.2 對象的創建
- 7.2.3 對象方法的調用
- 7.2.4 編程實例:模擬炮彈飛行
- 7.2.5 類與模塊化
- 7.2.6 對象的集合體
- 7.3 超類與子類*
- 7.3.1 繼承
- 7.3.2 覆寫
- 7.3.3 多態性
- 7.4 面向對象設計*
- 7.5 練習
- 第 8 章 圖形用戶界面
- 8.1 圖形用戶界面概述
- 8.1.1 程序的用戶界面
- 8.1.2 圖形界面的組成
- 8.1.3 事件驅動
- 8.2 GUI 編程
- 8.2.1 UI 編程概述
- 8.2.2 初識 Tkinter
- 8.2.3 常見 GUI 構件的用法
- 8.2.4 布局
- 8.2.5 對話框*
- 8.3 Tkinter 事件驅動編程
- 8.3.1 事件和事件對象
- 8.3.2 事件處理
- 8.4 模型-視圖設計方法
- 8.4.1 將 GUI 應用程序封裝成對象
- 8.4.2 模型與視圖
- 8.4.3 編程案例:匯率換算器
- 8.5 練習
- 第 9 章 模擬與并發
- 9.1 模擬
- 9.1.1 計算機建模
- 9.1.2 隨機問題的建模與模擬
- 9.1.3 編程案例:乒乓球比賽模擬
- 9.2 原型法
- 9.3 并行計算*
- 9.3.1 串行、并發與并行
- 9.3.2 進程與線程
- 9.3.3 多線程編程的應用
- 9.3.4 Python 多線程編程
- 9.3.5 小結
- 9.4 練習
- 第 10 章 算法設計和分析
- 10.1 枚舉法
- 10.2 遞歸
- 10.3 分治法
- 10.4 貪心法
- 10.5 算法分析
- 10.5.1 算法復雜度
- 10.5.2 算法分析實例
- 10.6 不可計算的問題
- 10.7 練習
- 第 11 章 計算+X
- 11.1 計算數學
- 11.2 生物信息學
- 11.3 計算物理學
- 11.4 計算化學
- 11.5 計算經濟學
- 11.6 練習
- 附錄
- 1 Python 異常處理參考
- 2 Tkinter 畫布方法
- 3 Tkinter 編程參考
- 3.1 構件屬性值的設置
- 3.2 構件的標準屬性
- 3.3 各種構件的屬性
- 3.4 對話框
- 3.5 事件
- 參考文獻