首页 卡雷尔机器人学java

卡雷尔机器人学java

举报
开通vip

卡雷尔机器人学java 翻译—伤寒明理论 包含全部章节 第一章: 机器人卡雷尔简介 在二十世纪七十年代,一位名字叫 Rich Pattis 的斯坦福研究生觉得,在编程基础的教学中,如果 学生可以在某种简单的环境中,摆脱大多数编程语言复杂的特性,学习基本的编程思想,可以取 得更好的效果。麻省理工 Seymour Papert’s LOGO 计划的成功,启发了灵感,Rich 设计了一个入 门编程环境,(这个编程环境)让学生教一个机器人来解决简单的问题。这个机器人名字叫卡雷尔。 因为捷克剧作家 KarelCapek 在 1923 年公...

卡雷尔机器人学java
翻译—伤寒明理论 包含全部章节 第一章: 机器人卡雷尔简介 在二十世纪七十年代,一位名字叫 Rich Pattis 的斯坦福研究生觉得,在编程基础的教学中,如果 学生可以在某种简单的环境中,摆脱大多数编程语言复杂的特性,学习基本的编程思想,可以取 得更好的效果。麻省理工 Seymour Papert’s LOGO 计划的成功,启发了灵感,Rich 设计了一个入 门编程环境,(这个编程环境)让学生教一个机器人来解决简单的问题。这个机器人名字叫卡雷尔。 因为捷克剧作家 KarelCapek 在 1923 年公演了 R.U.R (Rossum’s Universal Robots)后,为英语带来了 机器人这个英语单词--Robot 。 机器人卡雷尔相当成功。 卡雷尔被用于全国的计算机科学入门课程,到了 Rich 的教科书畅销超 过 10 万份的地步。许多学习 CS106A 的学生,通过设计卡雷尔的行为,学会了如何让程序工作。 在 20 世纪 90 年代中期,我们曾经使用的机器人卡雷尔模拟器停止工作了。但是,我们很快就得 到了一个 Thetis 编译的卡雷尔升级版供那时使用。但是,一年以前,CS106A 课程转向到 Java, 卡雷尔再次从课堂上消失了。虽然在过去的三个季度,由于卡雷尔的离去产生的空白,已经完全 被 Nick Parlante 的 Binky world 填补了。但现在是带卡雷尔回来的时候了。新完工的卡雷尔设计 得完全兼容 Java 和 Eclipse 编程环境,这就意味着,你将在这门课程的开始,就可以练习使用 Eclipse 的编辑器和调试器。 卡雷尔是啥? 卡雷尔是一个生活在非常简单的世界中的非常简单的机器人。在它的世界中,你可以通过给卡雷 尔一组命令,直接让卡雷尔执行某些任务。指定这些命令的过程称为编程。最初,卡雷尔只明白 极少数预定义的命令,但编程(学习)过程的一个重要内容,就是教卡雷尔可以扩展它的能力的 新命令。 当你谋划让卡雷尔执行某项任务的时候,你必需用非常精确的方式写出这些必需的命令,以便这 个机器人能够正确的理解你交待它做的事情。(另外)特别(注意的)是,你写的程序必须遵守 语法规则,它规定了什么样的命令和语言形式是合法的。二者合在一起,预定义的命令和语法规 则(一起)定义了卡雷尔编程语言。卡雷尔编程语言被设计的尽可能类似于 Java 语言,这样便于 顺利过渡到(Java)这门你将时刻使用的语言上。卡雷尔程序具有和 Java 程序相同的结构,也涉 及到相同的基本元素。最关键的区别是,卡雷尔的编程语言非常的小,从这个意义上讲,它只具 有非常少的命令和规则。它非常容易,例如,教授卡雷尔语言只需要几个小时,这也正是我们在 CS106A 中做的。在(课程)结束的时候,你将知道卡雷尔能做的一切事情,以及如何在一个程 序中实现它。这些细节是容易掌握的。即便如此,你会发现,(需要)解决的问题可能是极其具 挑战性的。解决问题是编程的本质;在这一学习过程中,对规则的关注是次要的。 在复杂的语言里,如 Java,有许多的细节,这些细节往往成为学习的课程的重点。当这种情况发 生时,对解决问题的更关键的东西,往往会在得到一片混乱中失去。通过从卡雷尔入手开始学习, 你可以在一开始的时候,就把精力集中在解决问题上面。而且,卡雷尔的学习鼓励想象力和创造 力,在学习过程中,你会收获不少乐趣。(这段话的意思是,要把精力集中在比语法细节更重要 的--解决问题的能力上。译者注) 卡雷尔的世界 卡雷尔的世界被定义为水平的街(东西方向),垂直的道(南北方向)。街和道的交点被称为街角。 卡雷尔只能定位在街角上,而且只能面对的四个标准罗盘方向(北,南,东,西)。一个简单的 卡雷尔世界显示如下。卡雷尔目前位于第一大街和第一大道相交的街角,面朝向东。 在这个例子中,卡雷尔世界的几个其它组件也可以看到。卡雷尔前面的物体是个蜂鸣器,在 Rich Pattis’s 的书中描述,蜂鸣器是一个发出轻微的哔哔声的塑料筒。只有当卡雷尔和蜂鸣器位于同一 个街角上的时候,卡雷尔才能感知这个蜂鸣器。图中的实线是墙壁。墙是卡雷尔世界的屏障。卡 雷尔不能穿过墙壁,而只能在墙的周边行走。卡雷尔的世界总是被作为边界的墙包围起来,但是, 随着卡雷尔需要解决不同的具体问题,卡雷尔的世界也有不同的尺寸。 卡雷尔能干点啥? 当卡雷尔出厂的时候,它只能响应非常小的命令集: move() 要求 对教师党员的评价套管和固井爆破片与爆破装置仓库管理基本要求三甲医院都需要复审吗 卡雷尔向前推进一步。当一堵墙挡在卡雷尔面前的时候,卡雷尔不能响应 move() 这个命令。 turnLeft() 要求卡雷尔向左转 90 度(逆时针转动)。 pickBeeper() 要求卡雷尔捡起街角上的蜂鸣器,把这个蜂鸣器放到它的蜂鸣器收藏包里,这个包 可容纳无限多的蜂鸣器。除非这个蜂鸣器恰好在卡雷尔所在的街角上,卡雷尔不能响应这个 pickBeeper()命令。 putBeeper() 要求卡雷尔从蜂鸣器收藏包里拿出一个蜂鸣器,放在卡雷尔所在的街角上。除非卡雷 尔的蜂鸣器收藏包里有蜂鸣器,卡雷尔不能响应这个 putBeeper()命令。 出现在这些命令后面的一对空括号,是卡雷尔和 Java 相同语法的一部分,通过这个(一对空括 号),来指定命令的调用。最终的时候,在你写的(Java)程序里,空括号之间会包含一些额外 的内容,但是,这些额外的内容并不是卡雷尔原始世界的一部分。将这些括号之间留空,才是标准 的卡雷尔程序,虽然括号里什么也没有,但你一定要记着把它们写在命令里。 需要特别注意的是,这几个命令对卡雷尔的行为进行了限制。如果卡雷尔试图做些非法的举动, 像穿墙或者捡起一个不存在的蜂鸣器,一个错误就发生了。在这时,卡雷尔会显示一条错误的信 息,同时拒绝执行剩余的命令。 卡雷尔的这些命令,不能自己自动执行。在卡雷尔可以执行这些命令之前,你需要先把它们写在 一个卡雷尔程序里。在第二章,你将有机会看到一些简单的卡雷尔程序。但是,在运行这些程序 之前,作一些执行卡雷尔程序语言必要的基本编程哲学的提醒,还是非常有用的。 卡雷尔和面向对象模式 在卡雷尔被引入的二十世纪七十年代,采用的计算机编程方法是过程模式。在很大程度上,过程 编程是把一个大规模的程序问题分解为小的过程,这些更易于管理的,定义了必要操作的单元称 为过程。这种把程序分割成小单元的谋划,是编程风格的重要组成部分,而现代语言例如 Java,强 调了不同的编程模式,叫面向对象模式。在面向对象模式下,程序员的注意力被从关注特定操作 的过程单元中转移出来,侧重到:为被称为对象的单元,抽象建模的行为上。在编程语言中,“对 象”有时和真实世界的物理对象相对应,但更多的时候是抽象的概念。是真实对象的核心特性或代 表(事物)整体的一种抽象。 面向对象模式的主要优势之一是,它鼓励程序员认识到一个对象的状态和其行为的基本关系。一 个对象的状态,涉及到一组和该对象相关,并可能随时间而改变的特性。一个对象可以被它在空 间的位置,它的颜色,它的名字,其它一些主要特性来特别指定出来。一个对象的行为是指它在 它的世界中响应事件或响应来自其它对象的命令的方法。在面向对象编程的语言中,触发指定对 象的行为的通用词叫消息--message(虽然从卡雷尔的上下文上看,命令--command 这个词可能更 让人明白点)。对一个消息的响应,通常需要改变一个对象的状态。例如,如果定义一个对象状 态的特性的之一是它的颜色,它就可以通过响应 setColor(BLUE) 这条消息把它的颜色改变成蓝色。 在许多方面,卡雷尔代表了讲述“面向对象方法”的理想环境。虽然实际上没有人建立一个机器来 执行卡雷尔,但却非常容易把卡雷尔想象为一个真实世界的对象。卡雷尔毕竟是一个机器人,机 器人是真实世界存在的实体。定义卡雷尔状态的特性是它在它的世界中的位置,它面对的方向, 在它蜂鸣器收藏包里的蜂鸣器数目。定义卡雷尔行为的是它响应的 move(), turnLeft(), pickBeeper(), 和 putBeeper()这些命令。move()命令可以改变卡雷尔的位置,turnLeft()命令可以改 变它面朝的方向,剩下的两个命令(指 pickBeeper()和 putBeeper()这两个命令--译者注),会同时 影响到收藏包里的蜂鸣器数量和当前街角上的蜂鸣器数量。 卡雷尔的环境还提供了面向对象编程的核心概念之一--一个有用的框架。无论卡雷尔还是 Java,一 个对象和一个类之间概念的区分是很有必要的。最容易理解这种分别的方法是把类认为是一些对 象(共同)的模式或模板,这些对象都有一个共同的行为和状态属性的集合。在你将看到的下一 章中,在卡雷尔程序里出现的 “karel"这个词,代表一个完整的机器人的类,这个类知道怎么去响 应 move(), turnLeft(), pickBeeper(), 和 putBeeper() 这些命令。但只要你在卡雷尔的世界里有了一个 实际的机器人,那么,这个机器人就是一个对象,这个机器人它代表了卡雷尔这个类的特定实例。 尽管在 CS106A 课程里,你将不会有机会去做这样一个类,但有一个以上的卡雷尔类的实例,运 行在同一个世界里,还是有可能的。即使只有一个单独机器人的时候,在你的脑海里,深刻记得 类和对象是不同的概念,还是很重要的。 实际经验的重要性 编程是一项需要大量边学边干的活动。就像你将在你的计算机学习中不断发现的那样,读懂一些 编程概念和能在一个程序中使用这个概念不是同一回事。在纸面上看起来似乎很清楚的东西,具 体实施起来就变得非常的难。 在编程学习中,你写出程序和让这些程序能在计算机上运行是同样重要的。你会惊讶的发现,这 本书没有包括多少手把手操作卡雷尔如何在你的计算机运行的讨论。遗漏这些部分的原因是,你 运行卡雷尔需要依靠你使用的计算机环境。在苹果机上运行卡雷尔程序和在 Windows下是不一样 的。即使你使用的编程环境对你运行程序的基本细节有很大的影响,但他对常规的(编程)概念 没有任何影响。这本书介绍常规概念;(而)每个平台的相关细节,被包含在分发的课程讲义中。 实际上,这本书省略了些实际的细节,但无意减少这些细节的重要性。即使你要弄明白像卡雷尔 提供的,这么简单的编程环境下,如何开始编程工作,你也必需”弄脏你的手“--开始实际的计算机 操作。这样做是进入编程世界,享受它带来的激情,目前最有效的方式。 第二章 卡雷尔程序设计 在卡雷尔新的面向对象的实现中(这个新的,应该是和七十年代的卡雷尔实现程序相对而言--译 者注),最简单的卡雷尔程序包含一个定义的 karel类,这个类指定了一系列在程序运行的时候, 可以被执行的内建命令。卡雷尔一个非常简单的程序如 Figure 1所示。 Figure 1. Simple Karel example to pick up a single beeper --------------------------------------------------------------------------------------- /* * File: BeeperPickingKarel.java * ----------------------------- * The BeeperPickingKarel class extends the basic Karel class * by defining a "run" method with three commands. These * commands cause Karel to move forward one block, pick up * a beeper, and then move ahead to the next corner. */ import stanford.karel.*; public class BeeperPickingKarel extends Karel { public void run() { move(); pickBeeper(); move(); } } ----------------------------------------------------------------------------------------- Figure 1 中的程序是由几部分组成的。第一部分由下面的几行组成。 /* * File: BeeperPickingKarel.java * ----------------------------- * The BeeperPickingKarel class extends the basic Karel class * by defining a "run" method with three commands. These * commands cause Karel to move forward one block, pick up * a beeper, and then move ahead to the next corner. */ 这些行是一个注释的例子,注释是指适合人类读者的,用来解释程序操作的,简单的文本。在卡 雷尔和 Java 中,注释都是以组合字符 /* 开头,以组合字符 */结尾。在这里,这段注释从第一行 开始,在几行之后结束。对注释中各行文本开头的”*“,不是必需的,但标记星号可以让人类读者看 清注释所占的范围。在一个简单的程序里,广泛的注释似乎是愚蠢的,因为程序的效果是显而易 见的,但是,在设计更大,更复杂的程序时,注释是一种非常有意义的 记录 混凝土 养护记录下载土方回填监理旁站记录免费下载集备记录下载集备记录下载集备记录下载 。 程序的第二部分是这一行: import stanford.karel.*; 这一行要求从 stanford.karel 库中调用所有的定义。这个库里包含了编写卡雷尔程序必要的基本定 义。例如定义了像 move()和 pickBeeper()这样的标准操作。因为你总是需要访问这些操作,所以, 每个你写的卡雷尔程序,在实际的程序语句之前,都要包含这条导入命令。 这个卡雷尔程序的最后部分包含了下面这个类的定义: public class BeeperPickingKarel extends Karel { public void run() { move(); pickBeeper(); move(); } } 多仔细观察一下它的结构,对理解这个定义是非常有帮助的。这个 “BeeperPickingKarel” 类 首先 包含了以 “public class” 开头的一行,在这一行的结尾,用一个左大括号,和这个类定义最后一行 的最后的那个右大括号一起,把中间的部分全部括了进去。声明新类的那一行,被称为(这个 类)定义的头部;两个大括号之间的程序代码,被称为(这个类)的主体。 在编程中,把特定的定义和它的主体分开考虑是非常有用的,在下图这个例子, BeeperPickingKarel 类定义是如下形式:主体部分被一个方块代替,现在你可以把你的想法放进去: 在顶端的头部这一行,在你看到主体里面的内容之前,就会告诉你关于 BeeperPickingKarel 这个 类相当多的信息。"extends"这个词,体现出了这个类头部关键的新东西。("extends"这个词)在 卡雷尔和 Java 中,都用来表明 这个新类是一个现存类的扩展。在 BeeperPickingKarel 类的头部这 一行表明,它是从 stanford.karel 库中导入的标准的 karel 类的一个扩展。 在面向对象编程语言中,通过扩展来定义一个新类(在这里,就是 BeeperPickingKarel)意味着新 类是建立在现有的类(在这个例子里,是 karel 类)提供的功能之上。特别是,在 karel 类之上进 行扩展的事实,保证了新的 BeeperPickingKarel 类 将具有下列属性: 1.任何 BeeperPickingKarel 类的实例也是 karel 类的实例。任何 karel类的实例都被表示成一个机器 人,这个机器人生活在一个有街,道,蜂鸣器和墙的世界,它的状态是它的位置,它面朝的方向, 它蜂鸣器收藏包里的蜂鸣器数目。因为 BeeperPickingKarel 类是 karel 类的扩展,你也就知道了, BeeperPickingKarel类的实例也将是一个机器人,它生活在同样类型世界里,并具有相同的状态属 性。 2.任何 BeeperPickingKarel 类的实例将会自动的响应与任何 karel 类的实例相同的命令,因为 每一 个 karel类的机器人都知道如何响应 move(), turnLeft(), pickBeeper(), 和 putBeeper()这些命令,作为 它的跟随者,BeeperPickingKarel 类的实例也能明白这同一套命令。 在其它世界里,新的 BeeperPickingKarel类自动获得 karel 类派生出的状态属性和行为。这个从它 的父类取得结构和行为的过程被称为继承。 当一个类是由扩展而来的,这个新类被称为原始类的子类。在这个例子中,BeeperPickingKarel 因 此被称为 karel 类的一个子类。相对的,karel类被称为 BeeperPickingKarel类的超级类。 不幸的是,这个称呼可能会引起新程序员的混乱,谁都会直观的认为,一个子类的能力要比它的 超级类小,而事实恰恰相反。一个子类继承了超级类的行为,因此可以响应超级类的整套命令。 但是,一个子类通常会定义一些额外的命令,这些额外的命令超级类不能执行。因此,一个典型 的子类实际上拥有比派生它的原始类更多的功能。这个认识被扩展这个概念表达的更清楚:一个 子类扩展了它的超级类,子类因此获得新的能力。 现在你对什么是类扩展的意思有了一些概念,它使 BeeperPickingKarel 类的本体看起来产生了一 些意义。这个本体包含下面的这些行: public void run() { move(); pickBeeper(); move(); } 这些行代表了一个新动作的定义,其中阐明了响应一个(新动作)命令必要的步骤序列。在 BeeperPickingKarel 类本身的实例里,这个动作的定义由可以被分开考虑的两部分组成:第一行构 成了这个动作的头部,两个括号之间的部分构成了这个动作的本体。如果你现在先忽略本体,这 个动作定义看起来是这样的: 在这个动作头部的前两个单词,“public”和“void”是 Java 语法结构的一部分,在这里你差不多可以 忽视它们。下一个单词指明了这个动作的名字,在这个例子里,这个动作的名字是“run”,定义一个 新动作,意味着这个新的 karel 类的子类能响应以这个动作命名的新命令。卡雷尔类内建可以响应 move(), turnLeft(), pickBeeper(), 和 putBeeper()这些命令;一个 BeeperPickingKarel 类可以响应这套 命令加上这个叫“run”的命令。这个“run”命令在卡雷尔程序中扮演了一个特殊的角色。当你在 Eclipse 环境中打开一个这样的卡雷尔程序,它创造出一个相应的子类的卡雷尔实例,添加了一个 你指定的卡雷尔机器人到卡雷尔的世界,它带着你编写的 “run”命令。这个动作的效果是由“run”这 个动作的本体来定义的。这个本体是一组命令的序列,机器人将按顺序执行。例如,为 BeeperPickingKarel 类 (编写的)"run" 动作的本体是这样的: move(); pickBeeper(); move(); 因此,如果一个实例的状态和第一章给出的例子一样,卡雷尔首先向前移动到包含一个蜂鸣器的 街角,然后捡起这个蜂鸣器,最后向前移动到墙前面的那个街角。状态像下面前和后(两幅)插 图显示的那样: 解决更有趣的问题 在 Figure 1 里定义的 BeeperPickingKarel类做的动作不多。让我们试着让它更有趣一点。设定目标 不是简单的让卡雷尔捡起蜂鸣器,而是要把这个蜂鸣器从它的初始位置,第一大街第二道的交点, 移动到第二大街和第五大道的交点。因此,你的下一个任务是定义一个新的 karel子类,它可以完 成下面插图描绘的任务: 新程序的前三个命令是,向前移动,捡起蜂鸣器,然后再向前移动到边界前,这个和以前的一样。 move(); pickBeeper(); move(); 从这儿开始,机器人左转,开始攀爬边界。这个操作很容易,因为在它的标准命令库中有一个左 转的命令。在卡雷尔到达第一大街和第三大道交汇的街角之后,执行这个命令,会让卡雷尔面向 北方。如果这时执行一个移动命令,它将会向北移动,到达随后的位置: 从这儿开始,你需要做的下一件事情是让卡雷尔向右转,以便它再一次面对东方。虽然这个操作 是和卡雷尔左转一样容易的概念,但这里有一个轻微的问题:卡雷尔的语言包括了一个 turnLeft 命令,但是没有 turnRight 命令。这就好像你买了个便宜的模型玩具,现在发现它缺少一些重要的 功能。 在这一点上,你有了第一个,像一个程序员一样,开始思考的机会。你有了一套命令,但是恰好 没有你需要的命令。你能做什么?你能仅仅使用你现在拥有的能力实现 turnRight 的效果吗?答案 当然是:Yes 。你可以通过三次左转来实现右转的效果。三次左转之后,卡雷尔将面对需要的方 向。从这儿开始,所有你需要在卡雷尔程序里做的就是移动到所需的那个中心点,放下蜂鸣器, 然后移动到最终的位置。一个可以实现整个任务的,完整的 BeeperTotingKarel 类的实现(后面的 这个实现是名词,也就是程序代码的意思--译者注)被显示在 Figure 2 。 Figure 2. Program to carry a beeper to the top of a ledge ----------------------------------------------------------- /* * File: BeeperTotingKarel.java * ---------------------------- * The BeeperTotingKarel class extends the basic Karel class * so that Karel picks up a beeper from 1st Street and then * carries that beeper to the center of a ledge on 2nd Street. */ import stanford.karel.*; public class BeeperTotingKarel extends Karel { public void run() { move(); pickBeeper(); move(); turnLeft(); move(); turnLeft(); turnLeft(); turnLeft(); move(); move(); putBeeper(); move(); } } 定义些新动作 尽管通过在 Figure 2 中的 BeeperTotingKarel 类展示了,仅仅通过卡雷尔内建命令实现 “turnRight” 操作是可能的, 但程序结果不是一个特别清楚的概念。你在这个程序里的设计思路是,当卡雷尔 到达了所需的那个点时,卡雷尔向右转。你不得不使用三次左转命令来做这个(向右转)的事实 让人很恼火。如果你能简单的说 “turnRight”,卡雷尔就能明白这个命令的话,那将简单多了。效果 是程序不但更短了,更容易写了,而且阅读起来明显更容易了。 幸运地是,卡雷尔编程语言使得通过简单的包括一个新动作,来定义新命令成为可能。当你有了 一个可以执行一些有用任务的卡雷尔命令序列,你可以把这个序列定义成可以执行命令序列的新 动作。定义一个新卡雷尔动作的格式,有很多和上面定义"run"的例子相同的地方。这就是一个定 义它自己右转的办法。一个典型的动作定义看起来像这样: private void name() { commands that make up the body of the method } 在这个格式里,“name”代表你给新动作选定的名字。接下来完成这个(动作)定义,所有你要做 的就是提供占两个括号中间几行的一套命令序列。例如,你可以按照下面的方式定义 “turnRight”: private void turnRight() { turnLeft(); turnLeft(); turnLeft(); } 类似的,你可以像这样定义一个新 “turnAround”动作: private void turnAround() { turnLeft(); turnLeft(); } 你可以直接调用新动作的名字,好像这个新动作是卡雷尔的内建动作一样。例如,一旦你定义了 “turnRight”,在 BeeperTotingKarel 类里,你就可以调用一个 “turnRight” 来代替三个 “turnLeft”命令。 一个使用 “turnRight” 执行的,订正过的程序被显示在 Figure 3 。 当然了,在这里,定义 “run” 和定义 “turnRight” 动作之间一个明显的不同被显示在 Figure 3: 对比 被标记为 “private”的“turnRight”,"run"动作被标记为 “public”,这两种设定间的不同是:“public”动作 可以被外部的类调用,而“private”类不可以。“run”动作被标记为“public”,是因为卡雷尔编程环境需 要一个向前走,拿东西的“run”命令。相比之下,“turnRight” 仅仅被出现在这个类里面的其它代码 使用。因此,这个定义是(这个类)私用的。尽可能的保持定义私有化,总体来说是一个好的编 程习惯。在你有机会为大项目工作之前,这么做的理由,你很难体会到。但是,基础的思想是, 这些类应该试着尽可能多的封装信息,这意味着这些类不仅可以聚集在一起工作,而且尽可能的 限制(这些类之间互相的)访问信息。大型程序在它们包含的项目的细节数量上很快变得非常复 杂。如果一个类被设计的很好,它将通过隐藏尽可能多的额外信息来寻求降低系统的复杂性。这 种属性被称为信息隐藏,是面向对象哲学的一块基石。 Figure 3. Revised implementation of BeeperTotingKarel that includes a turnRight method ------------------------------------------------------------------------ /* * File: BeeperTotingKarel.java * ---------------------------- * The BeeperTotingKarel class extends the basic Karel class * so that Karel picks up a beeper from 1st Street and then * carries that beeper to the center of a ledge on 2nd Street. */ import stanford.karel.*; public class BeeperTotingKarel extends Karel { public void run() { move(); pickBeeper(); move(); turnLeft(); move(); turnRight(); move(); move(); putBeeper(); move(); } /** * Turns Karel 90 degrees to the right. */ private void turnRight() { turnLeft(); turnLeft(); turnLeft(); } } ------------------------------------------------------------ 在你学习编程的这一点上,你很难找出关于封装特别有说服力的证据。在每个程序中都定义 “turnRight”和“turnAround”确实有点痛苦,鉴于事实上,他们非常有用,(每次都)编写它们比指出 它们在哪里定义过更容易使用是个谎言(上面这句话的本义是:从一般理解上讲,turnRight 用的 很频繁,不应该定义为私有,在每个类里都编写一遍,而应该定义为 public,编写一次,到处引 用。--译者注)。在 BeeperTotingKarel 类的定义中,把 "turnright"声明成“public”,也不会有太多 帮助。在面向对象语言中,作为指定一个类的行为的方法,是要被封装到这个类里的。这个让 BeeperTotingKarel 类的实例知道如何向右转 90 度的“turnRight”动作,不能被应用在 karel 类和它的 任何其它子类的实例里。 从某种意义上说,你真的想把 “turnRight”和“turnAround”这些不可否认,很有用的命令加入到 karel 类里,这样以来,所有(karel 类)的子类都能够使用。一个战略意义上的问题是,你没有必 要访问 karel 类来做这个所谓必要的改变。karel 类 是 Stanford 库的一部分,(这个库)被这个 CS106A 的所有学生使用。如果你去对这个类做了更改,你可能会破坏别人的程序,你不会得到 其它学生的拥戴。在这以后的某一刻,你真的想加些东西到一个 Java标准类中,你实际上也不能 改变这个类(这里指标准的 Java类,应该不包括 karel类--译者注),因为这些标准类是在 Sun 公 司控制之下的。你能做的,就是定义一个包含你的新特性的新类,作为扩展。因此,如果你想在 几个不同的卡雷尔程序里都使用 “turnRight”和“turnAround”,你可以定义一个包含这些动作定义的 新类,通过扩展这个类来创作你的程序。这种技术的说明在 Figure 4 里,它包含了两个程序文件。 第一个里包含了一个类定义叫 NewImprovedKarel,在 NewImprovedKarel 类里包含的 “turnRight” 和“turnAround”被定义为 “public”动作,这样其它类也可以使用它们。第二个里面, BeeperTotingKarel类是 NewImprovedKarel类的扩展,(BeeperTotingKarel类)它自己从而可以访 问这些动作。 Figure 4. Defining a NewImprovedKarel class that understands turnRight and turnAround --------------------------------------------------------------- /* * File: NewImprovedKarel.java * ------------------------------------------ * The NewImprovedKarel class extends the basic Karel class * so that any subclasses have access to the turnRight and * turnAround methods. It does not define any run method * of its own. */ import stanford.karel.*; public class NewImprovedKarel extends Karel { /** * Turns Karel 90 degrees to the right. */ public void turnRight() { turnLeft(); turnLeft(); turnLeft(); } /** * Turns Karel around 180 degrees. */ public void turnAround() { turnLeft(); turnLeft(); } } /* * File: BeeperTotingKarel.java * ---------------------------- * The BeeperTotingKarel class extends the basic Karel class * so that Karel picks up a beeper from 1st Street and then * carries that beeper to the center of a ledge on 2nd Street. */ import stanford.karel.*; public class BeeperTotingKarel extends NewImprovedKarel { public void run() { move(); pickBeeper(); move(); turnLeft(); move(); turnRight(); move(); move(); putBeeper(); move(); } } stanford.karel 包里不包括这个在这儿出现的 NewImprovedKarel类,但是它包括一个叫 SuperKarel 的类,这个 SuperKarel类包括“turnRight”和“turnAround”动作以及其它几个扩展,它使得你写出更 多精彩的程序成为可能。接下来的例子是 SuperKarel类的扩展,这确保了这些动作都是可用的。 (Superkarel类)其它的扩展将在第六章被描述。 分解 作为说明定义新动作是带来更多能力的一种途径,让卡雷尔做些比,从一个地方移动一个蜂鸣器 到另外一个地方更实际些的东西,是很有用的。围绕在 Palo Alto 周围的道路经常好像需要修理, 在它的抽象世界里,如果卡雷尔能够填坑,应该是比较有趣的。例如,设想卡雷尔正站在左侧插 图显示的路上,左侧公路上一个街角有个坑,卡雷尔的工作是用蜂鸣器把这个洞填上,继续推进 到下一个街角。右侧的插图描绘了这个程序执行以后,这个世界看起来的样子。 如果你受到那四个预定义命令的限制,解决这个问题的 “run ” 的代码看起来像这样: public void run() { move(); turnLeft(); turnLeft(); turnLeft(); move(); putBeeper(); turnLeft(); turnLeft(); move(); turnLeft(); turnLeft(); turnLeft(); move(); } 但是,你可以使用带有 “turnAround”和“turnRight”动作的 SuperKarel 扩展类使主程序读起来更容易。 这个版本的程序显示在 Figure 5 里。 Figure 5. Karel program to fill a single pothole -------------------------------------------------------------------------- /* * File: PotholeFillingKarel.java * ------------------------------ * The PotholeFillingKarel class puts a beeper into a pothole * on 2nd Avenue. This version of the program uses no * decomposition other than turnRight and turnAround, * which are inherited from SuperKarel. */ import stanford.karel.*; public class PotholeFillingKarel extends SuperKarel { public void run() { move(); turnRight(); move(); putBeeper(); turnAround(); move(); turnRight(); move(); } } 定义一个“右转”动作的初始动机是:老是重复三个左转命令来实现一个右转是累赘的。定义一个 新动作有另外一个超越了 “允许你在特定任务里避免每次都重复相同命令序列”这个事情更重要的 目的。这个定义新动作的能力解锁了编程过程中最重要的战略思想,那就是把一个大的问题,分 解成一些更容易解决的小一些的部分。这个把一个程序分成一些小部分的过程被称为分解,一个 大问题的这些小的组成部分被称为子问题。 作为一个例子,这个在路上填坑的问题可以被分解为下列子问题: 1.移动到坑的上方 2.丢下一个蜂鸣器填这个坑儿 3.移动到下一个街角 如果你用这种方式思考这个问题,你可以使用动作定义来创造一个“程序”,这个“程序”从概念上, 反映了你的真实程序的结构。这个 “run”动作看起来像这样: public void run() { move(); fillPothole(); move(); } 对应的大纲立刻非常清楚了,只要你让卡雷尔明白你的意思是填坑,所有事情将变得很棒。通过 定义些动作的能力,实施填坑是非常简单的。所有你要做的是定义一个填坑的动作,这个动作的 主体包含着你上面已经写过的,做这个工作的那些命令,像这个: private void fillPothole() { turnRight(); move(); putBeeper(); turnAround(); move(); turnRight(); } 完整的程序显示在 Figure 6。 Figure 6. Program to fill a single pothole using a fillPothole method for decomposition ----------------------------------------------------------------------------------------------------------- /* * File: PotholeFillingKarel.java * ------------------------------ * The PotholeFillingKarel class puts a beeper into a pothole * on 2nd Avenue. This version of the program decomposes * the problem so that it makes use of a fillPothole method. */ import stanford.karel.*; public class PotholeFillingKarel extends SuperKarel { public void run() { move(); fillPothole(); move(); } /** * Fills the pothole beneath Karel's current position by * placing a beeper on that corner. For this method to * work correctly, Karel must be facing east immediately * above the pothole. When execution is complete, Karel * will have returned to the same square and will again * be facing east. */ private void fillPothole() { turnRight(); move(); putBeeper(); turnAround(); move(); turnRight(); } } 选择正确的分解 但是,这里也有其它你可能试图使用的分解策略。例如,你可以像下面这样写程序: public void run() { approachAndFillPothole(); move(); } 这里的 approachAndFillPothole 动作也简单: private void approachAndFillPothole() { move(); turnRight(); move(); putBeeper(); turnAround(); move(); turnRight(); } 作为另外一种选择,你也可以像这样写程序: public void run() { move(); turnRight(); move(); fillPotholeYouAreStandingIn(); turnAround(); move(); turnRight(); move(); } 这里的 fillPotholeYouAreStandingIn 类的本体包含了单独的一条 “putBeeper”命令。这些程序每一个 都代表了一个可能的分解。每个程序都能正确的解决问题。给出这个程序工作的所有三种版本, 哪一种分解的选择比其它的好? 一般来说,决定如何分解一个程序不容易。选择一个适当的分解,将变成编程中比较困难的一个 方面。但是,你能在一定程度上依赖以下原则: 1.每一个子问题应该执行一个在概念上是很简单的任务。一个子问题的解决 方案 气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载 也许需要许多条 命令,也许在内部操作条件方面相当复杂。即便如此,它最终应该完成一些在概念上容易描述的 任务。如果你给出的动作名字能成功的概括出一个合理的任务,是一个好迹象。如果你可以通过 一个简单的描述性名称,准确的定义(这个动作)它的作用,你可能已经选择了一个很好的分解 方案。另一方面,如果你最终使用了像 approachAndFillPothole 这样的复杂名称,这个分解方案不 像有前途。 2.每个子问题应该执行尽可能具有通用性的任务,这样,这个(子问题)就可以在几个不同的场 景下调用。如果一个分解方案的结果只能在一个特定的场景中使用,而手上的另一个在几个不同 的相关场景中都能工作的同样好,你应该选择更通用的那个。 第三章 卡雷尔里的控制语句 定义新动作的技巧,虽然实际上并没有让卡雷尔解决任何新问题,但一样有用。因为每个动作的 名字不过是一组指定命令的速记, 在一个单独的主程序中,总是可能把调用来完成相同任务的, 一系列动作扩展成一个程序,尽管这样(扩展出)的程序很可能是很长而且难以阅读的。这些命 令,无论是写成了一个单一的程序或被分解写成一组方法,,仍然会不依赖于卡雷尔的世界的状 态,按一个固定的顺序执行。在你解决更有趣的问题之前,你需要发现如何写出不再按照一步步 的操作顺序,而是严格的线性的程序。特别是,你需要学习一些卡雷尔编程语言的新功能,这些 功能使得卡雷尔可以探测它的世界,(根据探测结果)改变它的执行模式成为可能。 (在程序代码中--译者注)那些可以影响一个程序里执行命令次序的语句被称为控制语句。控制 语句一般分为以下两类: 1.条件语句。条件语句是指在程序里这样一些语句,只有当特定的条件成立了,才会被执行。在 卡雷尔编程里,你可以使用 “if”来指定条件语句。 2.迭代语句。迭代语句是指在一个程序里,某些语句需要被反复执行,程序员称这个为“循环”。卡 雷尔支持两种不同的迭代语句。“for”语句用于当你想按预定的次数重复执行一组命令的时候, “while”语句用于当你想在某些条件满足时,重复执行一组命令的时候 这一章介绍了在每一种语句类型具体需求所对应的,卡雷尔环境问题上,这些控制语句的每一种 形式。 条件语句 为了明白条件语句可以派上用场的情况,让我们回到在第二章末尾提出的 “填坑” 程序。在 “fillPothole”动作之前,有几个卡雷尔想检查的条件。例如,卡雷尔想检查一下,看看是否有其它 人员已经把这个坑儿填了,这就意味着,那个街角上已经有一个蜂鸣器了。如果是这样的,卡雷 尔就不需要放第二个蜂鸣器了。为了在程序里表达在这种情况下的这种检查,你需要使用“if”条件 语句,它通常以下面的形式出现: if (条件检测) { 只有当条件满足时才会执行的语句 } 显示在这个模式第一行里的“条件检测”,必需指代一种卡雷尔可以在它的环境里执行的一种检测。 这种条件检测返回的结果是“真”或“假”。如果这个测试结果是“真”,卡雷尔执行括在大括号里的语 句;如果测试结果是“假”,卡雷尔什么也不做。 卡雷尔可以执行的这些检测被列在表 1里。注意每个测试格式里表括一对空括号,在卡雷尔的编 程语言中,这个被用来作为一个语法标记,表明这是个正在使用的测试。还要注意,列表中的每 个状态检测都有一个对应的反面。例如,你可以使用 “fro
本文档为【卡雷尔机器人学java】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_205528
暂无简介~
格式:pdf
大小:721KB
软件:PDF阅读器
页数:40
分类:理学
上传时间:2011-09-28
浏览量:108