带你读《Python 程序设计与问题求解(原书第2版)》之三:循环和选择语句-阿里云开发者社区

开发者社区> 华章出版社> 正文

带你读《Python 程序设计与问题求解(原书第2版)》之三:循环和选择语句

简介: 本书是为计算机科学专业程序设计课程编写的教材,选用流行且易于教学的Python语言,主要涵盖五个方面的内容:编程基础,包括数据类型、控制结构、 算法设计等;面向对象编程,包括基础原则和应用方法;数据和信息处理,包括字符串、数组、文件、列表等;软件开发生命周期,包括大量案例研究;编程实践,包括数字和文本处理、事件驱动编程、图形图像处理、网络通信等。书中的每个新概念只在解决问题时才被引入,重视培养良好的编程习惯,适合所有程序设计初学者阅读。

点击查看第一章
点击查看第二章

第3章

循环和选择语句
完成本章的学习之后,你将能够做到以下几点:
● 写一个循环来重复一系列固定的动作。
● 写一个循环遍历字符串中的字符序列。
● 写一个向下计数的循环和一个向上计数的循环。
● 写一个入口控制的循环,当一个条件为假时,该循环停止。
● 使用选择语句在程序中进行选择。
● 构造适当的条件,为条件控制的循环选择语句。
● 使用逻辑运算符构造复合布尔表达式。
● 使用选择语句和break语句退出不受入口控制的循环。

到目前为止,本书中研究的所有程序都是由一条接一条执行的指令短序列组成的。即使我们允许指令序列相当长,这种程序也不会很有用。像人类一样,计算机必须能够重复执行一系列动作,并且必须能够选择在特定情况下要执行的操作。本章集中讨论控制语句——允许计算机选择或重复一个动作的语句。

3.1 确定迭代:for循环

我们从重复语句开始研究控制语句,重复语句也称为循环,它可以重复一个动作。动作的每一次重复都被称为传递迭代。有两种类型的循环:重复一个动作预定次数的循环(确定迭代);执行该动作直到程序确定它需要停止的循环(不定迭代)。在本节中,我们将研究Python的for循环,这是最容易实现确定迭代的控制语句。

3.1.1 执行语句给定次数

在《科学怪人》中,当弗兰肯斯坦博士的怪物苏醒过来时,他不停地喊道:“它还活着!它还活着!”一台计算机可以轻松地打印这些感叹词,不仅仅是两次,而是十几次或一百次,你不需要写两次、十几次或一百次输出语句就可以做到这一点。下面是一个for循环,它运行相同的输出语句四次:

image.png

这个循环反复调用一个函数——print函数。第一行的常数4告诉循环调用这个函数的次数。如果我们想打印10或100个感叹句,只需将4改为10或100。这种for循环的语法格式是

image.png

循环中的第一行代码有时称为循环头。目前,循环头中唯一相关的信息是整数表达式,它表示循环执行的迭代次数。冒号(:)结束循环头。循环体包括循环头下面剩余代码行中的语句。这些语句在循环的每一遍中按顺序执行。注意,循环体中的语句必须缩进并对齐在同一列中。IDLE的shell或script窗口将自动缩进循环头下的行,但是如果缩进中出现了一个空格,你可能会看到语法错误。如果移动到下一行代码时没有自动缩进,最好缩进四个空格。
现在让我们探索Python的幂运算符如何在循环中实现。回想一下,这个运算符计算一个数字的给定的幂。例如,表达式2**3计算值image.png,或2*2*2。下面的代码使用循环来计算非负指数的幂运算。我们使用三个变量来指定数字、指数和乘积。乘积最初是1。每次通过循环时,乘积乘以该数,并重置为结果。为了追踪这个过程,乘积的值在每一次循环中都输出一次。

image.png

正如你所看到的,如果指数为0,循环体将不会执行,并且product的值将保持为1,这是任何数字的零次幂的值。
例子中变量的使用证明了我们的指数循环是一种解决一般问题的算法。这个特定循环的用户不仅可以将2次幂提升到3次幂,还可以计算任何数字的任何非负幂,只需用不同的值替换变量numberexponent

3.1.2 计数控制循环

当Python执行刚才讨论的for循环类型时,它会从0开始计数到循环头整数表达式的值减去1。每次通过循环时,循环头的变量都绑定到此计数的当前值。以下代码段演示了这一点:

image.png

在一系列数字中计数的循环也称为计数控制循环。计算中经常使用每次传递的计数值。例如,考虑4的阶乘,即1×2×3×4=24。计算此值的代码段以1的乘积开始,并将此变量重置为每次循环中乘以循环计数加1的结果,如下所示:

image.png

注意,count+1的值用于每次传递,以确保使用的数字是1到4,而不是0到3。
为了从明确的下限开始计数,程序员可以在循环头中提供第二个整数表达式。当向range提供两个参数时,计数的范围从第一个参数到第二个参数减1。以下代码段使用这个变量来简化循环体中的代码:

image.png

在这个版本中唯一需要注意的是range的第二个参数,它应该指定一个比所需计数上限大1的整数。以下是for循环的这个版本的形式:

image.png

从一系列值中累计一个结果值是计算中常见的操作。下面是一个求和的例子,它累加从下限到上限的一系列数字的总和:

image.png


注意,我们使用变量theSum而不是sum来累加代码中的数字之和。因为sum是Python中内置函数的名称,所以在我们的代码中避免将这样的名称用于其他目的是一个好主意。

3.1.3 增量赋值

像x=x+1或x=x+2这样的表达式经常出现在循环中,Python为它们提供了缩写形式。赋值符号可以与算术和级联运算符相结合,以提供增量赋值操作。以下是几个例子:

image.png

所有这些例子都有这样的格式:

image.png

相当于

image.png

注意,和=之间没有空格。增量赋值操作和标准赋值操作具有相同的优先顺序。

3.1.4 循环错误:大小差一错误

for循环不仅易于编写,而且不容易出错。一旦语法正确,我们只需要关注一个可能的错误:循环无法执行预期的迭代次数。因为这个数字通常是只差一的,所以这个错误被称为大小差一错误。大多数情况下,当程序员错误地指定循环的上限时,就会产生大小差一错误。程序员可能希望下面的循环从1到4计数,但是它却从1到3计数:

image.png

注意,这不是语法错误,而是逻辑错误。与语法错误不同,Python解释器不会检测到逻辑错误,只有仔细检查程序输出的程序员才会检测到。

3.1.5 遍历数据序列的内容

虽然我们一直使用for循环作为一个简单的计数控制循环,但是循环本身访问由range函数生成的一系列数字中的每个数字。以下代码段显示了这些序列:

image.png

在本例中,函数range生成的数字序列被送到Python的list函数,该函数返回一种特殊类型的序列,称为列表。字符串也是字符序列。通过运行for循环,可以访问任何序列中包含的值,如下所示:

image.png

在循环的每一次传递中,变量都绑定到或分配到序列中的下一个值,从第一个值开始,到最后一个值结束。下面的代码段遍历或访问两个序列中的所有元素,并打印其中包含的值,用空格隔开:

image.png

3.1.6 指定range函数的步长

到目前为止,我们已经看到的计数控制循环都是通过一系列连续的数字进行计数。然而,在一些程序中,我们可能希望循环跳过一些数字,也许每隔一个或每隔三个访问一次。Python中range函数的一个变体包括第三个参数,它允许你跳过一些数字。第三个参数指定一个步长值,或者range函数内使用的数字之间的间隔,如下例所示:

image.png

现在,假设你必须计算1到10之间偶数的总和。下面是解决这个问题的代码:

image.png

3.1.7 向下计数的循环

到目前为止,我们所有的循环都从下限向上计数。偶尔,有的问题需要以从上限向下到下限的相反方向计数。例如,当前10名的单曲被发行时,它们可能会按从最低(第10)到最高(第1)的顺序出现。在以下代码中,循环显示从10到1的计数,以说明如何做到这一点:

image.png

当步长参数为负数时,range函数生成从第一个参数向下到第二个参数加1的一系列数字。因此,在这种情况下,第一个参数应该表示上限,第二个参数应该表示下限减1。

image.png

3.2 格式化输出文本

在开始下一个案例研究之前,我们需要更仔细地检查输出文本的格式。许多数据处理应用程序需要具有表格格式的输出,如电子表格或数字数据表中使用的输出。在这种格式中,数字和其他信息在可以左对齐或右对齐的列中对齐。如果一列数据的值从最左边的字符开始垂直对齐,则该列数据是左对齐的。如果数据列的值从最右边的字符开始垂直对齐,则数据是列右对齐的。为了保持数据列之间的边距,左对齐要求在数据右侧添加空格,而右对齐要求在数据左侧添加空格。如果一列数据的两边有相等数量的空格,则该列数据是居中对齐的。
格式化字符串中给定数据的数据字符和附加空格的总数称为字段宽度
print函数自动开始打印第一个可用列中的输出数据。以下示例显示指数7至10以及10^7至10^10的值,print函数产生的两列格式如下:

image.png

注意,当指数达到10时,第二列的输出移位一个空格,看起来不整齐。如果左列是左对齐的,右列是右对齐的,输出看起来会更整洁。当我们格式化输出浮点数时,通常希望指定要显示的精度位数以及字段宽度。当显示精确到两位数的金融数据时,这一点尤为重要。
Python包括一个通用格式化机制,允许程序员为不同类型的数据指定字段宽度。以下代码展示了在字段宽度为6的情况下字符串"four"的左对齐和右对齐:

image.png

第一行代码右对齐字符串,左边用两个空格填充。下一行代码通过在字符串右侧放置两个空格来实现左对齐。
此操作的最简单形式如下:

image.png

其中包含格式字符串格式运算符%和要格式化的单个数据值。格式字符串可以包含字符串数据和其他关于数据格式的信息。在示例中,为了格式化字符串数据值,我们在格式化字符串中使用了符号%s。当字段宽度为正时,数据是右对齐的;当字段宽度为负时,数据是左对齐的。如果字段宽度小于或等于基准的打印长度(以字符为单位),则不会添加对齐。运算符%使用此信息构建并返回格式化字符串。
要格式化整数,则使用字母d而不是s。要格式化一系列数据值,需要构建一个格式字符串,其中包含每个数据的格式代码,并将数据值放在%运算符后面的元组中。此操作的第二个版本形式如下:

image.png

有了格式操作,我们求10的幂的循环现在可以以精确对齐的列显示数字。第一列在字段宽度为3时左对齐,第二列在字段宽度为12时右对齐。

image.png

float类型数据值的格式信息具有以下形式:

image.png

其中,.是可选的。以下代码显示一个浮点数字的输出,分别为没有格式字符串和有格式字符串:

image.png

下面是使用格式字符串的另一个小示例,即使用字段宽度6和精度3来格式化浮点值3.14:

image.png

注意,Python在字符串中添加了一个表示精度的数字,并在数字左边填充一个空格,以使字段宽度为6。这个宽度包括小数点所占的位置。

image.png

3.3 案例研究:投资报告

据说复利是世界第八大奇迹。下面这个计算投资报告的案例研究显示了原因。
image.png请求
编写一个计算投资报告的程序。
image.png分析
该程序的输入如下:
● 初始投资金额(浮点数)。
● 年数(整数)。
● 利率(以整数表示的百分比)。
该程序使用一种简化形式的复利,其中利息每年计算一次,并加到投资总额中。程序的输出是一份表格形式的报告,显示了投资期限内每年的年份、该年度账户的初始余额、该年度赚取的利息以及该年度的年末余额。表格的第一行用适当的标题标记。在表格输出之后,程序打印该期间的投资余额总额和利息收入总额。建议的用户界面如图3-1所示。

image.png

image.png设计
程序的四个主要部分执行以下任务:
1.接收用户输入并初始化数据。
2.显示表格的标题。
3.计算每年的结果,并在表格中显示为一行。
4.显示总数。
程序的第三部分用循环实现计算及结果的显示。下面是程序伪代码的一个稍微简化的版本,没有关于格式化输出的细节:

image.png

注意,起始余额是指原始输入余额,也指每年开始的余额。在这一点上忽略输出的细节让我们专注于正确的计算。我们可以将这段伪代码翻译成Python程序来检查计算过程。程序的粗略草稿叫作原型。一旦确信原型正在产生正确的数字,就可以回到设计中,并制定输出格式的细节。
输出的格式要将数字在列中很好地对齐。我们使用格式字符串来右对齐每行输出上的所有数字。我们还将格式字符串用于表标题中的字符串标签。经过反复试验,我们得出了年份、起始余额、利息和结束余额的字段宽度,分别为4、18、10和16。我们也可以在标题的格式字符串中使用这些宽度。
image.png实现(编码)
程序代码显示了设计中描述的每一个主要部分,以行尾注释作为划分。注意,其中使用了许多变量来跟踪程序使用的各种金额。明智的是,我们为这些变量选择了明确描述其目的的名称。print语句中的格式字符串相当复杂,但是我们已经努力对它们进行格式化,以便它们包含的信息仍然可读性较高。

image.png

image.png测试
测试包含循环的程序时,我们应该首先关注决定迭代次数的输入。在我们的项目中,这个值是年数。我们输入一个值,产生尽可能少的迭代次数,然后将这个数字增加1,再然后使用稍大的数字,如5,最后使用接近最大预期的数字,如50(在我们的问题领域,这可能是投资的最大现实周期)。其他输入的值,如我们这个项目中的投资金额和利率,应该相当小,并在测试的这个阶段保持不变。如果程序为所有这些输入产生了正确的输出,我们可以确信循环工作正常。
在下一阶段的测试中,我们将检查其他输入对结果的影响,包括它们的格式。我们知道,项目中的另外两项投入——投资和利率——已经在值较小时产生了正确的结果。一个合理的策略可能是用最小和最大的年数以及最小的利率来测试大量投资,然后用最大的年数和最大的合理利率来测试。表3-1给出了程序的这些测试数据集。

image.png

3.4 选择:if语句和if-else语句

我们已经看到,计算机可以处理长长的指令序列,并且可以重复这样做。然而,并非所有的问题都能以这种方式解决。在某些情况下,计算机可能会面临两种不同的行动方案,而不是直接执行下一条指令。计算机必须停下来检查或测试一个条件,这个条件表达了关于当时状态的一种假设。如果条件为真,计算机执行第一个动作并跳过第二个动作。如果条件为假,计算机跳过第一个动作并执行第二个动作。
换句话说,计算机不是盲目前进,而是通过对环境条件做出反应来锻炼智能。在本节中,我们将探讨几种类型的选择语句或控制语句,它们允许计算机进行选择。但是首先,我们需要考虑计算机如何测试条件。

3.4.1 布尔类型、比较和布尔表达式

在Python程序中测试条件之前,你需要了解布尔数据类型,这是以19世纪英国数学家乔治·布尔的名字命名的。布尔数据类型仅包含两个数据值——真和假。在Python中,布尔字面量可以用几种方式编写,但是大多数程序员更喜欢使用标准值True和False。
简单的布尔表达式包括布尔值True或False、绑定到这些值的变量、返回布尔值的函数调用或比较。选择语句中的条件通常采取比较的形式。例如,你可以比较值A和值B,看看哪一个更大。比较的结果是布尔值。值A大于值B是真,否则是假。要写进行比较的表达式,你必须熟悉Python的比较运算符,如表3-2所示。

image.png

以下代码显示了一些示例比较及其值:

image.png

注意“==”表示相等,而“=”表示赋值。正如你在第2章中所了解的,在计算Python中的表达式时,你需要了解优先级,即运算符在复杂表达式中的应用顺序。比较运算符在加法之后但在赋值之前应用。

3.4.2 if-else语句

if-else语句是最常见的选择语句类型。它也被称为双路选择语句,因为它指导计算机在两种不同的行动方案中做出选择。
if-else语句通常用于检查输入是否有错误,并在必要时使用错误消息进行响应。另一种选择是,如果输入有效,则继续执行计算。
例如,假设一个程序输入圆的面积,并计算和输出其半径。这个程序的合法输入应该是正数。但是,用户仍然可以错误地输入零或负数。因为程序别无选择,只能使用这个值来计算半径,所以它可能会崩溃(停止运行)或产生无意义的输出。以下代码段展示了如何使用if-else语句定位(陷入)此错误并对其做出响应:

image.png

以下是Python中if-else语句的语法:

image.png

if-else语句中的条件必须是布尔表达式,即计算结果为真或假的表达式。这两个可能的动作都由一系列语句组成。注意,每组语句必须相对于ifelse符号缩进至少一个空格。最后,注意条件后面的冒号(:)和else这个词。图3-2显示了if-else语句语义的流程图。在图中,包含问号的菱形表示条件。

image.png

以下示例打印两个输入数字的最大值和最小值。

image.png

Python包括maxmin两个函数,因此不必使用if-else语句。在以下示例中,函数max返回其参数中的最大值,而min返回其参数中的最小值:

image.png

3.4.3 单路选择语句

最简单的选择形式是if语句。这种类型的控制语句也称为单路选择语句,因为它由一个条件和一系列语句组成。如果条件为真,则运行语句序列。否则,程序将跳到整个选择语句之后的下一一个语句。以下是if语句的语法:

image.png

图3-3显示了if语句语义的流程图。

image.png

简单的if语句通常用于在条件不正确时阻止执行某个操作。例如,负数的绝对值是对那个数字的算术求反,否则就是那个数字本身。以下代码使用简单的if语句将变量值重置为绝对值:

image.png

3.4.4 多路if语句

偶尔,程序会面临测试几个条件的情况,这些条件需要两个以上的行动方案。例如,考;虑将数字等级转换为字母等级的问题。表3-3显示了一个简单的评分方案,该方案基于两个假设:数字评分范围从0到100,字母评分为A、B、C和F。

image.png

用语言来说明:使用这种方案的算法会声明,如果数字大于89,则字母等级为A,否则如果数字等级大于79,则字母等级为B……否则(默认情况下)字母等级为F。测试几个条件并做出相应响应的过程可以用代码通过多路选择语句来描述。下面是一个简短的Python脚本,它使用这样的语句来确定和打印与输入数字等级相对应的字母等级:

image.png

多路if语句考虑每个条件,直到一个条件为真或者所有条件为假。当条件为True时,相应的操作为执行当前条件下的语句并跳到整个选择语句的末尾。如果没有条件为True,则执行else之后的操作。
多路if语句的语法如下:

image.png

缩进再次帮助人类读者和Python解释器看到这个控制语句的逻辑结构。图3-4显示了具有两个条件和else子句的多路if语句的语义流程图。

image.png

3.4.5 逻辑运算符和复合布尔表达式

如果两个条件中的任何一个是真的,通常必须采取行动。例如,程序的有效输入通常位于给定的范围内,超过这个范围的任何输入都应该被错误信息拒绝,低于这个范围的任何输入也应该以类似的方式处理。以下代码段只接受等级转换脚本的有效输入,否则会显示错误消息:

image.png

注意,前两个条件与相同的操作相关联。换句话说,如果第一个条件为真或者第二个条件为真,程序输出相同的错误消息。这两个条件可以组合成使用逻辑运算符or的布尔表达式。得到的复合布尔表达式稍微简化了代码,如下所示:

image.png

描述这种情况的另一种方式是,如果数字大于等于0且小于等于100,那么我们希望程序执行计算并输出结果;否则,它应该输出错误消息。逻辑运算符and可用于构造不同的复合布尔表达式来表示此逻辑:

image.png

Python包括所有三个布尔或逻辑运算符:andornotand运算符和or运算符都需要两个操作数。and运算符在两个操作数都为真时返回True,否则返回Falseor运算符仅在两个操作数都为False时返回False,否则返回Truenot运算符需要一个操作数,如果为假,则返回其逻辑否定,如果为真,则返回False
每个运算符的行为可以在该运算符的真值表中完全指定。真值表中第一行下面的每一行都包含操作数的值和将运算符应用于操作数所产生的值的一种可能组合。第一行显示了操作数和正在计算的表达式。图3-5显示了andornot的真值表。

image.png

以下例子验证了图3-5真值表中的一些值:

image.png

在第2章中,你看到乘法和除法比加法和减法有更高的优先级。这意味着优先级较高的运算符会首先被计算,即使它们出现在优先级较低的运算符右侧。同样的想法也适用于比较运算符、逻辑运算符和赋值运算符。逻辑运算符在比较运算符之后但在赋值运算符之前进行计算。not运算符的优先级高于and运算符,and运算符的优先级高于or运算符。因此,在.我们的示例中,not A and B为假,而not (A and B)为真。虽然通常不必担心大多数代码中的运算符优先级,但你可能会看到如下代码,其中显示了所有不同类型的运算符:

image.png

表3-4总结了本书到目前为止所讨论的运算符的优先级。

image.png

3.4.6 短路计算

Python虚拟机有时在计算完所有操作数之前就知道布尔表达式的值。例如,在表达式AandB中,如果A是假的,那么表达式也是假的,没有必要考虑B
同样,在表达式AorB中,如果A是真的,那么表达式也是真的,没有必要考虑B。这种计算尽快停止的方法被称为短路计算
有时短路计算是有利的。考虑以下示例:

image.png

如果用户输入0进行计数,则该条件包含一个除以零的潜在计算;然而,由于短路计算,避免了除以零的情况发生。

3.4.7 测试选择语句

选择语句为程序添加了额外的逻辑,但它们也为额外的逻辑错误打开了大门。因此,在测试包含选择语句的程序时要特别小心。
第一条经验法则是确保选择语句中所有可能的分支或选项都得到执行。如果测试数据包含使每个条件为真以及使每个条件为假的值,则能够保证这一点。在我们的等级转换示例中,测试数据应该包括产生每个字母等级的数字。
在测试所有操作之后,你还应该检查所有条件。例如,当一个条件包含两个数字的比较时,试着用相等的操作数、减1的左操作数和加1的左操作数来测试程序,以捕捉边界情况下的错误。
最后,你需要使用产生所有可能的操作数值组合的数据来测试包含复合布尔表达式的条件。作为测试复合布尔表达式的蓝图,使用该表达式的真值表。

image.png
image.png

3.5 条件迭代:while循环

前面我们研究了for循环,它执行一组语句,执行次数由程序员指定。然而,在许多情况下,循环中的迭代次数是不可预测的。这个循环最终完成了工作,但是只有当条件改变时才如此。例如,程序可能会要求用户输入一组值。在这种情况下,只有用户知道她将输入的内容。程序的输入循环接收这些值,直到用户输入终止输入的特殊值或标记。这种类型的过程称为条件迭代,这意味着只要条件保持为真,这个过程就会继续重复。在本节中,我们将探讨while循环并用此来描述条件迭代。

3.5.1 while循环的结构和行为

条件迭代要求在循环中测试一个条件,以确定循环是否应该继续。这种条件被称为循环的继续条件。如果继续条件为假,则循环结束。如果继续条件为真,则循环中的语句将再次执行。while循环是为这种类型的控制逻辑量身定制的。以下是它的语法:

image.png

该语句的形式几乎与单路选择语句的形式相同。但是,使用保留字while而不是if表示只要条件保持为真,语句序列可能会执行多次。
显然,为了使循环的继续条件为假,最终必须在循环体内发生一些事情。否则,循环将永远持续下去,这种错误称为无限循环。循环主体中至少有一条语句必须更新影响条件值的变量。图3-6显示了while循环语义的流程图。

image.png

下面的示例是一个简短的脚本,提示用户输入一系列数字,计算它们的总和,并输出结果。当用户简单地按下Return或Enter键时,程序停止输入过程,而不是强迫用户输入一定数量的值。程序将此值识别为空字符串。我们首先以伪代码算法的形式给出一个粗略的草稿:

image.png

注意,有两个输入语句,一个在循环头之前,一个在循环体的底部。第一条输入语句将变量初始化为循环条件可以测试的值。这个变量也称为循环控制变量。第二条输入语句获得其他输入值,包括终止循环的输入值。还要注意,输入必须以字符串形式接收,而不是数字形式,因此程序可以测试空字符串。如果字符串不是空的,我们假设它代表一个数字,并将它转换成一个浮点数。下面是这个脚本的Python代码,包括示例运行的结果:

image.png

在这次运行中,有四个输入,包括空字符串。现在,假设我们再次运行脚本,用户在第一次提示时输入空字符串。while循环的条件不成立,它的循环体根本不执行!总和打印为0.0。
while循环也被称为入口控制循环,因为它的条件是在循环的顶部测试的。这意味着循环中的语句可以执行零次或更多次。

3.5.2 使用while循环的计数控制

你也可以将while循环用于计数控制的循环。接下来的两段代码分别显示了for循环和while循环的相同求和运算。

image.png

尽管两个循环产生了相同的结果,但还是存在折中。第二段代码明显更复杂,它包括一个布尔表达式和两个引用变量count的额外语句。这个循环控制变量必须显式地在循环头之前初始化,并在循环体中增加。变量count也必须在显式连续条件下检查。对于程序员来说,这种额外的手工劳动不仅耗时,而且可能是循环逻辑中新错误的来源。
相比之下,for循环在循环头中简洁地指定控制信息,并在幕后自动操作。然而,我们很快就会看到while循环是唯一的解决方案。因此,你必须掌握while循环的逻辑,并且要知道它们可能产生的逻辑错误。
以下示例显示了倒计时脚本的两个版本,从上限10到下限1。请你自己思考哪一个更容易理解和正确编写。

image.png

3.5.3 whileTrue循环和break语句

虽然while循环可能很难正确编写,但可以简化其结构,从而提高可读性。本节的第一个示例脚本包含两个输入语句,是一种不错的改进。如果我们在循环内接收第一个输入并且测试显示继续条件为假,则可以简化此循环的结构。这意味着将实际测试推迟到循环中间。Python包含break语句,允许我们在程序中进行此更改。这是修改后的脚本:

image.png

首先要注意的是循环的进入条件是布尔值True。有些读者可能会对这种情况感到震惊,这似乎意味着循环永远不会退出。但是,这种情况非常容易编写,并保证循环体至少执行一次。在此循环中,接收输入数据,然后在单路选择语句中测试循环的终止条件。如果用户想要退出,则输入将等于空字符串,break语句将导致退出循环。否则,控制将继续到选择语句后面处理输入的两个语句。
我们的下一个示例修改了等级转换程序的输入部分,以继续从用户获取输入数字,直到其输入可接受的值。该循环的逻辑类似于前一个示例的逻辑。

image.png

运行以上代码的交互如下:

image.png

一些计算机科学家认为,延迟退出的while True循环违反了while循环的精神。但是,在循环体必须至少执行一次的情况下,这种技术简化了代码并且实际上使程序的逻辑更清晰。如果你没有被这种推理说服并仍想测试循环顶部的继续和退出条件,则可以使用布尔变量来控制循环。以下是使用布尔变量的数字输入循环的一个版本:

image.png

有关此问题的经典讨论,请参阅EricRoberts的文章,“LoopExitsandStructuredProgramming:ReopeningtheDebate”,ACMSIGCSEBulletin,Volume27,Number1,March1995,pp.268-272。
虽然break语句在控制至少一次迭代的循环时非常有用,但是你应该主要将它用作这些循环的单一出口点。

3.5.4 随机数

到目前为止,我们的算法所做的选择完全是由给定的条件决定的,这些条件要么是真的,要么是假的。许多情况下,比如游戏,在做出的选择中包含一些随机性。例如,我们可以掷硬币来决定谁在足球比赛中开球,硬币正面朝上或反面朝上的概率相等。同样,在许多游戏中,骰子滚动时,数字1到6正面朝上的概率相等。为了在计算机应用中模拟这种随机性,编程语言包括用于生成随机数的资源。Python的random模块支持多种方法,但是最简单的方法是用两个整数参数调用random.randint函数。函数random.randint从两个参数之间的数字中返回一个随机数(包括这两个参数本身)。以下代码模拟骰子滚动10次:

image.png

尽管在这个简单的调用集中重复了一些值,但是在大量调用的过程中,值的分布接近真正的随机性。
我们现在可以使用random.randint、选择和循环来开发一个简单的猜谜游戏。在启动时,用户输入范围内的最小数字和最大数字。然后计算机从这个范围中选择一个数字。在每次循环中,用户输入一个数字,试图猜测计算机选择的数字。程序的回应是“你猜对了”“太大了,再试一次”或者“太小了,再试一次”。当用户最终猜到正确的数字时,程序会祝贺他,并告诉他猜测的总数。下面是程序的代码及其示例运行:

image.png
image.png

注意,我们的代码旨在允许用户智能猜测数字,从两个初始数字之间的中点开始,每次不正确的猜测都会消除一半的剩余数字。理想情况下,用户应该能够在不超过log2(最大值-最小值+1)次的尝试中猜出正确的数字。你将在练习和编程项目中探索log2的概念。

3.5.5 循环逻辑、错误和测试

你已经看到while循环通常是条件控制循环,这意味着它的继续与否取决于给定条件是真还是假。循环可能是最复杂的控制语句,为了确保它们的正确执行,需要仔细设计和测试。测试while循环必须结合用于for循环和选择语句的测试元素。测试while循环期间要排除的错误包括初始化不正确的循环控制变量、未能在循环中正确更新该变量以及未能在继续状态下正确测试该变量。此外,如果程序员只是忘记更新控制变量,结果导致出现无限循环,这甚至不符合算法的条件!要停止在测试期间似乎挂起的循环,请在终端窗口或IDLE中键入Control+C。
真正的条件控制循环易于设计和测试。如果继续条件可以在循环入口进行检查,那么就在那里进行检查,并提供产生0、1和至少5次迭代的测试数据。
如果循环必须运行至少一次,请使用while True循环,并延迟对终止条件的检查,直到它在循环主体中可用。确保在循环中会发生一些事情,以允许检查条件并最终到达break语句。

image.png
image.png

3.6 案例研究:近似平方根

袖珍计算器或Python math模块的用户不必考虑如何计算平方根,但是构建计算器或为该模块编写代码的人肯定会考虑。在这个案例研究中,我们深入其中,看看如何做到这一点。
image.png请求
编写一个计算平方根的程序。
image.png分析
这个程序的输入是一个正浮点数或整数。输出是一个浮点数,代表输入数的平方根。为了便于比较,我们同时使用math.sqrt输出Python对平方根的计算。以下是用户交互过程示例:

image.png

image.png设计
17世纪,艾萨克·牛顿爵士发现了一种近似正数平方根的算法。回想一下,正数x的平方根y是数字y,因此y^2=x。牛顿发现,如果对y的初始估计是z,那么通过将z的平均值与x/z相加,可以获得对y的更好估计。这个估计可以通过该规则反复变换,直到达到满意的估计。
在Python解释器中的快速编码展示了这种连续近似的方法。令x为25,初始估计z为1。然后我们用牛顿的方法将z重置为一个更好的估计值,并检查z是否接近实际平方根5。以下是代码记录:

image.png

经过三次变换后,z的值正好等于5,即25的平方根。为了包含具有无理数平方根的数字,例如2和10,我们可以将初始猜测设为1.0来产生浮点结果。
我们现在开发了一种算法来自动化连续变换的过程,因为可能有很多变换,我们不想把它们都写出来。到底需要多少次这样的运算取决于我们希望最终近似值接近实际平方根的程度。这个接近值叫作容差,可以在任何给定时间与x的值和我们估计的平方之间的差进行比较。若二者之差大于容差,这一过程继续,否则会停止。容差通常很小,例如0.000001。
我们的算法允许用户输入数字,使用循环应用牛顿法计算平方根,并打印这个值。下面是伪代码,后面是具体解释。

image.png

因为初始估计是1.0,所以循环必须计算至少一个新的估计。因此,我们使用一个while True循环。这个循环在确定估计值是否足够接近容差值以停止该过程之前变换估计值。当我们估计的平方与原始数字之间的差值小于或等于容差值时,该过程应该停止。注意,这种差异可能是正的也可能是负的,因此我们在检查之前使用abs函数来获得其绝对值。
while循环更传统的用法是将差异与循环头中的容差进行比较。然而,必须在循环之前将差值初始化为一个大的、相当无意义的值。这里介绍的算法更清晰、更简单地体现了逐次逼近法的逻辑。
image.png实现(编码)
这个程序的代码很简单。

image.png
image.png

image.png测试
这个程序的有效输入是正整数和浮点数。Python本身对平方根的最准确计算为评估我们自己的算法的正确性提供了一个基准。我们至少应该提供几个完美的平方数,比如4和9,以及平方根不精确的数字,比如2和3。还应该包括介于1和0之间的数字,例如0.25。因为我们的算法的准确性也取决于容差的大小,所以也可以在测试期间改变这个值。

3.7 本章小结

● 控制语句决定程序中其他语句的执行顺序。
● 确定迭代是执行一组语句的过程,执行次数是固定的、可预测的。for循环是一种简单方便的控制语句,描述了确定的迭代。
for循环由循环头和循环体的语句组成。循环头包含控制主体执行次数的信息。
for循环可以通过一系列整数计数,这种循环称为计数控制环路。
● 在for循环的计数控制执行期间,循环体中的语句可以使用循环头的变量引用计数的当前值。
● Python的range函数在计数控制的for循环中生成数字序列。此函数可以接收一个、两个或三个参数。单个参数M指定数字0到M-1的序列。两个参数M和N指定数字M到N-1的序列。当S为正时,三个参数M、N和S指定数字M到N-1的序列,按S步进;当S为负时,则为M到N+1。
for循环可以遍历和访问序列中的值。示例序列是字符串和数字列表。
● 格式化字符串及其运算符%允许程序员使用字段宽度和精度格式化数据。
● 当一个循环没有执行预期的迭代次数时,就会出现大小差一错误,即多了一个或少了一个。这个错误可能是由计数控制循环中不正确的下限或上限引起的。
● 布尔表达式包含值TrueFalse、绑定到这些值的变量、使用关系运算符的比较或使用逻辑运算符的其他布尔表达式。布尔表达式计算为真或假,用于在程序中形成条件。
● 逻辑运算符andornot用于构造复合布尔表达式。这些表达式的值可以通过构造真值表来确定。
● Python在复合布尔表达式中使用短路计算。对or的操作数的求值在第一个真值处停止,而对and的操作数的求值在第一个假值处停止。
● 选择语句是允许程序进行选择的控制语句。选择语句包含一个或多个条件和相应的操作。计算机没有直接进行下一步操作,而是检查一个条件。如果条件为真,计算机将执行相应的操作,然后移动到选择语句之后的操作。否则,计算机会移动到下一个条件(如果有),或者移动到选择语句之后的操作。
● 双路选择语句也称为if-else语句,有一个条件和两个可选的操作过程。单路选择语句也称为if语句,具有单个条件和单个操作过程。多路选择语句也称为扩展if语句,至少有两个条件和三个可选的操作过程。
● 条件迭代是在条件成立时执行一组语句的过程。当条件变为假时,迭代停止。因为不能总是预测何时会发生这种情况,所以迭代次数通常无法预测。
while循环用于描述条件迭代。这个循环由循环头和一组循环体语句组成。循环头包含循环的继续条件,只要继续条件为真,循环体就会执行。
while循环是一个入口控制循环。这意味着在循环入口测试继续条件,如果它是假的,循环的主体将不会执行。因此,while循环可以描述零次或更多次迭代。
break语句可用于从主体退出while循环。当循环必须执行至少一次迭代时,通常使用break语句。在这种情况下,循环头的条件是值Truebreak语句嵌套在测试终止条件的if语句中。
● 任何for循环都可以转换为等效的while循环。在计数控制的while循环中,程序员必须初始化和更新循环控制变量。
● 当循环的继续条件从未变为假,并且没有提供其他出口点时,就会出现无限循环。无限循环的主要原因是程序员未能正确更新循环控制变量。
● 函数random.randint返回两个参数指定范围内的随机数。

3.8 复习题

1.循环头为for count in range(10),其循环体中的语句执行多少次? 
a.9次
b.10次
c.11次

2.for循环便于: 
a.在程序中做出选择
b.以可预测的次数运行一组语句
c.通过一系列数字进行计数
d.描述条件迭代

3.for count in range(5):print(count,end=“ “)循环的输出是什么? 
a.12345
b.1234
c.01234

4.当range函数接收到两个参数时,第二个参数指定了什么? 
a.整数序列的最后一个值
b.整数序列的最后一个值加1
c.整数序列的最后一个值减1

5.考虑以下代码段:

image.png

 这段代码打印什么值? 
a.4
b.5

6.使用and运算符的布尔表达式在以下哪种情况下返回True: 
a.两个操作数都为真
b.一个操作数为真
c.两个操作数都不为真

7.默认情况下,while循环是一个: 
a.入口控制循环
b.出口控制循环

8.考虑以下代码段:

image.png

这段代码的输出是什么?
a.1 2 3 4 5
b.2 3 4 5
c.5 4 3 2 1
d.5 4 3 2

9.考虑以下代码段:

image.png

下列哪一项描述了此代码中的错误?
a.循环一次后终止
b.循环控制变量没有正确初始化
c.比较的方式有问题
d.循环是无限的

10.考虑以下代码段:

image.png

这个循环执行多少次迭代?
a.不执行
b.至少1次
c.零次或者多次
d.10次

3.9 编程项目

1.编写一个程序,接收三角形三条边的长度作为输入。程序输出应该表明三角形是否是等边三角形。

2.编写一个程序,接收三角形三条边的长度作为输入。程序输出应该表明三角形是否是直角三角形。回想毕达哥拉斯定理,在直角三角形中,一边的平方等于另两边的平方之和。

3.修改3.5节中的猜谜游戏程序,以便用户想到计算机必须猜的数字。计算机必须做出不超过指定最小次数的猜测,并且必须通过输入误导提示来防止用户作弊。(提示:使用math.log函数计算输入下限和上限后所需的最小猜测次数。)

4.做一项科学实验:丢下一个球,看看它反弹多高。一旦确定了球的“弹性”,这个比率就代表弹性指数。例如,如果一个球从10英尺的高度跌落,反弹6英尺高,则指数为0.6,一次反弹后,球行进的总距离为16英尺。如果球继续弹跳,两次弹跳后的距离将是10英尺+6英尺+6英尺+3.6英尺=25.6英尺。注意,每次连续弹跳的行进距离是指球回到地面的距离加上该距离乘以0.6。编写一个程序,让用户输入球落下的初始高度和允许球继续弹跳的次数。输出应该是球行进的总距离。

5.某地的生物学家需要一个程序来预测种群增长。输入将是生物体的初始数量、增长率(大于0的实数)、达到这个速度所需的小时数以及种群增长的小时数。例如,可以从500个有机体的种群开始,增长率为2,以及达到该增长率所需的6小时的生长周期。假设没有一种生物死亡,这意味着这一种群每6小时就会增加一倍。因此,在6小时的生长后,将有1000个生物,12小时后,将有2000个生物。编写一个接收这些输入并显示总种群预测的程序。

6.德国数学家戈特弗里德·莱布尼茨提出了以下方法来近似π的值:
π/4=1-1/3+1/5-1/7+...
编写一个程序,允许用户指定这个近似中使用的迭代次数,并显示结果值。

7.大多数学区的教师都是按时间表支付工资的,该时间表根据他们的教学经验提供工资。例如,列克星敦学区的初任教师第一年可能会得到30000美元。在第一年之后的每一年,直到10年,教师都会获得比之前增加2%的收入。编写一个程序,以表格形式为学区的教师显示工资计划。输入包括起薪、加薪百分比和年数。时间表中的每一行都应该包含该年度的年份和薪资。

8.两个正整数(A和B)的最大公约数(GCD)是可以整除这两个整数的最大数字。欧几里得算法可用于寻找两个正整数的最大公约数。你可以按以下方式使用此算法: 
a.计算大数字除以小数字的余数。 b.用较小的数字替换较大的数字,用剩余的数字替换较小的数字。 c.重复此过程,直到较小的数字为零。
此时较大的数字是A和B的GCD。编写一个程序,让用户输入两个整数,然后打印使用欧几里得算法查找GCD过程中的每个步骤。

9.编写一个程序,从用户那里接收一系列数字,并允许用户按下Enter键来表示输入完成。用户按下Enter键后,程序应该打印数字和它们的平均值。

10.TidBit电脑商店的信贷计划规定了10%的首付和12%的年利率。每月付款是列出的购买价格的5%减去首付款。编写一个以购买价格为输入的程序。该程序应该显示一个表格,其中有适当的标题,显示贷款期限内的付款时间表。表格的每一行都应该包含以下项目:
●月份(从1开始)
●当前欠款总额
●当月所欠利息
●当月所欠本金
●当月付款
●付款后剩余余额
一个月的利息金额等于余额×利率/12。一个月的本金金额等于每月付款减去所欠利息。

11.在“幸运七”的游戏中,玩家掷骰子,如果点数加起来是7,则赢4美元,否则将损失1美元。为了吸引玩家,游戏推广商说有很多赢的方法:(1,6),(2,5),等等。然而做一点数学分析就知道,没有足够的方法来确保玩家赢得这场游戏。然而,因为许多人一提到数学就眼花缭乱,所以你的挑战是编写一个程序来证明玩这个游戏是徒劳的。你的程序应该把玩家想要投入的钱作为输入,并且玩到钱输光为止。程序应该打印玩家输光前的掷骰数,以及所剩余的最大金额。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接