### 3.4.2 while 循环
for 循环要求预先确定循环的次数,但有很多问题难以预先确定循环次数,只知道在什么 条件下需要循环,这时可以使用 while 语句。Python 语言中 while 语句的常用格式是:
```
while <布尔表达式>:
<循环体>
```
其语义是:当布尔表达式计算为 True 时,执行一遍循环体,执行完毕控制转回 while 语句的开始处重新测试布尔表达式;当布尔表达式计算为 False 时,控制转向下一条语句。注意, 循环体部分相对于 while 部分要左缩进。while 语句的流程图如图 3.9 所示。
![](https://box.kancloud.cn/2016-02-22_56cafcde1ae46.png)
图 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 所示①。
![](https://box.kancloud.cn/2016-02-22_56cafcde2f9a3.png)
图 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 事件
- 参考文献