Pascal 编程/表达式和分支
在本章中,您将学习
- 区分语句和表达式,以及
- 如何编写分支程序。
在我们了解“表达式”之前,让我们更精确地定义“语句”:一条语句告诉计算机去改变某件事。所有语句以某种方式改变程序状态。程序状态指的是一整套个体状态的集合,包括但不限于
- 变量具有的值,或者
- 通常程序指定的内存内容,还有
- (隐式) 当前正在处理的语句。
最后这个指标存储在一个不可见的变量中,叫做程序计数器。PC总是指向当前正在处理的语句。想象一下用你的手指指向源代码行(或者更准确地说是语句): “我们在这里!” 在一条语句成功执行之后,PC向前移动,结果它指向下一条语句。[fn 1] PC不能直接改变,只能隐式改变。在本章中,我们将学习如何做到这一点。
语句可以分为两类:基本语句和复合语句。基本语句是高级编程语言的基本构建块。在 Pascal 中,它们是:[fn 2][fn 3]
- 赋值 (
:=
),以及 - 过程[fn 4]调用(例如
readLn(x)
和writeLn('Hi!')
)。
“复合”语句是
- 序列(用
begin
和end
括起来), - 分支,以及
- 循环。
与许多其他编程语言不同,在 Pascal 中,分号 ;
分隔两条语句。许多编程语言使用某个符号来终止一条语句,例如分号。然而,Pascal 认识到,额外的符号不应该成为语句的一部分,以使其成为一条实际的语句。第二章中的 helloWorld
程序可以在 writeLn(…)
之后不使用分号,因为后面没有语句。
program helloWorld(output);
begin
writeLn('Hello world!')
end.
然而,我们建议你在那里加上分号,即使它不是必需的。在本章后面,你将学习一个可能(并不总是)不想加分号的地方。
虽然分号不会终止一条语句,但程序头部、常量定义、变量声明以及其他一些语言结构是由这个符号终止的。你不能在这些地方省略分号。
与语句不同,表达式不会改变程序状态。它们是瞬时值,可以作为语句的一部分使用。表达式的例子有
42
,'D’oh!'
,或者x
(其中x
是先前声明的变量的名称)。
每个表达式都具有类型:当表达式被求值时,它会生成一个特定数据类型的的值。表达式 42
的数据类型是 integer
,'D’oh!'
是一种“字符串类型”,而仅由变量名称组成的表达式,例如 x
,则会生成该变量的数据类型。由于表达式的类型非常重要,所以表达式是以它们的类型命名的。表达式 true
是一个布尔表达式,false
也是。
表达式出现在很多地方
- 在赋值语句 (
:=
) 中,你在RHS上写一个表达式。这个表达式必须与LHS上变量的数据类型相同。[fn 5] 赋值通过将表达式的瞬时值存储到变量的内存块中来使其“永久化”。 - 过程调用的参数列表由表达式组成。为了调用一个过程,所有参数都必须存储在内存中。可以将过程调用理解为在实际调用过程之前对不可见变量的一系列赋值。因此
writeLn(output, 'Hi!')
可以理解为- destination 变成
output
- “第一个参数” 变成
'Hi!'
- 用不可见的“变量”destination 和 “第一个参数”调用过程
writeLn
- destination 变成
- 在常量定义中,RHS也是一个表达式,尽管它必须是常量(因此得名)。例如,你不能在该表达式中使用变量。
表达式的强大之处在于它能够与其他表达式链接。这是通过使用叫做运算符的特殊符号来实现的。在上一章中,我们已经看到一个运算符,即等于运算符 =
。现在我们可以分解这样的表达式
response = 'y' {
│ └──────┰─────┘ ┃ └──────┰──────┘ │
│ sub-expression operator sub-expression │
│ │
└─────────────────────┰─────────────────────┘
expression }
如你所见,表达式可以是更大表达式的组成部分。子表达式通过运算符符号 =
链接在一起。通过运算符符号链接或关联的子表达式也称为操作数。
通过操作符连接表达式会“创建”一个新的表达式,它拥有自己的数据类型。虽然上面的例子中,response
和 'y'
都是 char
-表达式,但整个表达式的总体数据类型为 Boolean
,因为连接操作符是相等比较。相等比较会产生一个布尔表达式。以下表格列出了我们可以使用已知知识使用的关系运算符。
名称 | 源代码符号 |
---|---|
=, 等于 | =
|
≠, 不等于 | <>
|
<, 小于 | <
|
>, 大于 | >
|
≤, 小于等于 | <=
|
≥, 大于等于 | >=
|
使用这些符号会产生布尔表达式。表达式的值将根据操作符的定义,是 true
或 false
。
所有这些关系运算符都要求两边的操作数具有相同的数据类型。[fn 6] 尽管我们可以说 '$' = 1337
是错误的,这意味着它应该评估为 false
,但它仍然是非法的,因为 '$'
是一个 char
‑表达式,而 1337
是一个 integer
‑表达式。Pascal 禁止你比较数据类型不同的东西/对象。所以,我想,你不能比较苹果和橘子。(注意,一些转换例程将允许你进行一些直接不允许的比较,但需要绕道。在下一章中,我们将看到其中一些。)
施加的数据类型限制是为了一个好的目的。你可能会觉得Pascal 对所有这些数据类型太挑剔了,但这种所谓的强类型安全性实际上是一种优势。它可以防止你,作为程序员,犯无意的编程错误。 |
表达式也用于计算,你使用的机器并非无缘无故被称为“计算机”。在标准Pascal中,你可以对两个数字进行加、减、乘和除,即integer
‑ 和 real
‑表达式以及它们的任何组合。适用于所有组合的符号是
名称 | 源代码符号 |
---|---|
+, 加 | +
|
−, 减 | -
|
×, 乘 | *
|
除法运算已被省略,因为它比较棘手,将在后面的章节中解释。
如果至少有一个操作数是real ‑表达式,则整个表达式将是real 类型,即使精确的值可以用integer 表示。 |
注意,与数学不同,两个“操作数”之间没有隐含的乘法:你总是需要显式地写出“乘法”,即星号*
。
运算符符号+
和 -
也可以只与一个数字表达式一起出现。它表示正负号,或者更正式地说,分别表示符号标识或符号反转。
就像在数学中一样,运算符具有一定的“力量”与之相关,在CS中,我们称之为运算符优先级。你可能还记得在小学或中学、学校或家庭教育中,PEMDAS 的首字母缩略词:它是记忆术语,代表了
- 括号
- 指数
- 乘法/除法
- 加法/减法
它为我们在数学中评估算术表达式的正确顺序提供了指导。幸运的是,Pascal 的运算符优先级完全相同,虽然严格来说,它并没有由“PEMDAS”这个词定义。[fn 7]
标准Pascal没有定义任何指数/幂运算符,因此,例如, 必须写成 x * x ,不能再简化了。但是,扩展的Pascal标准定义了pow 运算符。 |
你可能已经猜到了,运算符优先级可以通过使用括号在每个表达式中被覆盖:为了评估5 * (x + 7)
,子表达式x + 7
首先被评估,然后该值乘以 5
,即使乘法通常优先于加法或减法。
分支是复杂的语句。到目前为止,我们编写的程序都是线性的:它们从顶部开始,计算机(理想情况下)逐行执行它们,直到最后的end.
。分支允许你选择不同的路径,就像在十字路口一样:“我向左转还是向右转?” 处理程序“向下”的总体趋势依然存在,但(原则上)存在选择。
让我们回顾上一章的程序iceCream
。条件语句已突出显示
program iceCream(input, output);
var
response: char;
begin
writeLn('Do you like ice cream?');
writeLn('Type “y” for “yes” (“Yummy!”) and “n” for “no”.');
writeLn('Confirm your selection by hitting Enter.');
readLn(input, response);
if response = 'y' then
begin
writeLn('Awesome!');
end;
end.
现在我们可以说response = 'y'
是一个布尔表达式。单词if
和 then
是我们称为条件语句的语言结构的一部分。在then
之后,是一个语句,在本例中是一个复杂语句:begin … end
是一个序列,被认为是一个语句。
如果你记得或可以从源代码中推断出来,begin … end
之间的语句,writeLn('Awesome!')
只有在表达式response = 'y'
评估为true
时才会执行。否则,它将被跳过,就像什么都没有一样。
由于这种二元性(是/否,执行代码或跳过它),if
和 then
之间的表达式必须是布尔表达式。你不能写 if 1 * 1 then …
,因为 1 * 1
是一个 integer
-表达式。计算机无法根据一个 integer
-表达式来决定是否应该选择一条路径。
让我们扩展程序iceCream
,如果用户说不喜欢冰淇淋,就给出另一个响应。我们可以用另一个if
‑语句来实现,但对于这种情况,有一个更聪明的解决方案。
if response = 'y' then
begin
writeLn('Awesome!');
end
else
begin
writeLn('That’s a pity!');
end;
突出显示的备选方案,即 else
分支,仅在提供的布尔表达式计算结果为 false
时执行。无论哪种情况,无论 then
分支还是 else
分支被执行,程序执行将在 else
语句之后恢复(在本例中是在最后一行中的 end;
之后)。
在 end 之后没有分号,在 else 之前。分号用于分隔语句,但是整个结构 if … then … else … 是一个(复杂)语句。不允许使用分号来分割语句的一部分。注意,在某些情况下,您可以在此位置放置分号。我们将在后面的高级章节中详细介绍。 |
分支和(即将解释的)循环是根据数据、表达式以及因此响应用户输入的唯一方法来修改程序计数器 (PC),即指向当前执行语句的“手指”。没有它们,您的程序将是静态的,并且会一遍又一遍地做同样的事情,所以很无聊。利用分支和循环将使您的程序对给定输入更加响应。
char
表达式?所有?没有?'0'
到 '9'
,'A'
到 'Z'
以及 'a'
到 'z'
按您从英文字母表中熟悉的顺序排序,或者(关于数字)按它们的数值升序排序。因此,您可以进行 'A' <= 'F'
(它将计算为 true
)之类的比较。
注释
- ↑ 本段有意使用不精确的术语来保持简单。事实上,PC 是一个处理器寄存器(例如
%eip
,扩展指令指针),并指向下一个指令(而不是当前语句)。有关更多详细信息,请参见主题:汇编语言。 - ↑ 跳转 (
goto
) 已有意禁止进入附录,并且在此处不予介绍,但goto
也是一个基本语句。 - ↑ 异常扩展还将
raise
定义为一个基本语句。 - ↑ 更准确地说:
procedure
调用。 - ↑ 或者,一种“兼容”数据类型,例如,
integer
表达式可以存储到数据类型为real
的变量中,反之则不行。随着我们不断学习,我们将了解有关“兼容”类型的更多信息。 - ↑ 在关于集合的章节中,我们将扩展此语句。
- ↑ 要阅读技术定义,请参阅 ISO 标准 7185 中的 § 表达式,子部分 1“一般”。