卡雷尔JAVA的学习的机器人

来源:百度文库 编辑:神马文学网 时间:2024/04/28 16:40:07
KAREL THE ROBOT 卡雷尔的机器人
LEARNS JAVA 学习JAVA的
Eric Roberts 埃里克罗伯茨
Department of Computer Science 计算机科学系
Stanford University 斯坦福大学
September 2005 2005年9月
Chapter 1 第1章
Introducing Karel the Robot 卡雷尔的机器人介绍
In the 1970s, a Stanford graduate student named Rich Pattis decided that it would be 在20世纪70年代,斯坦福大学的一名研究生丰富Pattis命名决定,这将是
easier to teach the fundamentals of programming if students could somehow learn the 容易教编程的基础,如果学生能在某种程度上学习
basic ideas in a simple environment free from the complexities that characterize most 在简单的环境基本思路,从一个最自由的复杂性的特点
programming languages. 编程语言。 Drawing inspiration from the success of Seymour Papert’s 绘画的灵感来自帕尔特成功西摩
LOGO project at MIT, Rich designed an introductory programming environment in 麻省理工学院的项目,标志,设计了一个丰富的编程环境介绍
which students teach a robot to solve simple problems. 让学生教机器人解决简单的问题。 That robot was named Karel, 该机器人被命名为 卡雷尔,
after the Czech playwright Karel Capek, whose 1923 play RUR ( Rossum's Universal 捷克作家卡雷尔后恰佩克,其1923年播放 卢布(Rossum的通用
Robots ) gave the word robot to the English language. 机器人 )一词给机器人的英语。
Karel the Robot was quite a success. 卡雷尔的机器人是相当成功。 Karel was used in introductory computer science 卡雷尔是用于计算机科学入门
courses all across the country, to the point that Rich's textbook sold well over 100,000 全国各地的所有课程,到如此地步,丰富的教科书畅销超过10万
copies. 副本。 Many generations of CS106A students learned how programming works by CS106A很多学生学会了如何编程代作品
putting Karel through its paces. 卡雷尔将通过其步伐。 But nothing lasts forever. 但是没有什么是永远的。 In the middle of the 1990s, the 在中间20世纪90年代,
simulator we had been using for Karel the Robot stopped working. 我们一直机器人模拟器使用卡雷尔的停止工作。 We were, however, 我们,但是,
soon able to get a version of Karel up and running in the Thetis interpreter we were using 很快就能得到一个翻译的版本和运行的卡雷尔在我们使用的西蒂斯
at the time. 在时间。 But then, a year ago, CS106A switched to Java, and Karel again vanished 但是,一年前,CS106A切换到Java和卡雷尔再次消失
from the scene. 现场。 For the last three quarters, the hole in the curriculum left by Karel’s 在过去的三个季度中,在左洞课程由Karel的
departure has been competently filled by Nick Parlante's Binky world, but it seems about 出发称职填补了由尼克Parlante的宾基世界,但它似乎是
time to bring Karel back. 时间,使卡雷尔回来。 The new implementation of Karel is designed to be compatible 新实施的卡雷尔被设计为兼容
with both Java and the Eclipse programming environment, which means that you'll get to 与Java和Eclipse的编程环境,这意味着你会得到
practice using the Eclipse editor and debugger from the very beginning of the course. 练习使用Eclipse编辑器和调试过程中从一开始。
What is Karel? 卡雷尔是什么?
Karel is a very simple robot living in a very simple world. 卡雷尔是一个非常简单的机器人世界中生活在一个非常简单的。 By giving Karel a set of 通过给卡雷尔一套
commands, you can direct it to perform certain tasks within its world. 命令,你可以直接履行其在世界的某些任务。 The process of 该过程
specifying those commands is called programming . 指定的 编程 命令被调用 。 Initially, Karel understands only a最初,只有理解卡雷尔
very small number of predefined commands, but an important part of the programming 极少数的预定义的命令,而是一个重要组成部分的编程
process is teaching Karel new commands that extend its capabilities. 卡雷尔是教学过程中的新命令,它的功能扩展。
When you program Karel to perform a task, you must write out the necessary 当你计划卡雷尔执行任务,你必须写出必要的
commands in a very precise way so that the robot can correctly interpret what you have 用命令的方式非常精确,使机器人能够正确地解释你所拥有的
told it to do. 告诉它做的。 In particular, the programs you write must obey a set of syntactic rules that 特别是,你写的程序必须遵守一个规则的 语法是
define what commands and language forms are legal. 定义命令和语言形式是合法的。 Taken together, the predefined 两者合计,预定义
commands and syntactic rules define the Karel programming language . 命令和语法规则定义了卡雷尔 编程语言 。 The Karel该卡雷尔
programming language is designed to be as similar as possible to Java so as to ease the 编程语言被设计成尽可能类似Java,以纾缓
transition to the language you will be using all quarter. 过渡到语言,你将使用所有的四分之一。 Karel programs have much the 卡雷尔方案有很大的
same structure and involve the same fundamental elements as Java programs do. 同样的结构,并涉及相同的基本元素,Java程序做。 The 该
critical difference is that Karel's programming language is extremely small, in the sense 关键的区别是,卡雷尔的编程语言是非常小的感觉,在
that it has very few commands and rules. 它具有非常少的命令和规则。 It is easy, for example, to teach the entire Karel 这是很容易,例如,教整个卡雷尔
language in just a couple of hours, which is precisely what we do in CS106A. 语言在短短几个小时,而这正是我们CS106A做研究。 At the end 在结束
of that time, you will know everything that Karel can do and how to specify those actions 这个时候,你就会知道一切,卡雷尔可以做和如何指定这些行动
in a program. 在程序中。 The details are easy to master. 的细节很容易掌握。 Even so, you will discover that solving a 即便如此,你会发现,解决一
problem can be extremely challenging. 问题可以是非常具有挑战性。 Problem solving is the essence of programming; 解决问题是编程的本质;
the rules are just a minor concern along the way. 规则只是一个次要的方式一起关注。
In sophisticated languages like Java, there are so many details that learning these 在复杂的语言如Java,有这么多的细节,学习这些
details often becomes the focus of the course. 细节往往成为该课程的重点。 When that happens, the much more critical 当发生这种情况,更重要的
issues of problem solving tend to get lost in the shuffle. 解决问题,问题往往会得到一片混乱中失去的。 By starting with Karel, you can 由Karel开始,你们可以
concentrate on solving problems from the very beginning. 集中精力解决问题,从一开始。 And because Karel encourages 而且因为卡雷尔鼓励
imagination and creativity, you can have quite a lot of fun along the way. 想象力和创造力,你可以有很多很多的路沿的乐趣。
2 2
Karel's world 卡雷尔的世界
Karel's world is defined by streets running horizontally (east-west) and avenues running 卡雷尔的世界是指通过运行街道横向(东西向)和渠道运行
vertically (north-south). 纵向(南北)。 The intersection of a street and an avenue is called a corner . 路口的一和一大街被称为一个 角落 。
Karel can only be positioned on corners and must be facing one of the four standard 卡雷尔只能定位在角落的,必须面对一个标准的四个
compass directions (north, south, east, west). 罗盘方向(北,南,东,西)。 A sample Karel world is shown below. 卡雷尔的世界是一个样本所示。 Here 这里
Karel is located at the corner of 1st Street and 1st Avenue, facing east. 卡雷尔位于大街拐角第一街与第一,面向东方。
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
Several other components of Karel's world can be seen in this example. 卡雷尔的世界其它一些成分可以看出,在这个例子。 The object in 对象在
front of Karel is a beeper. As described in Rich Pattis's book, beepers are “plastic cones 卡雷尔面前的是一个 蜂鸣器。正如书中描述的富Pattis的,传呼机是“塑料筒
which emit a quiet beeping noise.” Karel can only detect a beeper if it is on the same 它发出一个安静的哔哔的声音。“卡雷尔只能探测到一个蜂鸣器,如果它是在同一
corner. 角落。 The solid lines in the diagram are walls . 图中的实线是 墙壁 。 Walls serve as barriers within Karel’s墙作为屏障内卡雷尔的
world. 世界。 Karel cannot walk through walls and must instead go around them. 卡雷尔不能穿过墙壁,而必须走他们周围。 Karel's world 卡雷尔的世界
is always bounded by walls along the edges, but the world may have different dimensions 总是被边界墙沿,但世界可能有不同的尺寸
depending on the specific problem Karel needs to solve. 卡雷尔取决于具体的问题需要解决。
What can Karel do? 卡雷尔做什么?
When Karel is shipped from the factory, it responds to a very small set of commands: 卡雷尔是出厂时的,它回应了一套非常小的命令:
move() 移动()
Asks Karel to move forward one block. 卡雷尔要求向前推进一块。 Karel cannot respond to a 卡雷尔不能响应
move() 移动()
command if there is a wall blocking its way. 命令如果有一道墙阻挡了方向。
turnLeft() turnLeft()
Asks Karel to rotate 90 degrees to the left (counterclockwise). 问卡雷尔旋转90度向左(逆时针)。
pickBeeper() pickBeeper()
Asks Karel to pick up one beeper from a corner and stores the beeper 问卡雷尔拿起一个蜂鸣器和存储从一个角落里的蜂鸣器
in its beeper bag, which can hold an infinite number of beepers. 在蜂鸣器袋,可容纳无限的寻呼机号码。 Karel 卡雷尔
cannot respond to a 不能响应
pickBeeper() pickBeeper()
command unless there is a beeper 命令,除非有一个蜂鸣器
on the current corner. 当前角落。
putBeeper() putBeeper()
Asks Karel to take a beeper from its beeper bag and put it down on 卡雷尔要求采取蜂鸣器蜂鸣器从包里拿出了一个,把它写在
the current corner. 目前的角落。 Karel cannot respond to a 卡雷尔不能响应
putBeeper() putBeeper()
command 命令
unless there are beepers in its beeper bag. 除非有袋寻呼机在蜂鸣器。
The empty pair of parentheses that appears in each of these commands is part of the 一对空的括号出现的这些命令是在每个部分
common syntax shared by Karel and Java and is used to specify the invocation of the 常见的语法和Java共享卡雷尔,用于指定调用的
command. 命令。 Eventually, the programs you write will include additional information in the 最后,你写的程序将包括在补充资料
space between the parentheses, but such information is not part of the Karel's primitive 括号之间的空间,但这些资料是不是原始的一部分,卡雷尔的
world. 世界。 These parentheses will therefore be empty in standard Karel programs, but you 因此,将这些空括号在标准卡雷尔程序,但你
must remember to include them nonetheless. 必须要记住,他们仍然。
It is also important to recognize that several of these commands place restrictions on 同样重要的是要认识到这些命令的限制,一些地方在
Karel's activities. 卡雷尔的活动。 If Karel tries to do something illegal, such as moving through a wall or 如果卡雷尔试图做一些非法或,如墙,通过移动
picking up a nonexistent beeper, an error condition occurs. 拿起一个不存在的蜂鸣器,一 错误情况发生。 At this point, Karel displays在这一点上,卡雷尔显示
an error message and does not execute any remaining commands. 一个错误信息和不执行任何剩余的命令。
Karel's commands, however, cannot be executed on their own. 卡雷尔的命令,但是,不能执行自己。 Before Karel can 卡雷尔之前可以
respond to any of these commands, you need to incorporate them into a Karel program. 响应命令任何这些,你需要把它们纳入计划的一个卡雷尔。
3 三
You will have a chance to see a few simple Karel programs in Chapter 2, but before 你将有机会看到卡雷尔方案第2章一些简单,但在此之前
doing so, it is useful to make a few general remarks about the programming philosophy 这样做,是非常有用的编程做出关于哲学的一些一般性发言
that underlies this particular implementation of the Karel programming language. 这种编程语言的基础特别是实施卡雷尔。
Karel and the object-oriented paradigm 卡雷尔和面向对象的范例
When Karel was introduced in the 1970s, the prevailing approach to writing computer 当卡雷尔是在70年代推出,当时的方法来编写计算机
programs was the procedural paradigm. To a large extent, procedural programming is 方案是 程序性的范例。很大程度上,程序编程
the process of decomposing a large programming problem into smaller, more manageable 过程分解成一个大的规划问题更小,更易于管理
units called procedures that define the necessary operations. 所谓 程序,确定单位必要的操作。 Although the strategy of虽然策略
breaking programs down into smaller units remains a vital part of any style of 打破计划分解成更小的单位仍然是一个风格的重要组成部分的任何
programming, modern languages like Java emphasize a different approach called the 规划,现代语言如Java强调了不同的方式被称为
object-oriented paradigm. In object-oriented programming, the programmer's attention 面向对象的范例。在面向对象编程中,程序员的关注
shifts away from the procedural specification of operations and focuses instead on 距转移程序的行动,而不是侧重于规范和
modeling the behavior of conceptually integrated units called objects. Objects in a 单位的行为建模的概念综合称为 对象。对象在
programming language sometimes correspond to physical objects in the real world, but 编程语言有时对应物理对象在现实世界,但
just as often represent more abstract concepts. 正如通常代表较抽象的概念。 The central feature of any object—real or 真正的核心特点或任何对象
abstract—is that it must make sense as a unified whole. 抽象的是,它必须作为一个统一的整体感。
One of the primary advantages of the object-oriented paradigm is that it encourages 范例一面向对象的主要优点是,它鼓励
programmers to recognize the fundamental relationship between the state of an object and 程序员认识对象之间的国家关系的根本
its behavior. 它的行为。 The state of an object consists of a set of attributes that pertain to that object一个对象的 状态由一组属性的,涉及到该对象
and might change over time. 并可能随时间而改变。 For example, an object might be characterized by its 例如,一个对象可能是其特点
location in space, its color, its name, and a host of other properties. 在太空中的位置,它的颜色,它的名字,和一个属性的其他主机。 The behavior of an 该一行为
object refers to the ways in which that object responds to events in its world or 对象引用的方法,使该对象的反应,它的世界事件或
commands from other objects. 命令从其他对象。 In the language of object-oriented programming, the 在语言的面向对象编程,
generic word for anything that triggers a particular behavior in an object is called a 任何事情的通用词,触发一个特定对象的行为被称为
message (although it generally seems clearer to use the word command in the context of 消息 (尽管它似乎更清楚,一般情况下使用这个词的命令
Karel). 卡雷尔)。 The response to a message typically involves changing the state of an object. 该消息的反应通常涉及到一个对象的状态改变的。 For 对于
example, if one of the properties defining the state of an object is its color, then it would 例如,如果一个对象的属性定义一个国家是它的颜色,那就
presumably respond to a 大概回应
setColor(BLUE) setColor(蓝色)
message by changing its color to blue. 消息通过改变其颜色为蓝色。
In many ways, Karel represents an ideal environment for illustrating the object- 在许多方面,卡雷尔代表说明了一个理想的环境,对象,
oriented approach. 导向的方法。 Although no one has actually built a mechanical implementation of 虽然没有人实际上已经建立了一个机械的执行情况
Karel, it is nonetheless easy to imagine Karel as a real-world object. 卡雷尔,但却是不难想象的对象卡雷尔作为一个真实的世界。 Karel is, after all, a 卡雷尔是,毕竟,一
robot, and robots are real-world entities. 机器人,机器人是真实世界的实体。 The properties that define Karel's state are its 卡雷尔的属性,它们定义的状态是其
location in the world, the direction it is facing, and the number of beepers in its beeper 在世界的位置,它正面临的方向,并发出提示其数量传呼机
bag. 袋。 Karel's behavior is defined by the commands to which it responds: 卡雷尔的行为是命令定义为它所回应:
move() 移动()
, ,
turnLeft() turnLeft()
, ,
pickBeeper() pickBeeper()
, and ,和
putBeeper() putBeeper()
. 。 The 该
move() 移动()
command changes Karel’s 命令更改卡雷尔的
location, 位置,
turnLeft() turnLeft()
changes its direction, and the remaining two affect both the number 改变其方向,剩下的两个数字都有影响
of beepers in Karel's bag and the number of beepers on the current corner. 传呼机在卡雷尔袋的数量和传呼机的角落上的电流。
The Karel environment also provides a useful framework for defining one of the 该卡雷尔环境还提供了一个有用的框架确定
central concepts of object-oriented programming. 面向对象编程的核心概念的对象。 In both Karel and Java, it is essential to 在这两个卡雷尔和Java,有必要
differentiate the notion of an object from that of a class . 区分一 类 的概念从一个 对象的。 The easiest way to understand最简单的方式了解
the distinction is to think about a class as a pattern or template for objects that share a模板的区别在于思考或 类的对象作为一种模式,共享一
common behavior and collection of state attributes. 共同的行为和属性的集合的状态。 As you will see in the next chapter, 正如你将看到在下一章
the word 字
Karel 卡雷尔
in a Karel program represents the entire class of robots that know how to 在卡雷尔方案代表了全班同学的机器人知道如何
respond to the 回应
move() 移动()
, ,
turnLeft() turnLeft()
, ,
pickBeeper() pickBeeper()
, and ,和
putBeeper() putBeeper()
commands. 命令。
Whenever you have an actual robot in the world, that robot is an object that represents a 当你在世界的实际机器人,该机器人是一个对象,表示
specific instance of the 具体的实例
Karel 卡雷尔
class. 类。 Although you won't have occasion to do so in 虽然你将没有机会这样做
CS 106A , it is possible to have more than one instance of the 政务司司长106A章,很可能有一个以上的实例
Karel 卡雷尔
class running in the 运行于类
same world. 同一个世界。 Even when there is only a single robot, however, it is important to remember 即使只有一个单一的机器人,但是,重要的是要记住
that object and class are different concepts and to keep those ideas straight in your mind. 对象和类是不同的概念,并使这些想法在你的心里直。
4 4
The importance of practical experience 实践经验的重要性
Programming is very much a learn-by-doing activity. 编程是一个很有学习边干边活动。 As you will continually discover in 正如你将在不断发现
your study of computer science, reading about some programming concept is not the 你的学习计算机科学的概念,阅读有关的一些编程不是
same thing as using that concept in a program. 同样的东西使用的程序概念研究。 Things that seem very clear on the page 事情似乎很清楚的页面上
can be difficult to put into practice. 可能很难付诸实施。
Given the fact that writing programs on your own and getting them to run on the 鉴于你写自己的程序并让他们运行在
computer are essential to learning about programming, it may seem surprising to discover 计算机编程是必不可少的学习,它可能看起来令人惊讶地发现
that this book does not include much discussion of the hands-on aspects of using Karel on 这本书不包括卡雷尔在许多方面的讨论使用的手,
your computer. 您的计算机。 The reason for that omission is that the steps you need to run a Karel 遗漏理由是,该步骤,您需要运行一个卡雷尔
program depend on the environment you're using. 程序取决于你使用的环境。 Running Karel programs on a 卡雷尔上运行方案
Macintosh is somewhat different from running it under Windows. Macintosh是有所不同的Windows下运行它。 Even though the 即使
programming environment you use has a great deal of influence on the nitty-gritty details 编程环境中,您使用具有坚韧不拔的细节nitty -很大的影响
you need to run programs, it has no influence whatsoever on the general concepts. 你需要运行程序,它具有一般概念上没有产生任何影响。 This 这
book describes the general concepts; the details pertinent to each platform will be 书中描述的一般概念,有关细节将在每个平台
distributed as handouts during the course. 课程讲义分发过程中。
The fact that this book omits the practical details, however, should in no sense be 事实上,这本书省略的具体细节,但是,在任何意义上应该是
interpreted as minimizing their importance. 解释为尽量减轻它们的重要性。 If you want to understand how programming 如果你想了解如何编程
works—even in an environment as simple as that provided by Karel—it is essential to 作品,即使在环境这么简单,卡雷尔该规定,有必要
“get your hands dirty” and start using the computer. “把你的手脏”,并开始使用计算机。 Doing so is by far the most effective 这样做是目前最有效的
introduction into the world of programming and the excitement that it holds. 将其引入拥有世界编程和兴奋的了。
Chapter 2 第2章
Programming Karel 编程卡雷尔
In its new object-oriented implementation, the simplest style of Karel program consists of 在其新的面向对象的实现,最简单的风格卡雷尔方案包括
a definition of a new Karel class that specifies a sequence of built-in commands that 一个命令定义一个新类,指定卡雷尔在一个序列的内置的
should be executed when the program is run. 应执行该程序时运行。 A very simple Karel program is shown in 一个非常简单的程序显示在卡雷尔
Figure 1. 图1。
Figure 1. 图1。 Simple Karel example to pick up a single beeper 卡雷尔简单的例子,拿起一个蜂鸣器
/* / *
* File: BeeperPickingKarel.java *文件:BeeperPickingKarel.java
* ----------------------------- * -----------------------------
* The BeeperPickingKarel class extends the basic Karel class *在BeeperPickingKarel类扩展类的基本卡雷尔
* 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.*; 进口stanford.karel .*;
public class BeeperPickingKarel extends Karel { 公共类BeeperPickingKarel延伸卡雷尔{
public void run() { 公共无效的run(){
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
} }
} }
The program in Figure 1 is composed of several parts. 图1方案是由几部分组成。 The first part consists of the 第一部分包括了
following lines: 下面几行:
/* / *
* File: BeeperPickingKarel.java *文件:BeeperPickingKarel.java
* ----------------------------- * -----------------------------
* The BeeperPickingKarel class extends the basic Karel class *在BeeperPickingKarel类扩展类的基本卡雷尔
* 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. *一个蜂鸣器,然后前进到下一个角落。
*/ * /
These lines are an example of a comment, which is simply text designed to explain the 这些行是一个 注释 的例子解释,这是简单的文本设计
operation of the program to human readers. 操作方案的读者给人类。 Comments in both Karel and Java begin with Java的评论中,并开始与卡雷尔
the characters 人物
/* / *
and end with the characters 和结束字符
*/ * /
. 。 Here, the comment begins on the first 在这里,开始第一次发表评论
line and ends several lines later. 行和结束以后几行。 The stars on the individual lines that make up the text of 各行对星星构成的文本
the comment are not required, but make it easier for human readers to see the extent of 注释不是必需的,但更容易为人类读者看到的程度
the comment. 评论。 In a simple program, extensive comments may seem silly because the 在一个简单程序,广泛的评论似乎是愚蠢的,因为
effect of the program is obvious, but they are extremely important as a means of 该方案的效果是显而易见的,但它们是极为重要的一种手段
documenting the design of larger, more complex programs. 记录了方案的设计更大,更复杂。
The second part of the program is the line 该计划的第二部分是行
import stanford.karel.*; 进口stanford.karel .*;
This line requests the inclusion of all definitions from the 该行要求列入定义所有来自
stanford.karel stanford.karel
library. 库。 This 这
6 6
library contains the basic definitions necessary for writing Karel programs, such as the 库包含必要的基本定义,如编写卡雷尔方案
definitions of the standard operations 定义的标准操作
move() 移动()
and 和
pickBeeper() pickBeeper()
. 。 Because you always 因为你总是
need access to these operations, every Karel program you write will include this 需要访问这些操作,你写的每卡雷尔方案将包括本
import 进口
command before you write the actual program. 命令之前你写的实际的程序。
The final part of the Karel program consists of the following class definition: 该计划的最后一部分卡雷尔的包括以下类的定义:
public class BeeperPickingKarel extends Karel { 公共类BeeperPickingKarel延伸卡雷尔{
public void run() { 公共无效的run(){
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
} }
} }
To understand this definition, it is useful to look more carefully at its structure. 要理解这个定义,它是有用的看它的结构更加仔细。 The 该
definition of the 的定义
BeeperPickingKarel BeeperPickingKarel
class consists of the line beginning with 班由与该行的开头
public class 公共类
and encompasses everything between the curly brace at the end of that line 并包括所有的花括号之间在该行的末尾
and the corresponding closing brace on the last line of the program. 右大括号和相应的程序中的最后一行。 The single line that 单行
introduces the new class is called the header of the definition; the code between the 引入了新的类称为定义头部的;之间的代码
braces is called the body. 括号被称为 机构。
In programming, it is often very useful to think about a particular definition and its 在编程中,往往是非常有用的一个特定的定义,并考虑其
body as separable ideas. 身体作为可分离的想法。 In this example, the definition of 在这个例子中,定义
BeeperPickingKarel BeeperPickingKarel
has the 有
following form, where the entire body of the definition has been replaced by a box that 下面的表格,其中整个身体的定义,取代了由一个盒子
you can put out of your mind for the moment: 你可以把你的心不烦的时刻:
public class BeeperPickingKarel extends Karel { 公共类BeeperPickingKarel延伸卡雷尔{
body of the class definition 身体类的定义
} }
The header line at the top tells you quite a bit about the 在顶端标题行在告诉你许多关于有点
BeeperPickingKarel BeeperPickingKarel
class, even 类,甚至
before you have looked to see what the body contains. 之前,你有什么期待,看看体内含有。 The key new concept in the class 新概念的关键在课堂
header is embodied in the word 头是体现在字
extends 延伸
, which is used in both Karel and Java to indicate ,这是用来和Java都表示卡雷尔
that a new class is an extension of an existing one. 一个新的类是一个扩展现有的。 Here, the class header line indicates 在这里,类标题行表示
that 这
BeeperPickingKarel BeeperPickingKarel
is an extension of the standard 是一个标准的扩展的
Karel 卡雷尔
class imported from the 从一流的进口
stanford.karel stanford.karel
library. 库。
In object-oriented languages, defining a new class by extension means that the new 在面向对象的语言中,定义一个新类的扩展,新手段
class (here, 类(在这里,
BeeperPickingKarel BeeperPickingKarel
) builds on the facilities provided by the existing class 器)基于现有类所提供的设施
(in this case, (在这种情况下,
Karel 卡雷尔
). )。 In particular, the fact that it extends 特别是,事实上,它扩展
Karel 卡雷尔
guarantees that the new 保证新
BeeperPickingKarel BeeperPickingKarel
class will have the following properties : 类将具有以下特点:
1. 1。 Any instance of the class 任何类的实例的
BeeperPickingKarel BeeperPickingKarel
is also an instance of the class 也是一个类的实例的
Karel 卡雷尔
. 。
Any instance of the class 任何类的实例的
Karel 卡雷尔
represents a robot that lives in a world of streets, 街道是一个机器人,生活在一个世界,
avenues, beepers, and walls whose state consists of its location, direction, and the 渠道,传呼机,其状态和墙壁方向在于它的位置,以及
number of beepers in its bag. 传呼机号码袋在其。 Because 由于
BeeperPickingKarel BeeperPickingKarel
is an extension of 是的延伸
Karel 卡雷尔
, you know that an instance of ,你知道的一个实例
BeeperPickingKarel BeeperPickingKarel
will also be a robot that 也将是一个机器人
lives in the same type of world and has the same state properties. 世界上生活在同一类型,并具有相同状态的属性。
2. 2。 Any instance of the 任何实例
BeeperPickingKarel BeeperPickingKarel
class will automatically respond to the 类将自动响应
same commands as an instance of the 相同的命令作为一个实例
Karel 卡雷尔
class. 类。 Because every robot in the 因为每次机器人
Karel 卡雷尔
class knows how to respond to the commands 类知道如何应对的命令
move() 移动()
, ,
turnLeft() turnLeft()
, ,
pickBeeper() pickBeeper()
, ,
and 和
putBeeper() putBeeper()
, it follows that a instance of ,可以得出一个实例
BeeperPickingKarel BeeperPickingKarel
will understand 明白
that same set of commands. ,命令相同。
7 7
In other words, the new 换言之,新
BeeperPickingKarel BeeperPickingKarel
class automatically acquires the state 类自动获得该国
attributes and the behavior of the 属性和行为的影响
Karel 卡雷尔
class from which it is derived. 类从它派生。 The process of 该过程
taking on the structure and behavior of the parent class is called inheritance. 类以母公司的结构和行为的被称为 继承。
When a class is defined by extension, the new class is said to be a subclass of the 当一个类被定义为扩展,新的类被认为是A的子类
original. 原始。 In this example, 在这个例子中,
BeeperPickingKarel BeeperPickingKarel
is therefore a subclass of 因此,一个子类
Karel 卡雷尔
. 。
Symmetrically, 对称,
Karel 卡雷尔
is said to be a superclass of 据说是一对超
BeeperPickingKarel BeeperPickingKarel
. 。 Unfortunately, 不幸的是,
this terminology can be confusing for new programmers, who are likely to make the 这个术语可能会造成混乱的新程序员,谁都有可能使
intuitive inference that a subclass is somehow less powerful that its superclass when in 直观推断,一个子类是不那么强大时,它的超类
fact the opposite is true. 事实正好相反。 A subclass inherits the behavior of its superclass and can 一个子类继承其超类的行为,可
therefore respond to the entire set of commands available to that superclass. 因此,对摆在超整套提供的命令。 A subclass, 一个子类,
however, usually defines additional commands that are unavailable to the superclass. 不过,通常定义了超类的其他命令不可用的。
Thus, the typical subclass actually has more functionality than the class from which it 因此,典型的子类,其实有更多的类的功能,它比从
was derived. 推导。 This idea is expressed much more clearly by the notion of extension: a 这个想法表达量的 扩展 更清楚的概念 :一
subclass extends its superclass and can therefore add new capabilities to it. 子类继承其超类,因此可以给它添加新的功能。
Now that you have some idea about what class extension means, it now makes sense to 现在,你有什么想法意味着类扩展,现在它是有意义的
look at the body of the 看看体内
BeeperPickingKarel BeeperPickingKarel
class. 类。 That body consists of the following 这一机构由以下
lines: 行:
public void run() { 公共无效的run(){
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
} }
These lines represent the definition of a new method, which specifies the sequence of 这些线代表的 方法 定义一个新的 ,它指定序列
steps necessary to respond to a command. 必要步骤来响应命令。 As in the case of the 正如在该个案
BeeperPickingKarel BeeperPickingKarel
class itself, the method definition consists of two parts that can be considered separately: 类本身,该方法定义由两个独立的部分,可以考虑:
The first line constitutes the method header and the code between the curly braces is the 第一行构成的方法头和花括号之间的代码是
method body. 方法体。 If you ignore the body for now, the method definition looks like this: 如果你忽略了身体,方法的定义如下:
public void run() { 公共无效的run(){
body of the method definition 身体的方法的定义
} }
The first two words in the method header, 前两个词的方法头,
public 市民
and 和
void 无效
, are part of Java's syntactic 是语法部分的Java
structure, and you should pretty much feel free to ignore them at this point. 结构,你应该感到几乎完全可以忽略这一点他们。 The next 下一个
word on the header line specifies the name of the new method, which in this case is the 行字的头指定的方法名称的新案例,这是
method 方法
run 运行
. 。 Defining a method means that the new Karel subclass can respond to a new 定义一个方法,新手段卡雷尔子类可以响应到一个新的
command with that name. 命令与这个名字。 The built-in 内置的
Karel 卡雷尔
class responds to the commands 类响应的命令
move() 移动()
, ,
turnLeft() turnLeft()
, ,
pickBeeper() pickBeeper()
, and ,和
putBeeper() putBeeper()
; a ;一
BeeperPickingKarel BeeperPickingKarel
responds to that 应这一
same set of commands plus a new command called 同一组的命令加新命令调用
run 运行
. 。 The 该
run 运行
command plays a 命令起着
special role in a Karel program. 在一个特殊的作用卡雷尔方案。 When you start a Karel program in the Eclipse 当您启动Eclipse的卡雷尔方案
environment, it creates a new Karel instance of the appropriate subclass, adds that Karel 环境,它会创建一个相应的子类的新实例卡雷尔补充说,卡雷尔
to a world that you specify, and then issues the 到您所指定的世界,然后问题
run 运行
command. 命令。 The effect of issuing that 认为影响发行
command is defined by the body of the 命令的定义是体内
run 运行
method, which is a sequence of commands 方法,它是一个命令序列
that the robot will execute in order. 该机器人将顺序执行。 For example, the body of the 举例来说,身体的
run 运行
method for the 方法为
BeeperPickingKarel BeeperPickingKarel
class is 类
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
8 8
Thus, if the initial state of the world matches the example given in Chapter 1, Karel first 因此,如果世界初始状态的比赛卡雷尔首先,在第一章中给出的示例
moves forward into the corner containing the beeper, picks up that beeper, and finally 寻呼机,移动到角落里包含着的回升是蜂鸣器,终于
moves forward to the corner just before the wall, as shown in the following before-and- 前进到街角的墙壁前,如下所示,在之前和
after diagram: 后图:
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
Before 前
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
After 后
Solving a more interesting problem 更有趣的问题求解
The 该
BeeperPickingKarel BeeperPickingKarel
class defined in Figure 1 doesn't do very much as yet. 一类图中定义没有做很应该。 Let’s 让我们
try to make it a little more interesting. 努力创造一个更有趣了。 Suppose that the goal is not simply to get Karel to 假设目标不只是要得到卡雷尔
pick up the beeper but to move the beeper from its initial position on 2nd Avenue and 1st 拿起呼叫器,但移动和第一蜂鸣器第二大街上从它的初始位置
Street to the center of the ledge at 5th Avenue and 2nd Street. 街,街中心的窗台在第五大道和第2。 Thus, your next assignment 因此,你的下一个任务
is to define a new Karel subclass that accomplishes the task illustrated in this diagram: 是定义一个新的卡雷尔子图,完成此任务中所示:
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
Before 前
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
After 后
The first three commands in the new program—the ones that move forward, pick up 前三,命令,在新方案向前推进的那些拿起
the beeper, and then move up to the ledge—are the same as before: 蜂鸣器,然后移动到窗台,是以前相同:
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
From here, the next step is to turn left to begin climbing the ledge. 从这里,下一步是向左转,开始攀登悬崖。 That operation is easy, 这种操作容易,
because Karel has a 因为卡雷尔有
turnLeft turnLeft
command in its standard repertoire. 命令其标准剧目。 Executing a 执行
turnLeft turnLeft
command at the end of the preceding sequence of commands leaves Karel facing north on 命令,命令在年底前序朝北的叶子上卡雷尔
the corner of 3rd Avenue and 1st Street. 第三大街的拐角处和第一街。 If Karel then executes a 如果卡雷尔然后执行
move 移动
command, it will 命令,它会
move north to reach the following position: 向北移动,达到以下立场:
9 9
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
From here, the next thing you need to do is get Karel to turn right so that it is again facing 从这里,接下来你需要做的就是卡雷尔右转,使其再次面临
east. 东。 While this operation is conceptually just as easy as getting Karel to turn left, there is 虽然这种操作是一样简单的概念越来越卡雷尔左转,有
a slight problem: Karel's language includes a 一个小问题:卡雷尔的语言中还包括
turnLeft turnLeft
command, but no 命令,但没有
turnRight turnRight
command. 命令。 It's as if you bought the economy model and have now discovered that it is 就好像你买了经济模式与已经发现,这是
missing some important features. 缺少一些重要的功能。
At this point, you have your first opportunity to begin thinking like a programmer. 在这一点上,你有你第一次有机会开始思考这样一个程序员。 You 你
have one set of commands, but not exactly the set you need. 有一组命令,但不完全相同的设置你的需要。 What can you do? 你可以做什么? Can you 你能
accomplish the effect of a 完成一个效果
turnRight turnRight
command using only the capabilities you have? 命令只用你的能力呢?
The answer, of course, is yes. 答案当然是肯定的。 You can accomplish the effect of turning right by turning 你就可以完成转右转看效果
left three times. 离开了三次。 After three left turns, Karel will be facing in the desired direction. 经过三年左转弯,卡雷尔将面临在希望的方向。 From 从
here, all you need to do is program Karel to move over to the center of the ledge, drop the 在这里,所有您需要做的是程序卡雷尔移动到窗台中心的,删除
beeper and then move forward to the final position. 蜂鸣器,然后继续前进到最后的位置。 A complete implementation of a 一个完整的实现
BeeperTotingKarel BeeperTotingKarel
class that accomplishes the entire task is shown in Figure 2. 类,完成整个任务见图2。
Figure 2. 图2。 Program to carry a beeper to the top of a ledge 计划携带寻呼机到窗台上的一
/* / *
* File: BeeperTotingKarel.java *文件:BeeperTotingKarel.java
* ---------------------------- * ----------------------------
* The BeeperTotingKarel class extends the basic Karel class *在BeeperTotingKarel类扩展类的基本卡雷尔
* so that Karel picks up a beeper from 1st Street and then *使卡雷尔拿起一条街蜂鸣器,然后从第一
* carries that beeper to the center of a ledge on 2nd Street. *携带,街头蜂鸣器到2日,中心的窗台。
*/ * /
import stanford.karel.*; 进口stanford.karel .*;
public class BeeperTotingKarel extends Karel { 公共类BeeperTotingKarel延伸卡雷尔{
public void run() { 公共无效的run(){
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
turnLeft(); turnLeft();
move(); 移动();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
move(); 移动();
move(); 移动();
putBeeper(); putBeeper();
move(); 移动();
} }
} }
10 10
Defining new methods 定义新方法
Even though the 即使
BeeperTotingKarel BeeperTotingKarel
class in Figure 2 demonstrates that it is possible to 2类图表明,它是可能的
perform the 执行
turnRight turnRight
operation using only Karel's built-in commands, the resulting 手术只用卡雷尔的内置命令,由此产生的
program is not particularly clear conceptually. 程序不是特别清楚概念。 In your mental design of the program, 在您的设计方案的精神,
Karel turns right when it reaches the top of the ledge. 卡雷尔右转当它到达窗台顶部。 The fact that you have to use three 事实上,你必须使用三
turnLeft turnLeft
commands to do so is annoying. 命令,这样做很讨厌。 It would be much simpler if you could simply 这将是非常简单,如果你可以简单地
say 说
turnRight turnRight
and have Karel understand this command. 卡雷尔明白这一点,并已命令。 The resulting program would 由此产生的方案将
not only be shorter and easier to write, but also significantly easier to read. 不仅要短,更容易编写,而且还极大地方便阅读。
Fortunately, the Karel programming language makes it possible to define new 幸运的是,卡雷尔编程语言使人们有可能定义新
commands simply by including new method definitions. 简单的命令,包括新方法的定义。 Whenever you have a sequence 只要你有一个序列
of Karel commands that performs some useful task—such as turning right—you can 卡雷尔命令的执行一些有用的任务,如右转,你可以
define a new method that executes that sequence of commands. 定义一个新的方法,执行该命令序列。 The format for defining a 该定义的格式
new Karel method has much the same as the definition of 卡雷尔新方法与其说是一样的定义
run 运行
in the preceding examples, 在前面的例子,
which is a method definition in its own right. 这是一个正确的方法定义自己的。 A typical method definition looks like this: 一个典型的方法定义如下:
private void name () { 私人无效 名称 (){
commands that make up the body of the method 命令弥补身体的方法
} }
In this pattern, 在这个模式中,
name 名称
represents the name you have chosen for the new method. 代表你的方法的名称为新的选择。 To 要
complete the definition, all you have to do is provide the sequence of commands in the 完整的定义,你所要做的就是提供的序列中的命令
lines between the curly braces. 线之间的大括号。 For example, you can define 例如,你可以定义
turnRight turnRight
as follows: 如下:
private void turnRight() { 私人无效turnRight(){
turnLeft(); turnLeft();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
} }
Similarly, you could define a new 同样的,你可以定义一个新的
turnAround 好转
method like this: 方法是这样的:
private void turnAround() { 私人无效周转(){
turnLeft(); turnLeft();
turnLeft(); turnLeft();
} }
You can use the name of a new method just like any of Karel's built-in commands. 你可以使用新的方法,就像任何一个卡雷尔的名称内置的命令。 For 对于
example, once you have defined 例如,一旦你已经定义
turnRight turnRight
, you could replace the three ,您可以取代三
turnLeft turnLeft
commands in the 命令中
BeeperTotingKarel BeeperTotingKarel
class with a single call to the 一类具有单个调用
turnRight turnRight
method. 方法。
A revised implementation of the program that uses 该方案一经修订的实施,使用
turnRight turnRight
is shown in Figure 3. 见图3。
There is, of course, one obvious difference between the definitions of the 有,当然,一个明显的差异的定义的
run 运行
and 和
turnRight turnRight
methods shown in Figure 3: the 3图所示的方法在:
run 运行
method is marked as 方法标记为
public 市民
in contrast 相反
to 到
turnRight turnRight
, which is marked as ,它被标记为
private 私人
. 。 The difference between these two 这两个之间的差异
designations is that public methods can be invoked from outside the class, while private 名称是类的公共方法,可以从外部调用,而私人
methods cannot. 方法不能。 The 该
run 运行
method needs to be public because the Karel environment 方法需要被公开,因为环境的卡雷尔
needs to be able to issue a 需要能够发出
run 运行
command to get things going. 命令得到的东西去。 By contrast, 相比之下,
turnRight turnRight
is 是
used only inside the other code appearing within this class. 只用在其他类中的代码出现。 That definition, therefore, can 这一定义,因此,可以
be private, and it is generally good programming practice to keep definitions private 是私有的,而且总体上是好的编程习惯保持私人定义
whenever possible. 只要有可能。 The reasons for this rule are difficult to appreciate until you have had 这一规则的理由是很难理解,直到有
a chance to work with larger programs, but the basic idea is that classes should try as 一个工作的机会和更大的方案,但基本的想法是,作为类应该尝试
much as possible to encapsulate information, which means not only to gather it together 尽可能地 封装信息,这意味着不仅要聚集一起
but also to restrict access to that information if possible. 而且还限制访问该信息,如果可能的。 Large programs quickly become 大项目迅速成为
very complex in terms of the volume of detail that they encompass. 非常复杂的计算,包括他们的音量细节。 If a class is well 如果一类是很好
designed, it will seek to reduce that complexity by hiding as much extraneous detail as it 设计的,它会设法减少,因为它是由许多复杂的细节无关的深藏不露
11 11
Figure 3. 图3。 Revised implementation of BeeperTotingKarel that includes a turnRight method 经修订的实施BeeperTotingKarel方法,其中包括一个turnRight
/* / *
* File: BeeperTotingKarel.java *文件:BeeperTotingKarel.java
* ---------------------------- * ----------------------------
* The BeeperTotingKarel class extends the basic Karel class *在BeeperTotingKarel类扩展类的基本卡雷尔
* so that Karel picks up a beeper from 1st Street and then *使卡雷尔拿起一条街蜂鸣器,然后从第一
* carries that beeper to the center of a ledge on 2nd Street. *携带,街头蜂鸣器到2日,中心的窗台。
*/ * /
import stanford.karel.*; 进口stanford.karel .*;
public class BeeperTotingKarel extends Karel { 公共类BeeperTotingKarel延伸卡雷尔{
public void run() { 公共无效的run(){
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
turnLeft(); turnLeft();
move(); 移动();
turnRight(); turnRight();
move(); 移动();
move(); 移动();
putBeeper(); putBeeper();
move(); 移动();
} }
/** / **
* Turns Karel 90 degrees to the right. *打开卡雷尔90度到右边。
*/ * /
private void turnRight() { 私人无效turnRight(){
turnLeft(); turnLeft();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
} }
} }
can. 可以。 This property is known as information hiding and is a cornerstone of the object- 此属性被称为 信息隐藏的基石,是一个对象的-
oriented philosophy. 为本的理念。
At this point in your study of programming, you are not likely to find these arguments 在这一点在编程的学习,你是不是很容易找到这些论点
about encapsulation particularly convincing. 关于封装特别令人信服。 Defining 定义
turnRight turnRight
and 和
turnAround 好转
in 在
every program is certainly a bit of a pain, particularly in light of the fact that they are so 每个程序无疑是一个痛苦的事实位,特别是鉴于他们是如此
useful. 有用。 The difficulty, however, in making them more easily available lies in figuring out 困难,但是,容易获得在于搞清楚在使他们更
where to put those definitions. 地方把这些定义。 Declaring 声明
turnRight turnRight
to be public in the definition of 为公众的定义
BeeperTotingKarel BeeperTotingKarel
would not be much help. 会不会有太大的帮助。 In an object-oriented language, the 在一个面向对象的语言,
methods that specify the behavior of a class are encapsulated within that class. 方法,指定一个类的行为是封装在那个类。 The 该
turnRight turnRight
method that appears within that class knows how to turn an instance of 方法类会出现在它知道如何把一个实例
BeeperTotingKarel BeeperTotingKarel
90 degrees to the right, but that method cannot be applied to an 90度的权利,但该方法不能应用到
instance of the 实例的
Karel 卡雷尔
class or any its subclasses. 类或任何其子类。
In some sense, what you really want to do is add 从某种意义上说,你真正想要做的就是添加
turnRight turnRight
and 和
turnAround 好转
to the 到
Karel 卡雷尔
class so that all subclasses will be able to use these undeniably useful commands. 所有子类,这样将能够使用这些无可否认有用的命令。
The problem with that strategy is that you don't necessarily have the access to the 该战略问题,那就是你不一定具有的访问
Karel 卡雷尔
class necessary to make such a change. 类有必要作出这样的改变。 The 该
Karel 卡雷尔
class is part of the Stanford library, 类是斯坦福大学图书馆的一部分,
which is used by all students in this CS106A. 这是使用这CS106A所有学生英寸 If you were to go and make changes to it, 如果你去,使其变向,
you might end up breaking someone else's program, which would not endear you to the 你可能会弄坏别人的计划,不会爱戴你到
other students. 其他学生。 Similarly, if you at some point later in the quarter decide that you really 同样,如果你在某些时候在本季度晚些时候,你真的决定
want to add something to one of the standard Java classes, you won't actually be able to 要添加到类的东西的一个标准的Java,你也不会真正能够
12 12
change that class because it is under the control of Sun Microsystems. 改变这一类,因为它是在Sun微系统的控制。 What you can do, 你可以做什么,
however, is define a new class that includes your new features as extensions. 然而,就是定义一个新类,其中包括作为扩展新的功能。 Thus, if you 因此,如果你
want to use 要使用
turnRight turnRight
and 和
turnAround 好转
in several different Karel programs, you could 卡雷尔在几个不同的方案,你可以
define a new class that included these method definitions and then create your programs 定义一个新类,其中包括这些方法的定义,然后创建您的程序
by extending that class. 通过扩展的类。 This technique is illustrated in Figure 4, which consists of two 这种技术是在图4所示,其中两个由
program files. 程序文件。 The first contains a class definition called 第一个包含一个类的定义称为
NewImprovedKarel NewImprovedKarel
that 这
includes the 包括
turnRight turnRight
and 和
turnAround 好转
definitions as public methods so that other 方法的定义,让其他公共
classes can use them. 类可以使用它们。 The second is yet another implementation of 二是实施的又一
BeeperTotingKarel BeeperTotingKarel
that extends 扩展
NewImprovedKarel NewImprovedKarel
, thereby giving itself access to these methods. ,从而使自身获得这些方法。
The 该
stanford.karel stanford.karel
package does not include the 包不包括
NewImprovedKarel NewImprovedKarel
class as it 类,因为它
appears here but does include a 出现在这里,但不包括
SuperKarel SuperKarel
class that includes the methods 类,包含的方法
turnRight turnRight
and 和
turnAround 好转
along with several other extensions that will make it possible for you to 随着一些其他的扩展,这将使得有可能为你
write much more exciting programs. 写更精彩的节目。 The examples that follow extend 下面的例子中延伸
SuperKarel SuperKarel
to 到
ensure that these methods are available. 确保这些方法都可用。 The other extensions are described in Chapter 6. 其他扩展第6章中介绍。
Decomposition 分解
As a way of illustrating more of the power that comes with being able to define new 作为一种新的方式界定,说明更多的权力,能够附带
methods, it's useful to have Karel do something a little more practical than move a beeper 方法,它的有用的卡雷尔做更实际一点的东西不是移动一个蜂鸣器
from one place to another. 从一个地方到另一个地方。 The roadways around Palo Alto often seem to be in need of 帕洛阿尔托道路周围似乎往往是有需要的
repair, and it might be fun to see if Karel can fill potholes in its abstract world. 修理,而且可能会很有趣,看看是否能填补世界坑洼卡雷尔在其抽象的。 For 对于
example, imagine that Karel is standing on the “road” shown in the left-hand figure, one 例如,假设卡雷尔是一个站在“道”,显示在左侧的数字
corner to the left of a pothole in the road. 拐角处的道路左边的一坑洞英寸 Karel's job is to fill the hole with a beeper and 卡雷尔的工作是为了填补的洞和蜂鸣器
proceed to the next corner. 进入下一个角落。 The diagram on the right illustrates how the world should look 右图说明了如何对世界看起来应该
after the program execution. 后执行程序。
Before 前
After 后
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
1 一
2 2
3 三
4 4
5 5
6 6
If you are limited to the four predefined commands, the 如果你被限制在四个预定义的命令,
run 运行
method to solve this problem 方法来解决这个问题
would look like this: 看起来像这样:
public void run() { 公共无效的run(){
move(); 移动();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
move(); 移动();
putBeeper(); putBeeper();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
move(); 移动();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
move(); 移动();
} }
13 13
Figure 4. 图4。 Defining a NewImprovedKarel class that understands turnRight and turnAround 定义NewImprovedKarel类,理解和周转turnRight
/* / *
* File: NewImprovedKarel.java *文件:NewImprovedKarel.java
* --------------------------- * ---------------------------
* The NewImprovedKarel class extends the basic Karel class *在NewImprovedKarel类扩展类的基本卡雷尔
* so that any subclasses have access to the turnRight and *使任何小类访问turnRight和
* turnAround methods. *周转方法。 It does not define any run method 它没有定义任何运行方法
* of its own. *自身。
*/ * /
import stanford.karel.*; 进口stanford.karel .*;
public class NewImprovedKarel extends Karel { 公共类NewImprovedKarel延伸卡雷尔{
/** / **
* Turns Karel 90 degrees to the right. *打开卡雷尔90度到右边。
*/ * /
public void turnRight() { 公共无效turnRight(){
turnLeft(); turnLeft();
turnLeft(); turnLeft();
turnLeft(); turnLeft();
} }
/** / **
* Turns Karel around 180 degrees. *打开约180度卡雷尔。
*/ * /
public void turnAround() { 公共无效周转(){
turnLeft(); turnLeft();
turnLeft(); turnLeft();
} }
} }
/* / *
* File: BeeperTotingKarel.java *文件:BeeperTotingKarel.java
* ---------------------------- * ----------------------------
* The BeeperTotingKarel class extends the basic Karel class *在BeeperTotingKarel类扩展类的基本卡雷尔
* so that Karel picks up a beeper from 1st Street and then *使卡雷尔拿起一条街蜂鸣器,然后从第一
* carries that beeper to the center of a ledge on 2nd Street. *携带,街头蜂鸣器到2日,中心的窗台。
*/ * /
import stanford.karel.*; 进口stanford.karel .*;
public class BeeperTotingKarel extends NewImprovedKarel { 公共类BeeperTotingKarel延伸NewImprovedKarel {
public void run() { 公共无效的run(){
move(); 移动();
pickBeeper(); pickBeeper();
move(); 移动();
turnLeft(); turnLeft();
move(); 移动();
turnRight(); turnRight();
move(); 移动();
move(); 移动();
putBeeper(); putBeeper();
move(); 移动();
} }
} }
14 14
You can, however, make the main program easier to read by extending 你可以,但是,使程序更易于阅读主要通过扩展
SuperKarel SuperKarel
and 和
then making use of the 然后使用
turnAround 好转
and 和
turnRight turnRight
methods. 方法。 This version of the 这个版本的
program appears in Figure 5. 方案显示在图5。
The initial motivation for defining the 在确定初步动机
turnRight turnRight
method was that it was cumbersome 方法是,它很麻烦
to keep repeating three 不断重复三
turnLeft turnLeft
commands to accomplish a right turn. 命令完成右转。 Defining new 定义新的
methods has another important purpose beyond allowing you to avoid repeating the same 重复同样的方法有一个重要的目的,使您避免以后
command sequences every time you want to perform a particular task. 命令序列每次要执行特定的任务。 The power to 的权力
define methods unlocks the most important strategy in programming—the process of 定义方法解锁,这个过程中最重要的战略规划
breaking a large problem down into smaller pieces that are easier to solve. 破容易解决的大问题分解成更小的片断的。 The process of 该过程
breaking a program down into smaller pieces is called decomposition , and the 分解成小块下来是一个程序称为 分解 ,
component parts of a large problem are called subproblems . 大问题的一个组成部分,被称为 子 。
As an example, the problem of filling the hole in the roadway can be decomposed into 作为一个例子,在巷道问题的填充孔可以分解成
the following subproblems: 下面的子问题:
1. 1。 Move up to the hole 向上移动到洞
2. 2。 Fill the hole by dropping a beeper into it 填充孔内投进它一个蜂鸣器
3. 3。 Move on to the next corner 移动到下一个弯道
If you think about the problem in this way, you can use method definitions to create a 如果你的方式思考这个问题,您可以使用方法定义来创建
program that reflects your conception of the program structure. 程序结构的概念反映了你的计划。 The 该
run 运行
method would 法会
look like this: 看起来像这样:
public void run() { 公共无效的run(){
move(); 移动();
fillPothole(); fillPothole();
move(); 移动();
} }
Figure 5. 图5。 Karel program to fill a single pothole 卡雷尔方案,以填补一个坑洞
/* / *
* File: PotholeFillingKarel.java *文件: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() { 公共无效的run(){
move(); 移动();
turnRight(); turnRight();
move(); 移动();
putBeeper();
turnAround();
move(); 移动();
turnRight(); turnRight();
move(); 移动();
} }
} }
15 15
The correspondence with the outline is immediately clear, and everything would be great
if only you could get Karel to understand what you mean by
fillPothole
. 。 Given the 鉴于
power to define methods, implementing
fillPothole
is extremely simple. All you have
to do is define a
fillPothole
method whose body consists of the commands you have
already written to do the job, like this:
private void fillPothole() {
turnRight(); turnRight();
move(); 移动();
putBeeper();
turnAround();
move(); 移动();
turnRight(); turnRight();
} }
The complete program is shown in Figure 6.
Figure 6. 图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() { 公共无效的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(); turnRight();
move(); 移动();
putBeeper();
turnAround();
move(); 移动();
turnRight(); turnRight();
} }
} }
16 16
Choosing the correct decomposition
There are, however, other decomposition strategies you might have tried. For example, 例如,
you could have written the program as
public void run() { 公共无效的run(){
approachAndFillPothole();
move(); 移动();
} }
where the 其中
approachAndFillPothole
method is simply
private void approachAndFillPothole() {
move(); 移动();
turnRight(); turnRight();
move(); 移动();
putBeeper();
turnAround();
move(); 移动();
turnRight(); turnRight();
} }
Alternatively, you might have written the program as
public void run() { 公共无效的run(){
move(); 移动();
turnRight(); turnRight();
move(); 移动();
fillPotholeYouAreStandingIn();
turnAround();
move(); 移动();
turnRight(); turnRight();
move(); 移动();
} }
where the body of
fillPotholeYouAreStandingIn
consists of a single
putBeeper
command. 命令。 Each of these programs represents a possible decomposition. Each program
correctly solves the problem. Given that all three versions of this program work, what
makes one choice of breaking up the problem better than another?
In general, deciding how to decompose a program is not easy. In fact, as the problems
become more complex, choosing an appropriate decomposition will turn out to be one of
the more difficult aspects of programming. You can, however, rely to some extent on the
following guidelines:
1. Each subproblem should perform a conceptually simple task. The solution of a
subproblem may require many commands and may be quite complex in terms of its
internal operation. Even so, it should end up accomplishing some conceptual task that
is itself easy to describe. A good indication of whether you have succeeded in
identifying a reasonable task comes from the name you give to the method. If you can 如果你能
accurately define its effect with a simple descriptive name, you have probably chosen
a good decomposition. On the other hand, if you end up with complex names such as
approachAndFillPothole
, the decomposition does not seem as promising.
2. Each subproblem should perform a task that is as general as possible, so that it can
be used in several different situations. If one decomposition results in a program that
is only useful in the exact situation at hand and another would work equally well in a
variety of related situations, you should probably choose the more general one.
Chapter 3 第3章
Control Statements in Karel
The technique of defining new methods—as useful as it is—does not actually enable
Karel to solve any new problems. Because each method name is merely a shorthand for a
specific set of commands, it is always possible to expand a program written as a series of
method calls into a single main program that accomplishes the same task, although the
resulting program is likely to be long and difficult to read. The commands—no matter
whether they are written as a single program or broken down into a set of methods—are
still executed in a fixed order that does not depend on the state of Karel's world. Before 前
you can solve more interesting problems, you need to discover how to write programs in
which this strictly linear, step-by-step order of operations does not apply. In particular, 特别是,
you need to learn several new features of the Karel programming language that make it
possible for Karel to examine its world and change its execution pattern accordingly.
Statements that affect the order in which a program executes commands are called
control statements. Control statements generally fall into the following two classes:
1. Conditional statements. Conditional statements specify that certain statements in a
program should be executed only if a particular condition holds. In Karel, you specify
conditional execution using an
if 如果
statement. 声明。
2. Iterative statements. Iterative statements specify that certain statements in a program
should be executed repeatedly, forming what programmers call a loop. Karel supports
two different iterative statements: a
for 为
statement that is useful when you want to
repeat a set of commands a predetermined number of times and a
while 而
statement 声明
that is useful when you want to repeat an operation as long as some condition holds.
This chapter introduces each of these control statement forms in the context of Karel
problems that illustrate the need for each statement type.
Conditional statements
To get a sense of where conditional statements might come in handy, let's go back to the
fillPothole
program presented at the end of Chapter 2. Before filling the pothole in the
fillPothole
method, there are a few conditions that Karel might want to check. For 对于
example, Karel might want to check to see if some other repair crew has already filled the
hole, which means that there is already a beeper on that corner. If so, Karel does not need
to put down a second one. To represent such checks in the context of a program, you need
to use the
if 如果
statement, which ordinarily appears in the following form:
if ( conditional test ) {
statements to be executed only if the condition is true
} }
The conditional test shown in the first line of this pattern must be replaced by one of the
tests Karel can perform on its environment. The result of that conditional test is either
true or false. 真或假。 If the test is true, Karel executes the statements enclosed in braces; if the test
is false, Karel does nothing.
The tests that Karel can perform are listed in Table 1. Note that each test includes an
empty set of parentheses, which is used as a syntactic marker in Karel's programming
language to show that the test is being applied. Note also that every condition in the list
has a corresponding opposite. For example, you can use the
frontIsClear
condition to
check whether the path ahead of Karel is clear or the
frontIsBlocked
condition to see if
18 18
Table 1. 表1。 Conditions that Karel can test
Test 测试
Opposite 相反
What it checks
frontIsClear()
frontIsBlocked()
Is there a wall in front of Karel?
leftIsClear()
leftIsBlocked()
Is there a wall to Karel's left?
rightIsClear()
rightIsBlocked()
Is there a wall to Karel's right?
beepersPresent()
noBeepersPresent()
Are there beepers on this corner?
beepersInBag()
noBeepersInBag()
Any there beepers in Karel's bag?
facingNorth()
notFacingNorth()
Is Karel facing north?
facingEast()
notFacingEast()
Is Karel facing east?
facingSouth()
notFacingSouth()
Is Karel facing south?
facingWest()
notFacingWest()
Is Karel facing west?
there is a wall blocking the way. The 该
frontIsClear
condition is true whenever
frontIsBlocked
is false and vice versa. Choosing the right condition to use in a program
requires you to think about the logic of the problem and see which condition is easiest to
apply. 适用。
You can use the 您可以使用
if 如果
statement to modify the definition of the
fillPothole
method so
that Karel puts down a beeper only if there is not already a beeper on that corner. To do 要做到
so, the conditional test you need is
noBeepersPresent
. 。 If there are no beepers present on
the corner, Karel should put down a new one. If there is already a beeper there, Karel
should do nothing. The new definition of
fillPothole
, which checks to make sure that
there is not already a beeper in the hole, looks like this:
private void fillPothole() {
turnRight(); turnRight();
move(); 移动();
if (noBeepersPresent()) {
putBeeper();
} }
turnAround();
move(); 移动();
turnRight(); turnRight();
} }
The 该
if 如果
statement in this example illustrates several features common to all control
statements in Karel. The control statement begins with a header , which indicates the type
of control statement along with any additional information to control the program flow.
In this case, the header is
if (noBeepersPresent())
which shows that the statements enclosed within the braces should be executed only if the
noBeepersPresent
test is true. The statements enclosed in braces represent the body of
the control statement.
By convention, the body of any control statement is indented with respect to the
statements that surround it. The indentation makes it much easier to see exactly which
statements will be affected by the control statement. Such indentation is particularly
important when the body of a control statement contains other control statements. For 对于
example, you might want to make an additional test to see whether Karel had any beepers
before trying to put one down. To do so, all you would need to do is add a new
if 如果
statement inside the existing one, like this:
19 19
if (noBeepersPresent()) {
if (beepersInBag()) {
putBeeper();
} }
} }
In this example, the 在这个例子中,
putBeeper
command is executed only if there is no beeper on the
corner and there are beepers in Karel's bag. Control statements that occur inside other
control statements are said to be nested .
The outcome of a decision in a program is not always a matter of whether to do
nothing or perform some set of operations. In some cases, you need to choose between
two alternative courses of action. For these cases, the
if 如果
statement in Karel has an
extended form that looks like this:
if ( conditional test ) {
statements to be executed if the condition is true
} else { 还有{}
statements to be executed if the condition is false
} }
This form of the
if 如果
statement is illustrated by the method
invertBeeperState
, which ,其中
picks up a beeper if there is one and puts down a beeper if the corner is empty.
private void invertBeeperState() {
if (beepersPresent()) {
pickBeeper();
} else { 还有{}
putBeeper();
} }
} }
Iterative statements
In solving Karel problems, you will often find that repetition is a necessary part of your
solution. 解决方案。 If you were really going to program a robot to fill potholes, it would hardly be
worthwhile to have it fill just one. The value of having a robot perform such a task comes
from the fact that the robot could repeatedly execute its program to fill one pothole after
another. 另一个。
To see how repetition can be used in the context of a programming problem, consider
the following stylized roadway in which the potholes are evenly spaced along 1st Street
at every even-numbered avenue:
1 一
2 2
3 三
4 4
5 5
6 6
7 7
8 8
9 9
10 10
11 11
1 一
2 2
3 三
Your mission is to write a program that instructs Karel to fill all the holes in this road.
Note that the road reaches a dead end after 11th Avenue, which means that you have
exactly five holes to fill.
20 20
Since you know from this example that there are exactly five holes to fill, the control
statement that you need is a
for 为
statement, which specifies that you want to repeat some
operation a predetermined number of times. The structure of the 本的结构
for 为
statement appears
complicated primarily because it is actually much more powerful than anything Karel
needs. 需要。 The only version of the
for 为
syntax that Karel uses is
for (int i = 0; i < count ; i++) {
statements to be repeated
} }
where 其中
count 计数
is an integer indicating the number of repetitions. For example, if you want
to change the
fillPothole
program so that it solves the more complex problem of filling
five evenly-spaced holes, all you have to do is change the
run 运行
method as follows:
public void run() { 公共无效的run(){
for (int i = 0; i < 5; i++) {
move(); 移动();
fillPothole();
move(); 移动();
} }
} }
The 该
for 为
statement is useful only when you know in advance the number of repetitions
you need to perform. In most applications, the number of repetitions is controlled by the
specific nature of the problem. For example, it seems unlikely that a pothole-filling robot
could always count on there being exactly five potholes. It would be much better if Karel
could continue to fill holes until it encountered some condition that caused it to stop, such
as reaching the end of the street. Such a program would be more general in its application
and would work correctly in either of the following worlds as well as any other world in
which the potholes were evenly spaced exactly two corners apart:
1 一
2 2
3 三
4 4
5 5
6 6
7 7
8 8
9 9
1 一
2 2
3 三
1 一
2 2
3 三
1 一
2 2
3 三
To write a general program that works with any of these worlds, you need to use a
while 而
statement. 声明。 In Karel, a
while 而
statement has the following general form:
while ( test ) {
statements to be repeated
} }
The test in the header line is chosen from the set of conditions listed in Table 1 earlier in
this chapter. In this case, Karel needs to check whether the path in front is clear by
invoking the condition
frontIsClear
. 。 If you use the
frontIsClear
condition in a
while 而
loop, Karel will repeatedly execute the loop until it hits a wall. The 该
while 而
statement 声明
therefore makes it possible to solve the more general problem of repairing a roadway, as
long as the potholes appear at every even-numbered corner and the end of the roadway is
marked by a wall. The 该
RoadRepairKarel
class that accomplishes this task is shown in
Figure 7. 图7。
21 21
Figure 7. 图7。 Program to fill regularly spaced potholes in a roadway
/* / *
/* / *
* File: RoadRepairKarel.java
* --------------------------
* The RoadRepairKarel class fills a series of regularly
* spaced potholes until it reaches the end of the roadway.
*/ * /
import stanford.karel.*;
public class RoadRepairKarel extends SuperKarel {
public void run() { 公共无效的run(){
while (frontIsClear()) {
move(); 移动();
fillPothole();
move(); 移动();
} }
} }
/** / **
* Fills the hole beneath Karel's current position by
* placing a beeper in the hole. For this method to
* work correctly, Karel must be facing east immediately
* above the hole. When execution is complete, Karel
* will have returned to the same square and will again
* be facing east. This version of fillPothole checks to
* see if there is already a beeper present before putting
* a new one down.
*/ * /
private void fillPothole() {
turnRight(); turnRight();
move(); 移动();
if (noBeepersPresent()) {
putBeeper();
} }
turnAround();
move(); 移动();
turnRight(); turnRight();
} }
} }
Solving general problems
So far, the various pothole-filling programs have not been very realistic, because they
rely on specific conditions—such as evenly spaced potholes—that are unlikely to be true
in the real world. 在现实世界中。 If you want to write a more general program to fill potholes, it should
be able to work with fewer constraints. In particular, 特别是,
• The program should be able to work with roads of arbitrary length. It does not make
sense to design a program that works only for roads with a predetermined number of
corners. 角落。 Instead, you want to make the same program work for roads of any length.
Such programs, however, do need to know when they have come to the end of the
road, so it makes sense to require that the end of the roadway is marked by a wall.
• The potholes may occur at any position in the roadway. There should be no limits on
the number of potholes or any restrictions on their spacing. A pothole is identified
simply by an opening in the wall representing the road surface.
22 22
• Existing potholes may already have been repaired. Any of the potholes may already
contain a beeper left by a previous repair crew. In such cases, Karel should not put
down an additional beeper.
To change the program so that it solves this more general problem requires you to
think about the overall strategy in a different way. Instead of having the loop in the main
program cycle through each pothole, you need to have it check each corner as it goes. If 如果
there is an opening at that corner, Karel needs to try and fill the pothole. If there is a wall,
Karel can simply move ahead to the next corner.
This strategic analysis suggests that the solution to the general problem may require
nothing more than making the following change to the
run 运行
method from Figure 7:
public void run() { 公共无效的run(){
while (frontIsClear()) {
if (rightIsClear()) {
fillPothole();
} }
move(); 移动();
} }
} }
However, as the bug symbol off to the side suggests, this program is not quite right. It 这
contains a logical flaw—the sort of error that programmers call a bug. On the other hand,
the particular bug in this example is relatively subtle and would be easy to miss, even if
you tested the program. For example, the program works correctly on the following
world, as shown in the following before-and-after diagrams:
Before 前
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
7 7
After 后
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
7 7
From this example, things look pretty good. If you ended your testing here, however,
you would never notice that the program fails if you change the world so that there is a
pothole on 7th Avenue. In this case, the before-and-after pictures look like this:
Before 前
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
7 7
After 后
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
7 7
23 23
Karel stops without filling the last pothole. In fact, if you watch the execution carefully,
Karel never even goes down into that last pothole to check whether it needs filling.
What's the problem here?
If you follow through the logic of the program carefully, you'll discover that the bug
lies in the loop within the
run 运行
method, which looks like this:
public void run() { 公共无效的run(){
while (frontIsClear()) {
if (rightIsClear()) {
fillPothole();
} }
move(); 移动();
} }
} }
As soon as Karel finishes filling the pothole on 6th Avenue, it executes the
move 移动
command and returns to the top of the
while 而
loop. 循环。 At that point, Karel is standing at the
corner of 7th Avenue and 2nd street, where it is blocked by the boundary wall. Because 由于
the 的
frontIsClear
test now fails, the
while 而
loop exits without checking the last segment
of the roadway.
The bug in this program is an example of a programming problem called a fencepost
error. The name comes from the fact that it takes one more fence post that you might
think to fence off a particular distance. How many fence posts, for example, do you need
to build a 100-foot fence if the posts are always positioned 10 feet apart? The answer is
11, as illustrated by the following diagram:
100 feet, 11 fenceposts
The situation in Karel's world has much the same structure. In order to fill potholes in a
street that is seven corners long, Karel has to check for seven potholes but only has to
move six times. Because Karel starts and finishes at an end of the roadway, it needs to
execute one fewer
move 移动
command than the number of corners it has to check.
Once you discover it, fixing this bug is actually quite easy. Before Karel stops at the
end of the roadway, all that the program has to do is make a special check for a pothole at
the final intersection, as shown in the program in Figure 8.
24 24
Figure 8. 图8。 Program to fill irregularly spaced potholes
/* / *
/* / *
* File: RoadRepairKarel.java
* --------------------------
* This version of the RoadRepairKarel class fills an
* arbitrary sequence of potholes in a roadway.
*/ * /
import stanford.karel.*;
public class RoadRepairKarel extends SuperKarel {
public void run() { 公共无效的run(){
while (frontIsClear()) {
checkForPothole();
move(); 移动();
} }
checkForPothole();
} }
/** / **
* Checks for a pothole immediately beneath Karel's current
* looking for a wall to the right. If a pothole exists,
* Karel calls fillPothole to repair it.
*/ * /
private void checkForPothole() {
if (rightIsClear()) {
fillPothole();
} }
} }
/** / **
* 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. This version of fillPothole checks to
* see if there is already a beeper present before putting
* a new one down.
*/ * /
private void fillPothole() {
turnRight(); turnRight();
move(); 移动();
if (noBeepersPresent()) {
putBeeper();
} }
turnAround();
move(); 移动();
turnRight(); turnRight();
} }
} }
Chapter 4 第四章
Stepwise Refinement
To a large extent, programming is the science of solving problems by computer. Because 由于
problems are often difficult, solutions—and the programs that implement those
solutions—can be difficult as well. In order to make it easier for you to develop those
solutions, you need to adopt a methodology and discipline that reduces the level of that
complexity to a manageable scale.
In the early years of programming, the concept of computing as a science was more or
less an experiment in wishful thinking. No one knew much about programming in those
days, and few thought of it as an engineering discipline in the conventional sense. As 随着
programming matured, however, such a discipline began to emerge. The cornerstone of
that discipline is the understanding that programming is done in a social environment in
which programmers must work together. If you go into industry, you will almost certainly
be one of many programmers working to develop a large program. That program,
moreover, is almost certain to live on and require maintenance beyond its originally
intended application. Someone will want the program to include some new feature or
work in some different way. When that occurs, a new team of programmers must go in
and make the necessary changes in the programs. If programs are written in an individual
style with little or no commonality, getting everyone to work together productively is
extremely difficult.
To combat this problem, programmers began to develop a set of programming
methodologies that are collectively called software engineering. Using good software
engineering skills not only makes it easier for other programmers to read and understand
your programs, but also makes it easier for you to write those programs in the first place.
One of the most important methodological advances to come out of software engineering
is the strategy of top-down design or stepwise refinement, which consists of solving
problems by starting with the problem as a whole. You break the whole problem down
into pieces, and then solve each piece, breaking those down further if necessary.
An exercise in stepwise refinement
To illustrate the concept of stepwise refinement, let's teach Karel to solve a new problem.
Imagine that Karel is now living in a world that looks something like this:
1 一
2 2
3 三
4 4
5 5
6 6
7 7
8 8
9 9
1 一
2 2
3 三
4 4
5 5
6 6
7 7
26 26
On each of the avenues, there is a tower of beepers of an unknown height, although
some avenues (such as 1st, 7th, and 9th in the sample world) may be empty. Karel's job
is to collect all the beepers in each of these towers, put them back down on the
easternmost corner of 1st Street, and then return to its starting position. Thus, when Karel
finishes its work in the example above, all 21 beepers currently in the towers should be
stacked on the corner of 9th Avenue and 1st Street, as follows:
1 一
2 2
3 三
4 4
5 5
6 6
7 7
8 8
9 9
1 一
2 2
3 三
4 4
5 5
6 6
7 7
21 21
The key to solving this problem is to decompose the program in the right way. This 这
task is more complex than the others you have seen, which makes choosing appropriate
subproblems more important to obtaining a successful solution.
The principle of top-down design
The key idea in stepwise refinement is that you should start the design of your program
from the top, which refers to the level of the program that is conceptually highest and
most abstract. At this level, the beeper tower problem is clearly divided into three
independent phases. First, Karel has to collect all the beepers. Second, Karel has to
deposit them on the last intersection. Third, Karel has to return to its home position. This 这
conceptual decomposition of the problem suggests that the
run 运行
method for this program
will have the following structure:
public void run() { 公共无效的run(){
collectAllBeepers();
dropAllBeepers();
returnHome();
} }
At this level, the problem is easy to understand. Of course, there are a few details left
over in the form of methods that you have not yet written. Even so, it is important to look
at each level of the decomposition and convince yourself that, as long as you believe that
the methods you are about to write will solve the subproblems correctly, you will then
have a solution to the problem as a whole.
Refining the first subproblem
Now that you have defined the structure for the program as a whole, it is time to move on
to the first subproblem, which consists of collecting all the beepers. This task is itself
more complicated than the simple problems from the preceding chapters. Collecting all
the beepers means that you have to pick up the beepers in every tower until you get to the
27 27
final corner. The fact that you need to repeat an operation for each tower suggests that
you need a
while 而
loop here.
But what does this
while 而
loop look like? First of all, you should think about the
conditional test. You want Karel to stop when it hits the wall at the end of the row. Thus, 因此,
you want Karel to keep going as long as the space in front is clear. Thus, you know that
the 的
collectAllBeepers
method will include a
while 而
loop that uses the
frontIsClear
test. 测试。 At each position, you want Karel to collect all the beepers in the tower beginning on
that corner. If you give that operation a name, which might be something like
collectOneTower
, you can go ahead and write a definition for the
collectAllBeepers
method even though you haven't yet filled in the details.
You do, however, have to be careful. The code for
collectAllBeepers
does not look
like this: 像这样:
private void collectAllBeepers {
while (frontIsClear()) {
collectOneTower();
move(); 移动();
} }
} }
This implementation is buggy for exactly the same reason that the first version of the
general 一般
RoadRepairKarel
from the previous chapter failed to do its job. There is a 有一
fencepost error in this version of the code, because Karel needs to test for the presence of
a beeper tower on the last avenue. The correct implementation is
private void collectAllBeepers {
while (frontIsClear()) {
collectOneTower();
move(); 移动();
} }
collectOneTower();
} }
Note that this method has precisely the same structure as the main program from the
RoadRepairKarel
program presented at the end of the last chapter. The only difference is
that this program calls
collectOneTower
where the other called
checkForPothole
. 。
These two programs are each examples of a general strategy that looks like this:
while (frontIsClear()) {
Perform some operation.
move(); 移动();
} }
Perform the same operation for the final corner.
You can use this strategy whenever you need to perform an operation on every corner as
you move along a path that ends at a wall. If you remember the general structure of this
strategy, you can use it whenever you encounter a problem that requires such an
operation. 运作。 Reusable strategies of this sort come up frequently in programming and are
referred to as programming idioms or patterns . The more patterns you know, the easier
it will be for you to find one that fits a particular type of problem.
Coding the next level
Even though the code for the
collectAllBeepers
method is itself complete, you can't
actually execute the program until you solve the
collectOneTower
subproblem. When 当
28 28
collectOneTower
is called, Karel is either standing at the base of a tower of beepers or
standing on an empty corner. In the former case, you need to collect the beepers in the
tower. 塔。 In the latter, you can simply move on. This situation sounds like an application for
the 的
if 如果
statement, in which you would write something like this:
if (beepersPresent()) {
collectActualTower();
} }
Before you add such a statement to the code, you should think about whether you need to
make this test. Often, programs can be made much simpler by observing that cases that at
first seem to be special can be treated in precisely the same way as the more general
situation. 情况。 In the current problem, what happens if you decide that there is a tower of
beepers on every avenue but that some of those towers are zero beepers high? Making use
of this insight simplifies the program because you no longer have to test whether there is
a tower on a particular avenue.
The 该
collectOneTower
method is still complex enough that an additional level of
decomposition is in order. To collect all the beepers in a tower, Karel needs to undertake
the following steps: 下列步骤:
1. 1。 Turn left to face the beepers in the tower.
2. 2。 Collect all the beepers in the tower, stopping when no more beepers are found.
3. 3。 Turn around to face back toward the bottom of the world.
4. 4。 Return to the wall that represents the ground.
5. 5。 Turn left to be ready to move to the next corner.
Once again, this outline provides a model for the
collectOneTower
method, which looks
like this: 像这样:
private void collectOneTower() {
turnLeft();
collectLineOfBeepers();
turnAround();
moveToWall();
turnLeft();
} }
Preconditions and postconditions
The 该
turnLeft turnLeft
commands at the beginning and end of the
collectOneTower
method are
both critical to the correctness of this program. When 当
collectOneTower
is called, Karel
is always somewhere on 1st Street facing east. When it completes its operation, the
program as a whole will work correctly only if Karel is again facing east at that same
corner. 角落。 Conditions that must be true before a method is called are referred to as
preconditions; conditions that must apply after the method finishes are known as
postconditions.
When you define a method, you will get into far less trouble if you write down exactly
what the pre- and postconditions are. Once you have done so, you then need to make sure
that the code you write always leaves the postconditions satisfied, assuming that the
preconditions were satisfied to begin with. For example, think about what happens if you
call 通话
collectOneTower
when Karel is on 1st Street facing east. The first 第一
turnLeft turnLeft
command leaves Karel facing north, which means that Karel is properly aligned with the
column of beepers representing the tower. The 该
collectLineOfBeepers
method—which
29 29
has yet to be written but nonetheless performs a task that you understand conceptually—
simply moves without turning. Thus, at the end of the call to
collectLineOfBeepers
, ,
Karel will still be facing north. The 该
turnAround
call therefore leaves Karel facing south.
Like 像
collectLineOfBeepers
, the 的
moveToWall
method does not involve any turns but
instead simply moves until it hits the boundary wall. Because Karel is facing south, this
boundary wall will be the one at the bottom of the screen, just below 1st Street. The final 最后
turnLeft turnLeft
command therefore leaves Karel on 1st Street facing east, which satisfies the
postcondition.
Finishing up
Although the hard work has been done, there are still several loose ends that need to be
resolved. 解决。 The main program calls two methods—
dropAllBeepers
and 和
returnHome
— -
that are as yet unwritten. Similarly, 同样,
collectOneTower
calls
collectLineOfBeepers
and 和
moveToWall
. 。 Fortunately, all four of these methods are simple enough to code without
any further decomposition, particularly if you use
moveToWall
in the definition of
returnHome
. 。 The complete implementation appears in Figure 9.
Figure 9. 图9。 Program to solve the collect towers of beepers
/* / *
* File: BeeperCollectingKarel.java
* --------------------------------
* The BeeperCollectingKarel class collects all the beepers
* in a series of vertical towers and deposits them at the
* rightmost corner on 1st Street.
*/ * /
import stanford.karel.*;
public class BeeperCollectingKarel extends SuperKarel {
/** / **
* Specifies the program entry point.
*/ * /
public void run() { 公共无效的run(){
collectAllBeepers();
dropAllBeepers();
returnHome();
} }
/** / **
* Collects the beepers from every tower by moving along 1st
* Street, calling collectOneTower at every corner. The 该
* postcondition for this method is that Karel is in the
* easternmost corner of 1st Street facing east.
*/ * /
private void collectAllBeepers() {
while (frontIsClear()) {
collectOneTower();
move(); 移动();
} }
collectOneTower();
} }
30 30
/** / **
* Collects the beepers in a single tower. When collectOneTower
* is called, Karel must be on 1st Street facing east. The 该
* postcondition for collectOneTower is that Karel must again
* be facing east on that same corner.
*/ * /
private void collectOneTower() {
turnLeft();
collectLineOfBeepers();
turnAround();
moveToWall();
turnLeft();
} }
/** / **
* Collects a consecutive line of beepers. The end of the beeper
* line is indicated by a corner that contains no beepers.
*/ * /
private void collectLineOfBeepers() {
while (beepersPresent()) {
pickBeeper();
if (frontIsClear()) {
move(); 移动();
} }
} }
} }
/** / **
* Drops all the beepers on the current corner.
*/ * /
private void dropAllBeepers() {
while (beepersInBag()) {
putBeeper();
} }
} }
/** / **
* Returns Karel to its initial position at the corner of 1st
* Avenue and 1st Street, facing east. The precondition for this
* method is that Karel must be facing east somewhere on 1st
* Street, which is true at the conclusion of collectAllBeepers.
*/ * /
private void returnHome() {
turnAround();
moveToWall();
turnAround();
} }
/** / **
* Moves Karel forward until it is blocked by a wall.
*/ * /
private void moveToWall()
{ {
while (frontIsClear()) {
move(); 移动();
} }
} }
} }
Chapter 5 第五章
Algorithms 算法
Although top-down design is a critical strategy for programming, it cannot be applied
mechanically without thinking about problem-solving strategies. Figuring out how to
solve a particular problem by computer generally requires considerable creativity. The 该
process of designing a solution strategy is traditionally called algorithmic design.
The word algorithm comes from the name of a ninth-century Persian mathematician,
Abu Ja'far Mohammed ibn Mûsâ al-Khowârizmî, who wrote an influential treatise on
mathematics. 数学。 Today, the notion of an algorithm has been formalized so that it refers to a
solution strategy that meets the following conditions:
• The strategy is expressed in a form that is clear and unambiguous.
• The steps in the strategy can be carried out.
• The strategy always terminates after a finite number of steps.
You will learn much more about algorithms as you continue your study of programming,
but it is useful to look at a few simple algorithms in Karel's world.
Solving a maze
As an example of algorithmic design, suppose that you wanted to teach Karel to escape
from a maze. In Karel's world, a maze might look like this:
1 一
2 2
3 三
4 4
5 5
6 6
1 一
2 2
3 三
4 4
5 5
6 6
The exit to the maze is marked by a beeper, so that Karel's job is to navigate the corridors
of the maze until it finds the beeper indicating the exit. The program, however, must be
general enough to solve any maze, and not just the one pictured here.
There are several strategies you could use for solving such a maze. When Theseus
needed to escape from the Labyrinth of Crete, he adopted—at the suggestion of King
Minos's daughter Ariadne, whom Theseus promptly abandoned on the next island he
reached—the strategy of unwinding a ball of string as he explored the maze. You could
devise a similar strategy for Karel, in which beepers serve the same function.
For most mazes, however, you can use a simpler strategy called the right-hand rule,
in which you begin by putting your right hand on the adjacent wall and then go through
the maze without ever taking your hand off the wall. Another way to express this strategy
is to proceed through the maze one step at a time, always taking the rightmost available
path. 路径。
32 32
Figure 10. 图10。 Program to solve a maze
/* / *
/* / *
* File: MazeRunningKarel.java
* ---------------------------
* This program extends Karel so that it can solve a maze
* using the right-hand rule.
*/ * /
import stanford.karel.*;
public class MazeRunningKarel extends SuperKarel {
public void run() { 公共无效的run(){
while (noBeepersPresent()) {
turnRight(); turnRight();
while (frontIsBlocked()) {
turnLeft();
} }
move(); 移动();
} }
} }
} }
You can easily write a Karel program to apply the right-hand rule. The program in
Figure 10, for example, expresses the algorithm for the right-hand rule in a particularly
compact form. You should work through the logic of this algorithm and convince
yourself that it indeed accomplishes the task. It is important to note that the code that
implements an algorithm may not be very complicated. Indeed, coming up with the right
algorithm often leads to extremely simple code.
Doubling the number of beepers
Another programming task that leads to interesting algorithmic choices is the problem of
getting Karel to double the number of beepers on a corner. For example, suppose that
Karel starts out in the world
1 一
2 2
3 三
4 4
5 5
1 一
2 2
3 三
4 4
where there are some number of beepers—in this case four—on the corner of 1st Street
and 2nd Avenue and an infinite number of beepers in Karel's beeper bag. The goal in this
problem is to write a method
doubleBeepers
that doubles the number of beepers on the
current square. Thus, if you execute the method
public void run() { 公共无效的run(){
move(); 移动();
doubleBeepers();
move(); 移动();
} }
33 33
on the world shown in the preceding diagram, the final state of the world should look like
this: 这样:
1 一
2 2
3 三
4 4
5 5
1 一
2 2
3 三
8 8
The program should be general enough to work for any number of beepers. For example, 例如,
if there had originally been 21 beepers on the corner of 1st Street and 2nd Avenue, the
program should end with 42 beepers on that corner.
Writing the
doubleBeepers
method is harder than it initially appears. Your first step is
to devise an algorithmic strategy to solve the problem. You can't start by picking up all
the beepers on the corner, because you would then have no way of telling how many
beepers to put down. As with most algorithms in Karel's world, you need to process the
beepers one at a time. You can pick one up from the corner, but you then have to keep
track somehow of the fact that you have to add two beepers to the result.
The easiest strategy to devise involves the use of a temporary storehouse on some
corner that is initially empty, such as the corner at 1st Street and 3rd Avenue. If every
time you pick up a beeper from the original pile on 2nd Avenue you put down two
beepers in the storehouse on 3rd Avenue, you will have twice the original number of
beepers when the first pile is exhausted. Thus, you can create the correct value in the
storehouse by calling the following method:
private void doubleIntoStorehouse() {
while (beepersPresent()) {
pickBeeper();
move(); 移动();
putBeeper();
putBeeper();
turnAround();
move(); 移动();
turnAround();
} }
} }
The precondition for this method is that Karel is standing on a corner containing a pile of
N beepers facing a corner with no beepers. The postcondition is that Karel winds up in its
original position with no beepers on that corner but 2 N beepers on the corner Karel is
facing.
This method does the interesting algorithmic work, but does not entirely satisfy the
constraints of the problem as stated because the final pile of beepers is not on the original
square. 广场。 To get it there, you need to implement a similar method that simply transfers the
pile back to the adjacent square. This method has almost exactly the same structure,
except that it deposits only one beeper for each one it collects. If you design a
transferBeepersBack
method to work with the same precondition as that used for
doubleIntoStorehouse
, it will look like this:
34 34
private void transferBeepersBack() {
while (beepersPresent()) {
pickBeeper();
move(); 移动();
putBeeper();
turnAround();
move(); 移动();
turnAround();
} }
} }
The 该
doubleBeepers
method itself then consists of the following code:
private void doubleBeepers() {
doubleIntoStorehouse();
move(); 移动();
turnAround();
transferBeepersBack();
move(); 移动();
turnAround();
} }
This strategy, however, is not the only one you might use. In many cases, there are
algorithms that work much better than the obvious ones, although they are often difficult
to discover. Many such algorithms depend on sophisticated programming techniques that
you will encounter later in your study of computer science. For example, the 例如,
doubleBeepers
problem can be solved quite easily if you use a technique called
recursion, which is simply the process of having a method call itself. The following以下
implementation of
doubleBeepers
gets the job done without needing a storehouse or any
moving around:
private void doubleBeepers() {
if (beepersPresent()) {
pickBeeper();
doubleBeepers();
putBeeper();
putBeeper();
} }
} }
Although it is fun to try and figure out what this program is doing, you shouldn't
worry at this point if you find it hard to understand. The point of showing this solution is
simply to demonstrate that there are many different algorithms for solving problems,
some of which can be very compact and efficient. As you study computer science, you
will learn a great deal more about algorithmic techniques and gain the skills you need to
write this type of program on your own.
Chapter 6 第6章
SuperKarel SuperKarel
As it comes from the factory, Karel is a bit on the boring side. The world—reflecting the
nature of hardware at the time Karel was invented—is entirely black and white.
Moreover, Karel always behaves in a strictly deterministic fashion. Each of these
limitations makes it harder to program Karel to perform exciting tasks. To make it easier
to write more interesting Karel programs, the
stanford.karel
package includes a
SuperKarel SuperKarel
that includes several new features. Those features are described in the
sections that follow.
The 该
turnRight turnRight
and 和
turnAround
methods 方法
As you already know from Chapter 2, the
SuperKarel SuperKarel
class includes definitions for
turnRight turnRight
and 和
turnAround
. 。 Although it certainly easy to define these methods on your
own, it is inconvenient to do so in every Karel program. Moreover, the 此外,
SuperKarel SuperKarel
implementations of these methods tie into the internal workings of Karel to implement
these operations more efficiently. If you use
turnRight turnRight
in a 在一
SuperKarel SuperKarel
subclass, your
program will make an immediate right turn and not go through the process of turning left
three times. 三次。
Using color
The 该
SuperKarel SuperKarel
class allows Karel to paint the corner on which it is standing using the
instruction 指令
paintCorner( color );
The value enclosed in the parentheses—which is called an argument in the terminology
of programming languages—may be any of the following:
BLACK 黑色
BLUE 蓝
CYAN
DARK_GRAY
GRAY
GREEN 绿色
LIGHT_GRAY
MAGENTA
ORANGE
PINK 粉红色
RED 红
WHITE 白
YELLOW 黄色
null 空
The 该
null 空
color value indicates that a corner has no color, which means that the little
cross in the center shows through. When you create a new Karel world, all corners
initially have
null 空
as their color.
SuperKarel SuperKarel
also understands a new condition
cornerColorIs( color )
, which is true if
the current corner has been painted with the specified color. For example, you could paint
the current corner red by executing the instruction
paintCorner(RED);
and later perform operations only on red corners by using the following
if 如果
statement: 声明:
if (cornerColorIs(RED)) {
Statements to execute only if the corner is painted red
} }
36 36
Random behavior
SuperKarel SuperKarel
also defines a new condition
random 随机
, which is true half the time, but in an
unpredictable way. For example, if you execute the statements
if (random()) {
paintCorner(YELLOW);
} else { 还有{}
paintCorner(MAGENTA);
} }
Karel will paint the current corner yellow or magenta, each with equal probability.
If you need greater control over how often Karel executes a random event, the
random 随机
condition takes an argument, which is the a number specifying the probability of the
condition returning true. As in statistics, probabilities are numbers between 0.0 and 1.0,
where 0.0 indicates that the condition will always be false and 1.0 indicating that it will
always be true. If, for example, you wanted to have Karel put down a beeper 25 percent
of the time, you could use the following code:
if (random(0.25)) {
putBeeper();
} }
Logical operations
As you write more sophisticated Karel programs, you will discover that it is sometimes
difficult to express certain conditional tests whose English equivalents include
conjunctions like and and or . As an example, try to write a Karel
while 而
statement that
moves Karel forward until it is either blocked by a wall or encounters a beeper. To make 为了使
it easier to write interesting programs, the Karel language allows you to use the following
logical operators, which are actually part of Java and not a
SuperKarel SuperKarel
extension:
&&
Equivalent to the English word and.
|| | |
Equivalent to the English word or (in the formal sense of either or both ).
! !
Equivalent to the English word not.
With these operators, it is easy to write the
while 而
statement suggested earlier in this
section, because you can combine the conditions into a single test:
while (frontIsClear() && noBeepersPresent()) {
move(); 移动();
} }
The fact that these operators work in Karel programs reveals a notable fact about the
way such programs are implemented. The Karel programs that you write turn out to be
simply Java programs in disguise. There is no separate Karel language; everything that
you've seen in Karel is actually just part of standard Java or implemented using standard
Java as part of one of the classes in the
stanford.karel
package. While this strategy
makes the Karel simulator much easier to implement and means that you will be using the
same tools that you will use throughout the quarter, it does have a downside. The logical 逻辑
operators 经营者
&&
, ,
|| | |
, and ,和
! !
are not the only pieces of standard Java that you might
incorporate into a Karel program. Given the way Karel is implemented, you could include
anything from standard Java in a Karel program, and the Java compiler would not
complain at all. Doing so, however, defeats the purpose of Karel, which is intended to
provide a simple platform for learning programming. So even though the Java compiler
won't complain if you use more advanced Java structures, your section leader will.
Acceptable Karel programs must limit themselves to the features described in this book.
Appendix A 附录A
Karel Reference Card
This appendix defines the structure of the Karel programming language on a single page.
Built-in Karel commands:
move(); 移动();
turnLeft();
putBeeper();
pickBeeper();
Karel program structure:
/* / *
* Comments may be included anywhere in
* the program between a slash-star and
* the corresponding star-slash characters.
*/ * /
import stanford.karel.*;
/* Definition of the new class */
public class name extends Karel {
public void run() { 公共无效的run(){
statements in the body of the method
} }
definitions of private methods
} }
Karel condition names:
frontIsClear()
frontIsBlocked()
leftIsClear()
leftIsBlocked()
rightIsClear()
rightIsBlocked()
beepersPresent() noBeepersPresent()
beepersInBag()
noBeepersInBag()
facingNorth()
notFacingNorth()
facingEast()
notFacingEast()
facingSouth()
notFacingSouth()
facingWest()
notFacingWest()
Conditional statements:
if ( condition ) {
statements executed if condition is true
} }
if ( condition ) {
statements executed if condition is true
} else { 还有{}
statements executed if condition is false
} }
Iterative statements:
for (int i = 0; i < count ; i++) {
statements to be repeated
} }
while ( condition ) {
statements to be repeated
} }
Method definition:
private void name () {
statements in the method body
} }
New commands in the
SuperKarel SuperKarel
class:
turnRight(); turnRight();
turnAround();
paintCorner( color );
New conditions in the
SuperKarel SuperKarel
class:
random()
random( p )
cornerColorIs( color )















英语原文:
LOGO project at MIT, Rich designed an introductory programming environment in
更好的翻译建议