Python程序设计.第3版.pdf

上传人:淡然 文档编号:2115410 上传时间:2019-12-05 格式:PDF 页数:342 大小:17.88MB
返回 下载 相关 举报
Python程序设计.第3版.pdf_第1页
第1页 / 共342页
亲,该文档总共342页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《Python程序设计.第3版.pdf》由会员分享,可在线阅读,更多相关《Python程序设计.第3版.pdf(342页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、美 约翰策勒(John Zelle)著    王海鹏 译国 外 著 名 高 等 院 校 信息科学与技术优秀教材 Python程序设计 (第3版)(第3版)本书以 Python 语言为工具教授计算机程序设计。本书强调解决问题、设计和编程是计算机科学的核心技能。本书特色鲜明、示例生动有趣、内容易读易学,适合 Python 入门程序员阅读,也适合高校计算机专业的教师和学生参考。访问异步社区()的本书页面,可下载本书示例代码、习题解答和教学 PPT。  广泛使用计算机图形学本书提供一个简单的图形软件包 graphics.py 作为示例。  生动有趣的例子本书包含

2、了完整的编程示例来解决实际问题。  亲切自然的行文以自然的叙事风格介绍了重要的计算机科学概念。  灵活的螺旋式学习过程简单地呈现概念,逐渐介绍新的思想,章节末加以巩固强化。  时机恰好地介绍对象本书既不是严格的“早讲对象”,也不是“晚讲对象”,而是在命令式编程的基础上简要地介绍了对象概念。  提供丰富的教学素材提供了大量的章末习题。还提供代码示例和教学 PPT 下载。本书具有以下特点:John Zelle 是美国 Wartburg 大学数学和计算机系教授。他负责教授 Python 程序设计课程,并且结合多年的教学经验编写了本书,在美国高校受到普遍的欢迎。

3、他还从事 VR、AI 等方面的研究,发表了一些机器学习方面的论文。作者简介分类建议:计算机/程序设计/ Python 人民邮电出版社网址:Python程序设计Python Programming: An Introduction to Computer Science, 3rd Edition 程序设计(第版)Python 之父作序推荐    知名 Python 编程入门教程Python程序设计(第3版).indd   1-32017/12/22   上午10:57美 约翰策勒(John Zelle)著    王海鹏 译Python程

4、序设计 (第3版)国 外 著 名 高 等 院 校 信息科学与技术优秀教材 Python程序设计(第3版).indd   42017/12/22   上午10:57版 权 声 明Simplified Chinese translation copyright 2017 by Posts and Telecommunications Press ALL RIGHTS RESERVED Python Programming An Introduction to Computer Science, Third Edition by John M. Zelle. Copyright

5、2017 Franklin, Beedle  wink,wink,你懂的) 。 180 第 9 章  模拟与设计着所有的值都会出现(大约)相等的次数。 random 函数可用于生成伪随机浮点值。它不需要任何参数,返回值均匀分布在 0 和 1 之间(包括 0,但不包括 1) 。下面是一些交互式例子: >>> from random import random  >>> random()   0.545146406725   >>> random()   0.221621655814

6、  >>> random()   0.928877335157   >>> random()   0.258660828538   >>> random()   0.859346793436  模块的名称(random)与函数的名称相同,这让 import 行看起来很有趣。  我们的短柄壁球模拟可以利用 random 函数来确定选手是否赢得了发球回合。我们来看 一个具体的例子。假设选手的发球回合得分概率是 0.70。这意味着他应该赢得 70%的发球 回合。你可以

7、想象一下程序中的一个判断:if : score = score + 1 我们需要插入 70%的次数获胜的概率条件。  假设我们生成一个 01 之间的随机值。区间(0, 1)的 70%正好在 0.7 的左边。所以 70%的时间随机数将小于 0.7,而其他 30%的次数将大于等于 0.7。 (=在上半个区间,因为 随机生成器可以产生 0,但不会产生 1) 。一般来说,如果 prob 表示选手获胜的概率,则 random() scoreB:   winsA = winsA + 1 else:  winsB = winsB + 1  return winsA,

8、winsB  9.3.5  第三层设计 一切似乎都配合得很好。我们继续处理模拟的内部。下一个明显的攻击点是 simOneGame。这里我们实际上必须对短柄壁球规则的逻辑进行编码。选手不断完成回合, 直到游戏结束。这让人想到某种无限循环结构,我们不知道其中一名选手获得 15 分之前需 要多少回合。循环只是持续下去,直到游戏结束。 在这个过程中,我们需要记录得分,我们还需要知道目前谁在发球。分数可能只是两 个整数累积器, 但是我们如何记录谁在发球?它是选手 A 或选手 B。 一种方法是用存储 “A” 或“B”的字符串变量。它也是一种累积器,但更新它的值时,我们只是将它从一个值切

9、换 到另一个值。 以上分析已足够得到一个粗略的算法。我们来试试看:9.3  自顶向下的设计 185 Initialize scores to 0   Set serving to “A“   Loop while game is not over: Simulate one serve of whichever player is serving update the status of the game  Return scores  至少这是一个开始。显然,这方面还有一些工作要做。我们可以快速填充算法的前两 步,得到以下结果:def sim

10、OneGame(probA, probB): scoreA = 0  scoreB = 0  erving = “A“  while : 此时的问题是这个条件到底是什么。只要比赛没结束,就需要继续循环。我们应该能 够通过查看分数来判断游戏是否结束。我们在上一章讨论了这种情况的一些可能性,其中 一些是相当复杂的。让我们将细节隐藏在另一个函数 gameOver 中,如果比赛结束,则返回 True,否则返回 False。这暂时让我们来到循环剩下的部分。  图 9.3 展示了包含新函数的结构图。simOneGame 的代码现在如下: def simOneGame

11、(probA, probB): scoreA = 0  scoreB = 0  serving = “A“  while not gameOver(scoreA, scoreB): 图 9.3  短柄壁球模拟的第三层结构图 在循环中,我们需要发一次球。回忆一下,为了确定发球者是否得分(random() scoreB:  winsA = winsA + 1  else:  winsB = winsB + 1 return winsA, winsB  def simOneGame(probA, probB): # Si

12、mulates a single game or racquetball between players whose #    abilities are represented by the probability of winning a serve. # Returns final scores for A and B  serving = “A“  scoreA = 0  scoreB = 0  while not gameOver(scoreA, scoreB):  if serving = “A“:  

13、if random() >> gameOver(0,0)   False   >>> gameOver(5,10)  False   >>> gameOver(15,3)  True   >>> gameOver(3,15)  True  我选择测试数据,尝试了该函数的所有重要的情形。第一次调用时,得分将为 0 比 0。 该函数正确回应 False,比赛还没结束。随着比赛的进行,该函数将用中间得分调用。第二 个例子表明,该函数再次回应比赛仍在进行中。

14、最后两个例子表明,任何一名选手达到 15 分时,这个函数就能正确地识别比赛结束。 确定了 gameOver 功能正常,现在我们可以回去实现 simOneGame 函数。这个函数有一 些概率行为,所以我不知道输出会是什么。我们能做的最好的测试,是看它的行为是否合 理。下面是示例会话:>>> simOneGame(.5,.5)  (13, 15)   >>> simOneGame(.5,.5)  (15, 11)   >>> simOneGame(.3,.3)  (15, 11)  

15、>>> simOneGame(.3,.3)  (11, 15)   >>> simOneGame(.4,.9)  (4, 15)   >>> simOneGame(.4,.9)  (1, 15)   >>> simOneGame(.9,.4)  (15, 3)   >>> simOneGame(.9,.4)  (15, 0)   >>> simOneGame(.4,.6)  (9

16、, 15)   >>> simOneGame(.4,.6)  (6, 15)  请注意,当概率相等时,分数接近。当概率相差较大时,比赛就是一边倒。这符合我 们预期的函数行为。 我们可以继续这样的部分实现,将组件添加到代码中,同时测试每个组件。软件工程 师称这个过程为“单元测试” 。独立测试每个函数更容易发现错误。当你测试整个程序的时 候,有可能一切顺利。 通过模块化设计实现关注点分离,让我们能够设计复杂的程序。通过单元测试实现关 注点分离,让我们能够实现和调试复杂的程序。当你自己试试这些技术时,你会发现让程 序跑起来的工作量变少了,挫折感也少了很

17、多。9.4.2  模拟结果 最后,我们来看看 Denny Dibblebit 的问题。能力的小差异导致结果的巨大差异,这是190 第 9 章  模拟与设计短柄壁球的特点吗?假设 Denny 赢得约 60%的发球回合,而他的对手要好 5%。Denny 赢得 比赛的机会如何?下面是一个例子,Denny 的对手总是先发球: This program simulates a game of racquetball between two   players called “A“ and “B“. The ability of each player is   i

18、ndicated by a probability (a number between 0 and 1) that  the player wins the point when serving. Player A always   has the first serve.  What is the prob. player A wins a serve? .65  What is the prob. player B wins a serve? .6   How many games to simulate? 5000  Games

19、 simulated: 5000   Wins for A: 3360 (67.2%)  Wins for B: 1640 (32.8%) 尽管能力差距很小,但 Denny 只能在大约三分之一的比赛中获胜。他赢得三局或五局比赛 的机会相当渺茫。显然,Denny 的战绩符合他的能力。他应该跳过心理医生,在比赛中更加努力。   说到比赛,扩展这个程序来计算赢得多局比赛的可能性是一个很好的练习。你为什么 不试试呢?9.5  其他设计技术 自顶向下的设计是一种非常强大的程序设计技术,但并不是创建程序的唯一方法。有 时你可能会陷入困境,不知道如何对它逐级求精。或者

20、原来的规格说明可能相当复杂,以 至于逐级求精太难。9.5.1  原型与螺旋式开发 另一种设计方法是从程序或程序组件的简单版本开始,然后尝试逐渐添加功能,直到 满足完整的规格说明。初始的朴素版本称为“原型” 。原型通常会导致一种“螺旋式”开发 过程。不是拿来整个问题并经过规格说明、设计、实施和测试阶段,我们先设计、实现并 测试一个原型。然后新功能被设计、实现和测试。在开发过程中,我们完成许多小循环, 原型逐渐扩展为最终的程序。 作为一个例子,考虑我们可能如何开发短柄壁球模拟。问题的本质在于模拟一个短柄 壁球的比赛。我们可能就从 simOneGame 函数开始。进一步简化,我们的原型可以

21、假设每 名选手有一半对一半的机会赢得每一分,并且只比赛 30 回合。这需要考虑问题的关键,即 处理得分和换发球。下面是一个示例原型:from random import random def simOneGame(): scoreA = 0  scoreB = 0  serving = “A“  for i in range(30):  if serving = “A“: 9.6  小结 191 if random() = 66 brandom() = 0.66  2以下项不是纯粹的自顶向下设计的一步。 a对较小的问题重复该过程 b用

22、较小问题的接口详细说明算法  c构建一个简化的系统原型 d用较小的问题来表示算法  3设计中组件之间依赖关系视图称为。9.7  练习 193 a流程图  b原型 c界面 d结构图  4模块层次结构图中的箭头表示。 a信息流  b控制流 c粘贴附件 d单行道  5在自顶向下的设计中,设计的子组件是。 a对象   b循环 c函数 d程序  6使用概率事件的模拟称为        。  a蒙特卡罗  b伪随机 cMonty Python d混沌 &nb

23、sp;7在螺旋式开发中使用的系统的初始版本称为。 a入门套件  b原型 c模型 dbeta 版本  8在短柄壁球模拟中,gameOver 函数返回数据类型。 abool   bint cstring dfloat  9字符串格式化模板中百分号表示。 a%    b% c% d%  10系统结构中,最容易开始单元测试的地方是。 a顶部    b底部 c中间 dmain 函数 讨论 1绘制包含以下 main 函数的程序的顶层结构图。 def main(): printIntro()  length,

24、 width = getDimensions()  amtNeeded = computeAmount(length,width) printReport(length, width, amtNeeded)  2用 random 或 randrange 编写一个表达式来计算以下内容。  a范围 010 中的随机整数 b范围0.50.5 中的随机浮点数 c表示六面骰子的投 掷的随机数 d表示两个六面骰子之和的随机数 e范围10.010.0 中的随机浮点数  3用你自己的话来描述什么因素可能导致设计者选择螺旋式开发,而不是自顶向下的 方法。编程练习 1修改短

25、柄壁球模拟,以便计算最佳 n 场比赛结果。先发球是交替的,所以选手 A 在 奇数局比赛中是先发球,选手 B 在偶数局比赛中是先发球。  2修改短柄壁球模拟,考虑零封的规则。你的升级版本应该为两名选手报告获胜的局 数、获胜的百分比、零封的局数以及因此获胜的百分比。 3设计并实现排球比赛模拟。普通的排球像短柄壁球一样,球队只能在发球时得分。 比分上升到 15,但必须至少赢得 2 分。  4大多数排球分站赛现在采用回合计分制。在这个系统中,赢得一个回合的球队得 1 分,即使不是发球的球队。一方得 25 分时比赛结束。设计并实现使用回合计分制的排球 模拟。 5设计并实现一个系统,将

26、普通排球比赛与使用回合计分制的比赛进行比较。你的程194 第 9 章  模拟与设计序应该能够研究回合计分制是否会增强或减弱较好球队的相对优势,还是没有影响。 6设计并实现其他一些球拍运动的模拟(如网球或乒乓球) 。  7花旗骰是在许多赌场玩的骰子游戏。一个玩家掷一双普通的六面骰子。如果初始点 数是 2、3 或 12,则玩家失败。如果是 7 或 11,则玩家获胜。任何其他初始点数将导致玩家 “再掷点” 。也就是说,玩家持续掷骰子直到掷出 7 或重新掷出初始点。如果选手在掷出 7 之前重新掷出初始点,就获胜。先掷出 7 则失败。  编程模拟多次掷骰子游戏,并估计玩家获

27、胜的可能性。例如,如果玩家在 500 场比赛 中赢了 249 场,那么估计的获胜概率是 249/500 = 0.498。  8二十一点是用纸牌玩的赌场游戏。游戏的目标是拿到尽可能接近 21 点的牌,但不 超过。所有花牌为 10 分,A 为 1 或 11,所有其他牌均按值计分。  该游戏是针对发牌者进行的。玩家尝试比发牌者更接近 21 点(不超过) 。如果发牌者 爆牌(超过 21) ,玩家自动获胜(只要玩家尚未爆牌) 。发牌者必须始终根据固定的规则取 牌。发牌者至少发牌直到自己达到 17 点以上。如果发牌者的牌中包含一个 A,那么如果总 和在 1721 之间时(含 21) ,

28、它将被计为 11。否则,A 被计为 1。  编写一个模拟多局二十一点的程序,并估计发牌者爆牌的可能性。提示:将牌的副数 当成无限的(赌场使用包含许多副牌的“发牌盒” ) 。你不需要记录手上的卡,只要记录到 目前为止的总和(将 A 计为 1)和一个 bool 变量 hasAce,表明是否包含一个 A。包含 A 的 一手牌应该在总和上加 10 点,如果这样做会得到导致停止的总和(1721 之间) 。  9二十一点的发牌者总是从一张牌开始。对于每个可能的起始值,选手如果知道发牌 者的爆牌概率(参见上一个问题)将是有用的。编写一个模拟程序,针对每种可能的起始 值(A10)玩多局二十

29、一点,并估计发牌者针对每种起始值爆牌的可能性。  10蒙特卡罗技术可用于估计 pi 的值。假设你有一个圆形的飞镖板,刚好放在一个方 形盒子里面。如果你随机投掷飞镖,那么击中飞镖板与击中盒子(不在板上的角落)的比 例将由飞镖靶板和盒子的相对面积来确定。如果 n 是随机投掷的飞镖的总数(落在盒子的 范围内) ,而 h 是击中飞镖板的数字,很容易推出  h4n 编写一个程序,接受“飞镖数”作为输入,然后进行模拟以估计 。 (提示:你可以使用 2 * random()-1在(0,0)为中心的 2 2 的正方形中生成随机点的 x 和 y 坐标。如果 x2 +  y2 1,则

30、该点位于内切圆中。 )  11编写一个模拟程序,估计一把掷五个六面骰子,点数都相同的概率。  12 “随机行走”是一种特殊的概率模拟,它模拟某些统计系统,如布朗运动的分子。 你可以将抛硬币想象为一维随机行走。假设你站在一个非常长的直线人行道上,在你前后 伸展。抛一枚硬币,如果出现正面,你向前迈出一步,反面就向后退一步。 假设你随机走 n 步。平均来说,最后距离起点多少步?编写一个程序来帮助你研究这 个问题。 13假设你正在城市街道的街区随机行走(见上一个问题) 。在每个“步骤”中,你选 择向前,向后,向左或向右走一个街区(随机) 。在 n 步后,你预期离出发点多远?编写一

31、个程序来帮助解决这个问题。9.7  练习 195 14编写一个图形程序来跟踪二维随机行走(见前两个问题) 。在这个模拟中,你应该 允许在任何方向上走一步。你可以生成一个随机方向作为与 x 轴的夹角。 angle = random() * 2 * math.pi 新的 x 和 y 位置由以下公式给出: x = x + cos(angle)  y = y + sin(angle) 程序应该以步数作为输入。在 100 100 网格的中心启动你的行走者,并绘制一条记录 行走的线条。 15 (高级)这是一个难题,可以通过一些高难度的分析几何(微积分)或(相对)简 单的模拟来解决。 假

32、设你位于立方体的中心。如果你可以在每个方向看看周围,立方体的每个墙壁将占 据你的视野。假设你走向一个墙壁,这样你现在就在它和立方体的中心之间。你的视野现 在有多少部分被最近的墙壁占据?(提示:使用蒙特卡罗模拟,在随机的方向上重复“看” , 并计算它看到墙壁的次数。 )第第 1 10 0 章章    定定    义义    类类  学习目标 领会定义新类如何能为复杂程序提供结构。  能够阅读并编写 Python 类定义。  理解封装的概念,以及它如何有助于构建模块化的、可维护的程序。 能够编写包含简单类定义

33、的程序。 能够编写包含创新(程序员设计的)控件的交互式图形程序。10.1  对象的快速复习 在前三章中,我们已经掌握了一些技术,让程序的“计算”结构化。在接下来的几章 中,我们来看一些技术,让程序使用的“数据”结构化。你知道,对象是管理复杂数据的 重要工具。到目前为止,我们的程序已经利用了诸如 Circle 等预定义的类创建的对象。在本 章中,你将学习如何编写自己的新类。 回忆一下,在第 4 章中,我将“对象”定义为一种主动的数据类型,它知道一些事情, 并可以做一些事情。更准确地说,一个对象包括: (1)一组相关的信息。 (2)操作这些信息的一组操作。 信息存储在对象中的“实例变量”

34、中。这些操作,称为“方法” ,是“存在”于对象内 的函数。实例变量和方法一起被称为对象的“属性” 。 举一个现在很熟悉的例子。一个 Circle 对象有一些实例变量,如 center,它记住圆的 中心点,radius,它保存圆的半径。Circle 的方法需要这些数据来执行动作。draw 方法检 查 center 和 radius,以确定窗口中的哪些像素应该着色。move 方法改变中心的值,以反映 圆的新位置。 记住,每个对象都被认为是一个类的一个实例。对象的类决定对象将具有什么属性。 基本上,一个类描述了它的实例知道什么和做什么。调用构造方法,将从类创建新对象。 你可以将类本身视为创建新实例的

35、工厂。 请考虑创建一个新的 Circle 对象: myCircle = Circle(Point(0,0), 20)  Circle 是类的名称,用于调用构造方法。这一行创建一个新的 Circle 实例,并将引用保10.2  示例程序:炮弹 197 存在变量 myCircle 中。 构造方法的参数用于初始化 myCircle 内部的一些实例变量 (即 center 和 radius) 。实例被创建后,就通过调用它的方法来操作它: myCircle.draw(win)   myCircle.move(dx, dy)  .  10.2  

36、示例程序:炮弹 开始详细讨论如何编写自己的类之前,我们先简单地看看新类多么有用。10.2.1  程序规格说明 假设我们希望编写一个模拟炮弹(或任何其他抛体,如子弹、棒球或铅球)的程序。 我们特别感兴趣的是,确定在各种发射角度和初始速度下,炮弹将飞多远。程序的输入是 炮弹的发射角(以度为单位) 、初始速度(以米每秒为单位)和初始高度(以米为单位) 。 输出是抛体在撞击地面前飞行的距离(以米为单位) 。 如果忽略风阻的影响,并假设炮弹靠近地球表面(即我们不试图将它发射到轨道上) , 这是一个相对简单的经典物理问题。地球表面附近的重力加速度约为 9.8 米/秒2。这意味着如果一个物体以每秒

37、 20 米的速度向上抛出,经过一秒钟之后,它的向上速度将会减慢 到 20 9.8 = 10.2 米/秒。再过一秒,速度将只有 0.4 米/秒,之后不久就会开始回落。  对于知道一点微积分的人来说,不难推导出一个公式,给出炮弹在飞行中任何给定时 刻的位置。然而,我们的程序不用微积分的方法,而是用模拟来跟踪炮弹每个时刻的位置。 利用一点简单的三角学,以及一个明显的关系,即物体在给定时间内飞行的距离等于其速 率乘以时间(d = rt) ,我们可以用算法解决这个问题。 10.2.2  设计程序 我们先设计一个算法。鉴于问题陈述,很明显,我们需要考虑炮弹的两个维度:一是高 度,这样我

38、们知道什么时候碰到地面。二是距离,记录它飞多远。我们可以将炮弹的位置视 为二维图中的点(x,y) ,其中 y 的值给出了地面之上的高度,x 的值给出了与起点的距离。  我们的模拟必须更新炮弹的位置来说明它的飞行。假设炮弹从位置(0,0)开始,我们 希望定时检查它的位置,比如说,每隔十分之一秒。在那段时间内,它将向上移动一些距 离(正 y)并向前移动一些距离(正 x) 。每个维度的精确距离由它在该方向上的速度决定。   分离速度的 x 和 y 分量让问题更容易。由于忽略风阻,所以 x 速度在整个飞行中保持 不变。然而,由于重力的影响,y 速度随时间而变化。事实上,y 速度开始

39、为正,然后随着 炮弹开始下降,会变为负值。 根据这样的分析,模拟要做什么就清楚了。下面是粗略的大纲:输入模拟参数:角度,速度,高度,间隔  计算炮弹的初始位置:xpos,ypos  计算炮弹的初始速度:xvel,yvel 198 第 10 章  定义类while 炮弹仍在飞行:  将xpos,ypos和yvel的值更新为飞行输出中距离作为xpos的距离 让我们用逐步求精的方法,将它变成一个程序。 算法的第一行很简单。我们只需要合适的输入语句序列。下面是开始:def main(): angle = float(input(“Enter the launch

40、 angle (in degrees): “)  vel = float(input(“Enter the initial velocity (in meters/sec): “) h0 = float(input(“Enter the initial height (in meters): “)  time = float(input(   “Enter the time interval between position calculations: “) 计算炮弹的初始位置也很简单。它将从距离 0 和高度 h0 开始。我们只需要两句赋值语句:  xp

41、os = 0.0 ypos = h0 接下来,需要计算初始速度的 x 和 y 分量。我们需要一点高中三角学知识。 (看到吗,这告 诉你它们会有使用的那一天。 )如果我们认为初始速度由 y 方向的一些分量和x 方向的一些分量组成, 那么这三个量 (速 度,x 速度和 y 速度)形成一个直角三角形。图 10.1 说明了 这种情况。如果我们知道速度的大小和发射角度(标记为, 因为希腊字母经常用作角度的度量) ,我们可以通过公式 xvel =速度*cos(),很容易地计算 xvel 的大小。类似的公式(使用 sin())计算出 yvel。 即使你不完全理解三角学也没关系, 重要的是我们可以将这些公式转

42、换成 Python 代码。 还有一个微妙的问题需要考虑。我们的输入角度以度为单位,Python 数学库采用弧度度量。应用公式之前,我们必须转换角度。圆周有 2弧度(360 度) ,所以*180角度。这是常见的转换,所以 math 库提供了一个方便的函数,称为 radians,用于执行这种计 算。这三个公式给出了计算初始速度的代码:theta = math.radians(angle)  xvel = velocity * math.cos(theta) yvel = velocity * math.sin(theta) 接下来是程序的主循环。我们希望不断更新炮弹的位置和速度,直到它到

43、达地面。我 们可以通过检查 ypos 的值来做到这一点: while ypos >= 0.0: 我使用>=作为关系,这样可以从炮弹在地面上开始(=0) ,仍然让循环执行。一旦 ypos 的值下降到 0 以下,循环就会退出,表明炮弹已经略微嵌入地面了。  现在我们来到模拟的关键。每次通过循环,我们希望更新炮弹的状态,让它在飞行中 移动 time 秒。我们先从水平方向考虑运动。由于规格说明指出可以忽略风阻,所以炮弹的 水平速度将保持不变,由 xvel 的值给出。  作为一个具体的例子,假设炮弹以 30 米/秒的速度飞行,目前距离发射点 50 米。下 1 秒钟,它将再

44、次前进 30 米,距离发射点 80 米。如果间隔时间只有 0.1 秒(而不是 1 秒) , 那么炮弹只飞行 0.1 * 30= 3 米,距离为 53 米。你可以看到,飞行的距离总是由 time * xvel 给出。要更新水平位置,我们只需要一个语句:图 10.1  计算速度的 x 和 y 分量10.2  示例程序:炮弹 199 xpos = xpos + time * xvel 垂直分量的情况稍微复杂一些,因为重力会导致 y 速度分量随时间而变化。每秒必须 减少 9.8 米/秒,即重力加速度。在 0.1 秒内,速度将减少 0.1 * 9.8 = 0.98 米/秒。间隔结束时

45、 的新速度计算为yvel1 = yvel - time * 9.8 为了计算在这个时间间隔内炮弹的飞行的距离,我们需要知道它的“平均”垂直速度。 由于重力加速度是恒定的,所以平均速度就是开始和结束速度的平均值(yvel + yvel1)/2.0。 平均速度乘以间隔时间,给出了高度的变化。 下面是完成的循环:while ypos >= 0.0: xpos = xpos + time * xvel yvel1 = yvel - time * 9.8 ypos = ypos + time * (yvel + yvel1)/2.0 yvel = yvel1  注意,时间间隔结束时的速度

46、先存储在临时变量 yvel1 中。这是为了保持初始值,从而 可以用两个值计算平均速度。最后,在循环结束时,将 yvel 赋予新值。这表示在间隔结束 时炮弹的正确垂直速度。 程序的最后一步就是输出飞行距离。添加此步骤得到了完整的程序:# cball1.py   from math import sin, cos, radians def main(): angle = float(input(“Enter the launch angle (in degrees): “)  vel = float(input(“Enter the initial velocity (in m

47、eters/sec): “) h0 = float(input(“Enter the initial height (in meters): “)  time = float(input(   “Enter the time interval between position calculations: “) # convert angle to radians  theta = radians(angle)  # set the initial position and velocities in x and y directions xpos = 0

48、  ypos = h0  xvel = vel * cos(theta)  yvel = vel * sin(theta)  # loop until the ball hits the ground while ypos >= 0.0:   # calculate position and velocity in time seconds  xpos = xpos + time * xvel   yvel1 = yvel - time * 9.8   ypos = ypos + time * (yvel +

49、 yvel1)/2.0   yvel = yvel1  print(“nDistance traveled: 0:0.1f meters.“.format(xpos) 10.2.3  程序模块化 在设计讨论时你可能注意到,我采用了逐步求精的方法(自顶向下的设计)来开发该200 第 10 章  定义类程序,但是我没有将程序分成单独的函数。我们将以两种不同的方式对程序进行模块化。 首先,我们将使用函数(即向顶向下设计) 。 最后的程序虽然不是太长,但相对于它的长度来说却相当复杂。复杂的一个原因是它 使用了 10 个变量,这对读者来说太多,难以记住。让我们尝试

50、将程序划分成一些函数,看 是否有帮助。下面是使用辅助函数的主算法版本:def main(): angle, vel, h0, time = getInputs()  xpos, ypos = 0, h0  xvel, yvel = getXYComponents(vel, angle) while ypos >= 0:   xpos, ypos, yvel = updateCannonBall(time, xpos, ypos, xvel, yvel) print(“nDistance traveled: 0:0.1f meters.“.format(xpo

51、s)  根据这些函数的名称和原来的程序代码,这些函数做什么应该是明显的。你可能需要 几分钟的时间来编写三个辅助函数。 第二个版本的主算法肯定比较简洁。变量的数量已经减少到 8 个,因为 theta 和 yvel1 已经从主算法中消除了。你看到它们去哪儿了吗?只有在 getXYComponents 内部才需要 theta 的值。同样,yvel1 现在是 updateCannonBall 的局部变量。能隐藏一些中间变量是关注 点分离的主要好处,这是自顶向下设计提供的。 即使这个版本似乎也过于复杂。特别看看循环。记录炮弹的状态需要四条信息,其中 三条必须随时改变。需要所有四个变量以及时间的

52、值来计算三个变量的新值。这导致一个 丑陋的函数调用,有五个参数和三个返回值。参数很多通常表明程序可能会有更好的组织 方式。我们来试试另一种方法。 原来的问题规格说明本身就表明,有更好的方法来查看程序中的变量。有一个真实世 界的炮弹对象,但在当前的程序中,描述它需要 xpos、ypos、xvel 和 yvel 四个信息。假设 我们有一个 Projectile 类,能“理解”炮弹这类物体的物理特性。利用这样的类,我们可以 用单个变量来创建和更新合适的对象,表示主算法。通过这种“基于对象”的方法,我们 可以这样编写主程序:def main(): angle, vel, h0, time = getI

53、nputs() cball = Projectile(angle, vel, h0) while cball.getY() >= 0:   cball.update(time)  print(“nDistance traveled: 0:0.1f meters.“.format(cball.getX() 显然,这是比较简单而直接表达的算法。angle、vel 和 h0 的初始值作为参数,创建了 一个 Projectile,名为 cball。每次通过循环时,都会要求 cball 更新其状态以记录时间。我们 可以随时用它的 getX 和 getY 方法来获取 cball 的位置。为了让它工作,我们只需要定义一 个合适的 Projectile 类,让它实现 update、getX 和 getY 方法。 10.3  定义新类 在设计 Projectile 类之前,让我们来看一个更简单的例子,了解基本的想法。 10.3  定义新类 201 10.3.1  示例:多面骰子 普通的骰子(die,dice 的单数)是一个

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 应用文书 > 培训材料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com