Pascal 编程/数组
数组是用于自定义数据类型的结构概念。它将相同数据类型的元素分组在一起。如果您处理大量相同数据类型的数据,您将经常使用数组。
一般来说,数组是有限的且排列好的对象集合,所有对象都具有相同的数据类型,称为基本类型或组件类型。[1] 数组至少有一个离散的、有界的维度,连续地枚举所有其对象。每个对象都可以通过一个或多个标量值(称为索引)沿这些维度唯一标识。
在 Pascal 中,数组数据类型使用保留字 array
结合辅助保留字 of
来声明,后跟数组的基本类型
program arrayDemo(output);
type
dimension = 1..5;
integerList = array[dimension] of integer;
保留字 array
后面跟着一个非空的用逗号分隔的维度列表,用方括号括起来。[fn 1] 所有数组的维度必须是序数数据类型,但基本类型可以是任何类型。如果数组只有一个维度,比如上面的数组,我们也可以称它为列表。
如上所述,数据类型 integerList
的变量包含五个独立的 integer
值。访问它们遵循特定的方案
var
powerN: integerList;
begin
powerN[1] := 5;
powerN[2] := 25;
powerN[3] := 125;
powerN[4] := 625;
powerN[5] := 3125;
writeLn(powerN[3]);
end.
这个 program
将打印 125
,因为它是 powerN
的值,其索引值为 3
。数组就像一系列“桶”,每个桶都包含一个基本数据类型的值。每个桶都可以通过根据维度规范的值来标识。当引用其中一个桶时,我们必须命名该组,即变量名(在本例中为 powerN
),以及用方括号括起来的适当索引。
字符列表通常在 I/O 和操作方面具有并曾经具有特殊支持。本节主要是关于理解,因为在下一章中,我们将了解一个更复杂的数据类型,称为 string
。
可以使用赋值语句将字符串字面量分配给 array[…] of char
变量,因此,您可以使用
program stringAssignmentDemo;
type
fileReferenceDimension = 1..4;
fileReference = array[fileReferenceDimension] of char;
var
currentFileReference: fileReference;
begin
currentFileReference[1] := 'A';
currentFileReference[2] := 'Z';
currentFileReference[3] := '0';
currentFileReference[4] := '9';
您可以简单地写
currentFileReference := 'AZ09';
end.
请注意,您不再需要指定任何索引。您正在引用赋值符号(:=
)LHS 上的整个数组变量。这仅适用于覆盖整个数组的值。确实有一些扩展允许您仅覆盖 char
数组的一部分,但关于这些将在下一章中详细介绍。
大多数将字符串字面量分配给 char
数组的实现将填充给定的字符串字面量,如果它小于变量的容量,则使用不重要的 char
值。填充字符串意味着用其他字符填充剩余的位置以满足特定的大小。GPC 使用空格字符(' '
),而FPC 使用 char
值,其 ord
数值为零(#0
)。
虽然没有标准化,[fn 2] read
/readLn
和 write
/writeLn
通常支持写入和读取 array[…] of char
变量。
program interactiveGreeting(input, output);
type
nameCharacterIndex = 1..20;
name = array[nameCharacterIndex] of char;
var
host, user: name;
begin
host := 'linuxnotebook'; { in lieu of getHostName }
writeLn('Hello! What is your name?');
readLn(user);
write('Hello, ', user, '. ');
writeLn('My name is ', host, '.');
end.
Hello! What is your name?
Aïssata
Hello, Aïssata. My name is linuxnotebook.
user
的最大容量。用户输入已突出显示,并且 program
是使用 FPC 编译的。这是因为 text
文件,如标准文件 input
和 output
,被理解为无限的 char
值序列。[fn 3] 由于我们的 array[…] of char
也是有限的 char
值序列,因此单个值可以非常直接地复制到和从 text
文件,不需要任何类型的转换。
与其他数组不同,array[…] of char
变量可以使用 =
和 <>
进行比较。
if user <> host then
begin
write('Hello, ', user, '. ');
writeLn('My name is ', host, '.');
end
else
begin
writeLn('No. That is my name.');
end;
这种比较只对相同的数据类型有效。它是一种基本的逐字符比较。如果任何一个数组更长或更短,则 =
比较将始终失败,因为并非所有字符都可以相互比较。相应地,<>
比较对于长度不同的 array[…] of char
值将始终成功。
注意,EP 标准还定义了 EQ
和 NE
函数,以及更多其他函数。与 =
和 <>
的区别在于,空白填充(即 FPC 中的 #0
或 GPC 中的 ' '
)在 EQ
和 NE
中没有意义。因此,这意味着使用这些函数,您可以比较字符串和 char
数组,无论其各自的最大容量如何,仍然可以获得自然预期的结果。另一方面,=
和 <>
比较会查看内存的内部表示。
数组的基本类型可以是任何数据类型,[fn 4] 甚至是另一个数组。如果我们想声明一个“数组的数组”,有一个简短的语法:
program matrixDemo(output);
const
columnMinimum = -30;
columnMaximum = 30;
rowMaximum = 10;
rowMinimum = -10;
type
columnIndex = columnMinimum..columnMaximum;
rowIndex = rowMinimum..rowMaximum;
plot = array[rowIndex, columnIndex] of char;
这已经在上面描述了 描述。最后一行与以下相同:
plot = array[rowIndex] of array[columnIndex] of char;
它可以扩展为两个独立的声明,使我们能够“重复使用” “内部” 数组数据类型
row = array[columnIndex] of char;
plot = array[rowIndex] of row;
注意,在后一种情况下,plot
使用 row
作为基本类型,它本身就是一个数组。然而,在简短的符号中,我们指定 char
作为基本类型,而不是 row
,而是其基本类型。
当一个数组被声明为包含另一个数组时,也有一个简短的符号来访问单个数组项。
var
curve: plot;
x: columnIndex;
y: rowIndex;
v: integer;
begin
// initialize
for y := rowMinimum to rowMaximum do
begin
for x := columnMinimum to columnMaximum do
begin
curve[y, x] := ' ';
end;
end;
// graph something
for x := columnMinimum to columnMaximum do
begin
v := abs(x) - rowMaximum;
if (v >= rowMinimum) and (v <= rowMaximum) then
begin
curve[v, x] := '*';
end;
end;
这对应于数组的声明。务必确保您指定的索引确实是有效的。在后一个循环中,if
分支会检查这一点。尝试访问不存在的数组值,即通过提供非法索引,可能会导致程序崩溃,或者更糟糕的是,无法检测到错误,从而导致难以发现的错误。
使用 GPC 编译的程序将以以下方式终止:./a.out: value out of range (error #300 at 402a76)
‑Cr 命令行开关,或者在源代码中放置编译器指令注释:{$rangeChecks on}
另请参阅 第“枚举”章,小节“限制”。 |
注意,x
和 y
的“不寻常”顺序是为了方便绘制直立的图形。
// print graph, note reverse `downto` direction
for y := rowMaximum downto rowMinimum do
begin
writeLn(curve[y]);
end;
end.
这意味着,仍然可以将整个“子”数组作为一个整体引用。不必写出数组值的所有维度,只要有意义就行。
具有正好两个维度的数组数据类型也称为矩阵,单数为矩阵。
在数学中,一个矩阵不必一定是同构的,但可以包含不同的“数据类型”。 |
如第一章中介绍的,数据类型 real
是 Pascal 编程语言的一部分。它用于存储整数,并可选择包含小数部分。
为了区分 integer
字面量和 real
字面量,在源代码中指定 real
值(以及对于 read
/readLn
)略有不同。以下源代码片段显示了一些 real
字面量
program realDemo;
var
x: real;
begin
x := 1.61803398;
x := 1E9;
x := 500e-12
x := -1.7320508;
x := +0.0;
end.
总结规则
- 一个
real
值字面量始终包含一个.
作为基数标记,或者一个E
/e
来分隔指数( in ),或者两者都有。 - 在
.
(如果有)之前至少有一个西阿拉伯数字,之后也有一个西阿拉伯数字。 - 整个数字和指数之前都有符号,但是正符号是可选的。
0.0 接受符号,但实际上(在数学上)是一个无符号数。 -0.0 和 +0.0 表示相同的值。 |
和以往一样,所有数字值都不能包含空格。
在有效使用real
数据类型时,您需要了解它的一些限制。首先,我们要再次强调一个在介绍数据类型时提到的问题:在计算中,real
变量只能存储有理数 (ℚ) 的一个子集。[2] 这意味着,例如,您无法存储(数学意义上的)实数 (ℝ) 。这个数字是一个无理数(即不是有理数)。如果您无法将一个数字写成一个有限的real
文本,那么在使用有限内存的系统中,例如计算机系统,无法存储它。
幸运的是,在 EP 中,三个常量可以帮助您使用real
值。 minReal
是最小的正real
值。它与常量maxReal
一起,保证了 中的所有算术运算都会产生“合理的”近似值。[3] 并没有明确规定“合理”近似值的具体含义,因此它可能意味着,例如,maxReal - 1
作为“近似值”会产生 maxReal
。[fn 5] 此外,real
变量可能存储大于maxReal
的值。
epsReal
是“epsilon real
”的缩写。希腊字母 ε(epsilon)在数学中通常表示一个无穷小的(正)值,但不为零。根据 ISO 标准 10206(“扩展 Pascal”),epsReal
是从大于1.0
的最小值中减去 1.0
的结果。[3] 在这个值和 1.0
之间,没有其他值可以表示,因此 epsReal
代表可用的最高精度,但仅限于该点。[fn 6] real
数据类型的大多数实现将显示出显著变化的精度。最值得注意的是,符合 IEEE 标准 754 格式的 real
数据类型实现的精度会向极端值衰减,当接近(并超过)-maxReal
和 maxReal
时。因此,通常情况下,您使用,如果有的话,一个合理的 epsReal
倍数,以适应给定的情况。
Pascal 的强类型系统阻止您将real
值分配给integer
变量。real
值可能包含integer
变量无法存储的小数部分。
Pascal 作为语言的一部分,定义了两个标准函数,以一种定义明确的方式解决这个问题。
- 函数
trunc
,是“截断”的缩写,它会简单地丢弃任何小数部分,并作为integer
返回整数部分。因此,这实际上是将一个数字向零取整。trunc(-1.999)
将返回-1
的值。 - 如果您觉得这“不自然”,
round
函数会将real
数字四舍五入到最接近的integer
值。round(x)
(关于结果值)等效于trunc(x + 0.5)
(对于非负值),并等效于trunc(x - 0.5)
(对于负x
值)。[fn 7] 换句话说,这就是您可能在小学或家庭教育中学习到的第一个取整方法。它通常被称为“商业取整”。
如果不存在满足函数各自定义的integer
值,这两个函数都将失败。
如果您想(明确地)将一个integer
值“转换”为一个real
值,没有函数可以实现。相反,人们使用算术中性运算
integerValue * 1.0
(使用乘法中性元素),或integerValue + 0.0
(使用加法中性元素)
这些表达式利用了这样一个事实,正如之前在关于表达式的一章中作为旁注提到的那样,表达式的整体数据类型将在涉及到一个real
值时变为real
。[4]
不保证所有可能的integer 值都可以存储为real 变量。[fn 8] 这主要涉及非小值,但重要的是要理解,integer 数据类型是存储整数、整数值的最佳选择。 |
默认情况下,write
/writeLn
使用“科学计数法”打印real
值。
real
值常量 pi
。它被许多编译器采用。
program printPi(output);
begin
writeLn(pi);
end.
3.141592653589793e+00
- 符号,其中正号用空格代替
- 一位数字
- 一个点
- 一个正数的十进制后数字
- 一个
E
(大写或小写) - 指数的符号,但这次始终写正号,零指数前也加正号
- 指数值,具有固定的最小宽度(此处为 2)和前导零
虽然这种样式非常通用,但对于某些读者来说也可能不寻常。特别是 E
表示法现在相当古老,通常只在袖珍计算器上看到,即缺少足够显示空间的设备。
幸运的是,write
/writeLn
允许我们自定义显示样式。
real
参数也是合法的,但这也会显示更多数字
program printPiDigits(output);
begin
writeLn(pi:40);
end.
3.141592653589793238512808959406186e+00
40
指的是整个宽度,包括符号、基数标记、e
和指数表示。对于 real
类型的值(仅对于 real
值),过程 write
/writeLn
接受另一个冒号分隔的格式说明符。第二个数字确定十进制后数字(精确)的数量,“小数部分”。提供两个格式说明符也会禁用科学计数法。所有 real
值都使用常规位置表示法打印。这可能意味着对于诸如 1e100
的“大”数字,打印一个后跟一百个零(仅针对整数部分)。
program realFormat(output);
var
x: real;
begin
x := 248e9 + 500e-6;
writeLn(x:32:8);
end.
248000000000.00048828
.
和负数的情况下的 -
,是 32 个字符。在 .
之后有 8 个数字。请记住精确的数字,尤其是小数部分,可能会有所不同。在某些地区和语言中,习惯上使用 ,
(逗号)或其他字符而不是点作为基数标记。Pascal 的内置 write
/writeLn
过程,另一方面,始终会打印一个点,并且为此事 – read
/readLn
始终只接受点作为基数标记。然而,所有当前的 Pascal 编程套件,Delphi、FPC 和 GPC 都提供适当的实用程序来克服此限制。有关更多详细信息,请参阅其手册。此问题不应阻止我们继续学习 Pascal。
write
/writeLn
(以及在 EP 中的 writeStr
)生成的将相对于最后一个打印的数字进行舍入。
program roundedWriteDemo(output);
begin
writeLn(3.75:4:1);
end.
3.8
real
的限制 产生的近似值区分开来。经验证实,用于此演示的计算机确实可以精确存储指定的数值。您在此特定情况下看到的舍入不是由于任何技术原因。比较
[edit | edit source]首先,所有(算术)比较运算符都适用于 real
值。然而,运算符 =
和 <>
处理起来尤其棘手。
在大多数应用程序中,在检查相等性或不等性时,您不将 real
值相互比较。问题是,诸如 ⅓ 之类的数字无法用有限的一系列二进制数字精确存储,只能近似,但对于 ⅓ 来说并非只有一个有效的近似值,而是许多合法的近似值。但是,=
和 <>
运算符进行比较 – 可以这么说 – 特定位模式。这通常不是期望的(对于无法精确表示的值)。相反,您希望确保要比较的值在某个范围内,例如
function equal(x, y, delta: real): Boolean;
begin
equal := abs(x - y) <= delta;
end;
Delphi 和 FPC 的标准 RTS 将函数 sameValue
作为 math
单元 的一部分提供。您不希望编写其他人已经编写的东西,即使用这些资源。
除法
[edit | edit source]既然我们知道用于存储(有理数的子集)的数据类型,在 Pascal 中称为 real
,我们就可以执行并使用另一个算术运算的结果:除法。
风格
[edit | edit source]Pascal 定义了两个不同的除法运算符
div
运算符执行整数除法,并丢弃(如果适用)任何余数。表达式的结果数据类型始终为integer
。div
运算符仅在两个操作数都是integer
表达式时才有效。- 可能更熟悉的运算符
/
(斜杠),也将 LHS 数字(被除数)除以 RHS 数字(除数),但/
除法始终产生real
类型的值。[4] 即使小数部分为零也是如此。“余数”对于/
运算不存在。
div
运算可以用 /
表示
divisor div dividend = trunc(divisor / dividend)
但是,这只是一个语义等效物,[fn 9] 它不是实际计算的方式。原因是,/
运算符将首先将两个操作数转换为 real
值,并且由于,如上所述,并非所有 integer
值都能精确地表示为 real
值,这将产生可能遭受舍入误差的结果。另一方面,div
运算符是一个纯 integer
运算符,并使用“整数精度”,这意味着实际上计算 div
结果时没有舍入参与。
除数限制
[edit | edit source]注意,由于没有普遍接受的零除定义,零除算式是非法的,会导致程序中止。如果除数不是常数,而是依赖于运行时数据(如从用户输入读取的变量),则应该在进行除法之前检查它是否为非零值。
if divisor <> 0 then
begin
x := dividend / divisor;
// ...
end
else
begin
writeLn('Error: zero divisor encountered when calculating rainbow');
end;
或者,您可以声明不包含零的数据类型,这样任何对零值的赋值都会被检测到。
type
natural = 1..maxInt;
var
divisor: natural;
begin
write('Enter a positive integer: ');
readLn(divisor); // entering a non-positive value will fail
一些 Pascal 方言引入了“异常”的概念,可以用来识别此类问题。异常可能在本书“扩展”部分再次提及。
数组作为参数
[edit | edit source]数组可以通过一个简单的赋值语句 dataOut := dataIn;
进行复制。但是,正如 Pascal 的强类型安全所要求的那样,两个数组必须是赋值兼容的:这意味着它们的基类型和维度规格必须相同。[fn 10]
由于调用例程涉及隐式赋值,如果整个程序只能使用一种数组类型,那么编写处理大量不同情况的通用代码将几乎不可能。为了缓解这种情况,一致数组类型参数允许编写接受不同数组维度长度的例程。数组维度数据类型仍然必须匹配。
让我们看一个使用此功能的示例程序。
program tableDemo(output);
procedure printTableRows(data: array[minimum..maximum: integer] of integer);
var
i: integer; // or in EP `i: type of minimum;` [preferred alternative]
begin
for i := minimum to maximum do
begin
writeLn(i:20, ' | ', data[i]:20);
end;
end;
一致数组参数看起来与常规数组变量声明非常相似,但维度的指定方式不同。通常,在声明新数组时,您会提供常量值作为维度限制。但是,由于我们不需要常量,因此我们为任何数组的实际维度限制命名占位符标识符 printTableRows
将接收。在本例中,它们被命名为 minimum
和 maximum
,通过中间的 ..
连接,表示一个范围。
minimum
和 maximum
在 printTableRows
的定义中成为变量,但您不允许为它们赋值。[fn 11] 您不允许声明与数组边界变量具有相同名称的新标识符。
在 Pascal 中,所有常量隐式地具有一个明确可确定的数据类型。由于我们的数组限制标识符实际上是变量,因此它们需要数据类型。: integer
表示 minimum
和 maximum
都有数据类型 integer
。
在一致数组参数中,嵌套数组的简短表示法使用 ; 来分隔多个维度,因此类似于常规参数列表。 |
一旦我们声明并定义了 printTableRows
,我们就可以对许多不同大小的数组使用它。
var
table: array[0..3] of integer;
nein: array[9..9] of integer;
begin
table[0] := 1;
table[1] := 2;
table[2] := 4;
table[3] := 8;
printTableRows(table);
nein[9] := 9;
printTableRows(nein);
end.
Delphi 和 FPC(截至 2020 年发布的 3.2.0 版本)不支持一致数组参数,但 GPC 支持。
对数
[edit | edit source]特殊支持
[edit | edit source]在 21 世纪之前,对数表和计算尺在手工计算中被广泛使用,以至于导致 Pascal 中包含了两个基本函数。
- 函数
exp
将数字以 (欧拉常数)为底进行指数运算。exp(x)
的值等效于数学术语 . - 函数
ln
(“logaritmus naturalis”的缩写)求取正数的自然对数。“自然”是指 。
这两个函数始终返回 real
值。
介绍
[edit | edit source]由于并非所有课程都教授对数的使用,或者您可能只是需要复习一下,这里简要介绍一下:对数的基本思想是所有运算都提升一个级别。
对数级别 | |||
---|---|---|---|
真实级别 |
在提升的层面上,许多操作变得更简单,尤其是当数字很大时。例如,如果你实际上要取两个数字的乘积,那么你可以执行一个相当简单的 *加法*。为此,你需要将所有操作数向上提升一级:这可以通过取对数来完成。特别注意 : 在对数层面上, 是一个非“对数化”的因子,你只需要取 的对数。
完成后,你需要再次下降一级以获得 *预期* 操作(在底层)的实际“真实”结果。 ln
的反向操作是 exp
。[fn 12] 要用 Pascal 术语来表达这个原则,
x * y = exp(ln(x) + ln(y))
请记住,x
和 y
必须为正数才能成为 ln
的有效参数。
应用
[edit | edit source]取对数然后再次对值进行指数运算被认为是“缓慢”的操作,会引入一定程度的开销。在编程中,*开销* 指的是执行与实际底层问题没有直接关系的步骤,而只是为了解决问题而采取的步骤。一般来说,应该避免开销,因为(首先)它会让我们离解决方案更远。
real
数据类型的范围,但 *已知* 最终结果 *肯定* 会再次在 real
的范围内,那么就可以使用对数来 *克服* real
数据类型的范围限制。
program logarithmApplicationDemo(output);
const
operand = maxReal;
var
result: real;
begin
// for comparison
writeLn(maxReal:40);
result := sqr(operand);
result := sqrt(result);
writeLn(result:40);
// “lift” one level
result := ln(operand);
result := 2.0 * result; // corresponds to sqr(…)
result := 0.5 * result; // corresponds to sqrt(…)
// reverse `ln`: bring `result` “back down”
result := exp(result);
writeLn(result:40);
end.
1.7976930000000000495189307440746532950903200318892038e+308
Inf
1.7976929999999315921963138504476453672053533033977331e+308
sqr
的中间结果超出了 real
的范围,导致任何后续结果无效。在这个特定的实现中,这种情况显示为 Inf
(无穷大)。由于我们 *知道* 通过取主平方根来逆转这个操作会再次得到可存储的结果,所以我们可以使用对数来执行相同的操作。 显示的输出是由使用 GPC 编译的 program
生成的。该程序在使用 80 位数字的具有 FPU 的 64 位平台上执行。如您所见,这会损害精度。这是在快速操作和“足够精确”的结果之间的 *妥协*。
当然,最好的解决方案是找到一个 *更好的算法*。上面的演示 实际上是 ,即 abs(x)
(请记住,对数字进行平方运算始终会产生一个非负数)。此操作的结果将保留在 real
的范围内。
任务
[edit | edit source]所有任务,包括以下章节中的任务, *都可以* 在 *没有* 符合数组参数的情况下解决。这考虑到了并非所有主要编译器都支持符合数组参数这一事实。[fn 13] 尽管如此,使用带有符合数组参数的例程通常是更优雅的解决方案。
program
,打印以下乘法表 1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
writeLn
)。数据 *生成* 和数据 *打印* 应由 *单独* 的例程实现(这些例程 *不能* 互相调用)。program multiplicationTable(output);
const
xMinimum = abs(1);
xMaximum = abs(10);
yMinimum = abs(1);
yMaximum = abs(10);
// NB: Only Extended Pascal allows constant definitions
// based on expressions. The following two definitions
// are illegal in Standard Pascal (ISO 7185).
zMinimum = xMinimum * yMinimum;
zMaximum = xMaximum * yMaximum;
现在(作为 *常量*)计算最大和最小预期值,这样如果任何值超过了 maxInt
,编译器会在 *编译* 期间发出警告。
插入 abs
是作为一种文档方式:该程序仅对 *非* 负数有效。
type
x = xMinimum..xMaximum;
y = yMinimum..yMaximum;
z = zMinimum..zMaximum;
table = array[x, y] of z;
使用 z
作为 table
数组的基本类型(而不仅仅是 integer
)的优点是,如果我们意外地 *错误地* 实现了乘法,分配超出范围的值将失败。对于像这样的简单任务,这当然无关紧要,但对于更复杂的情况,*故意限制* 允许的范围可以 *阻止* 编程错误。如果您只是使用了一个简单的 integer
,请不要担心。
var
product: table;
注意,product
变量必须在定义 populateTable
和 printTable
*之前* 和 *外部* 声明。这样,这两个例程都引用了 *同一个* product
变量。[fn 14]
procedure populateTable;
var
factorX: x;
factorY: y;
begin
for factorX := xMinimum to xMaximum do
begin
for factorY := yMinimum to yMaximum do
begin
product[factorX, factorY] := factorX * factorY;
end;
end;
end;
也可以重复使用先前计算的值,利用表格可以沿着对角线轴镜像的事实,或进行其他“优化技巧”。不过,对于 *此任务* 来说,重要的是正确地嵌套 for
循环。
procedure printTable;
var
factorX: x;
factorY: y;
begin
for factorY := yMinimum to yMaximum do
begin
for factorX := xMinimum to xMaximum do
begin
write(product[factorX, factorY]:5);
end;
writeLn;
end;
end;
当然,高级实现首先会确定最大预期长度并将其存储为一个变量,而不是使用硬编码格式说明符 :5
。不过,这超出了此任务的范围。[fn 15] 只是应该提到,像这样硬编码的值被认为是不好的风格。
begin
populateTable;
printTable;
end.
real
值文字的多种不同方式,它们都表示值“正一”。它们是什么?real
值文字1.0
+1.0
1.00
1E0
1E+0
1E-0
1E00
1.0
。本练习旨在让你意识到,与 integer
值不同,real
数值可以有多种有效的表示形式。注意,一些编译器也会接受诸如 1.
的字面量,但这是非标准的。GPC 仅在非 ISO 兼容模式下接受它,但仍会发出警告。
function
,计算正数的 n 次方。尽可能限制参数的数据类型。如果结果无效(即超出范围),函数应返回 0
。type
naturalNumber = 1..maxInt;
wholeNumber = 0..maxInt;
{**
\brief iteratively calculates the integer power of a number
\param base the (positive) base in x^n
\param exponent the (non-negative) exponent in x^n
\return \param base to the power of \param exponent,
or zero in the case of an error
**}
function power(base: naturalNumber; exponent: wholeNumber): wholeNumber;
var
accumulator: wholeNumber;
begin
{ anything [except zero] to the power of zero is defined as one }
accumulator := 1;
for exponent := exponent downto 1 do
begin
{ if another “times `base`” would exceed the limits of `integer` }
{ we invalidate the entire result }
if accumulator > maxInt div base then
begin
accumulator := 0;
end;
accumulator := accumulator * base;
end;
power := accumulator;
end;
exponent := exponent
之类的空语句完全有效,以满足 Pascal 语法的要求。一个好的编译器会对其进行优化。请注意,EP 标准提供了 integer
次方运算符 pow
。[fn 16]
base
值。如果你的编译器支持 EP procedure halt
,你的 function
应该打印一条错误消息并终止 program
,如果 ,因为没有普遍认可的 定义 。{**
\brief iteratively calculates the integer power of a number
\param base the non-zero base in x^n
\param exponent the (non-negative) exponent in x^n
\return \param base to the power of \param exponent,
or zero in the case of an error
The program aborts if base = 0 = exponent.
**}
function power(base: integer; exponent: wholeNumber): integer;
var
accumulator: integer;
negativeResult: Boolean;
begin
if [base, exponent] = [0] then
begin
writeLn('Error in `power`: base = exponent = 0, but 0^0 is undefined');
halt;
end;
set
来让你对这种可能性有所了解。但是,你通常并且最有可能编写 (base = 0) and (base = exponent)
或类似的语句,这同样有效。 { anything [except zero] to the power of zero is defined as one }
accumulator := 1;
negativeResult := (base < 0) and odd(exponent);
{ calculating the _positive_ power of base^exponent }
{ simplifies the invalidation condition in the loop below }
base := abs(base);
if base > 1 then
begin
for exponent := exponent downto 1 do
begin
{ if another “times `base`” would exceed the limits of `integer` }
{ we invalidate the entire result }
if accumulator > maxInt div base then
begin
accumulator := 0;
end;
accumulator := accumulator * base;
end;
end;
if
分支的必要性可能不像它应该的那样明显:因为我们之前将可能的 base
值的范围扩展到所有 integer
值,因此也可以指定 0
。但是,请记住 除以零 是非法的。由于我们的无效条件依赖于 div base
,因此我们需要采取预防措施。 if negativeResult then
begin
accumulator := -1 * accumulator;
end;
power := accumulator;
end;
- 用户将用空行终止其输入。事先打印此说明。
- 完成后,打印消息。
- 打印时,一行最多可以有 80 个字符(或对你来说合理的长度)。你可以假设用户的输入行最多为 80 个字符。
- 确保你只在空格字符处换行(除非一行中没有空格字符)。
你可以在以下 Wikibook 页面找到更多可以解决的任务
- A-level Computing 2009/AQA/Problem Solving, Programming, Data Representation and Practical Exercise/Fundamentals of Programming/One-Dimensional Arrays
- A-level Computing 2009/AQA/Problem Solving, Programming, Data Representation and Practical Exercise/Fundamentals of Programming/Two-Dimensional Arrays
来源
- ↑ Jensen, Kathleen; Wirth, Niklaus. Pascal – user manual and report (4th revised ed.). p. 56. doi:10.1007/978-1-4612-4450-9. ISBN 978-0-387-97649-5.
An array type consists of a fixed number of components (defined when the array type is introduced) all having the same type, called the component type.
{{cite book}}
: no-break space character in|title=
at position 7 (help) - ↑ 这个限制来自 Pascal: ISO 7185:1990 (报告). ISO/IEC. 1991. p. 16. "real-type. 所需的类型标识符 real 应表示 real-type。 […] 这些值应为实数的实现定义子集,如 6.1.5 中所述,用带符号实数表示。" 最后一句话的末尾暗示,只有可以写入的那些值,即你在源代码中可以指定的那些值,才是合法的
real
值。例如,值 与 不同,后者的小数表示将无限长(也称为无理数),因此 的实际正确值不能在源代码中以 “real
” 值的形式出现。 - ↑ a b Joslin, David A. (1989-06-01). "扩展 Pascal – 数值特性". Sigplan Notices. 24 (6): 77–80. doi:10.1145/71052.71063.
程序员可以通过实现定义的常量
MINREAL
、MAXREAL
和EPSREAL
(均为正数)了解实数范围和精度。在[-maxreal,-minreal]
、0
和[minreal,maxreal]
这些范围内,“可以预期算术运算将以合理的近似值进行”,而在这些范围之外则不能。然而,由于什么是“合理的近似值”是一个见仁见智的问题,并且在标准中没有定义,因此此声明可能不如乍一看那么有用。精度的衡量标准则更加明确:EPSREAL
是常用的精度度量(通常是浮点精度),即最小的值,使得1.0 + epsreal > 1.0
。{{cite journal}}
: 换行符在|quote=
位置 259 (帮助) - ↑ a b Jensen, Kathleen; Wirth, Niklaus. Pascal – 用户手册和报告 (第 4 版修订版). p. 20. doi:10.1007/978-1-4612-4450-9. ISBN 978-0-387-97649-5.
只要至少一个操作数的类型为
Real
(另一个操作数可能是Integer
类型),以下运算符将生成一个实数值:*
乘法 /
除法(两个操作数都可以是整数,但结果总是实数) +
加法 -
减法 {{cite book}}
: 换行符在|quote=
位置 218 (帮助); 不间断空格字符在|title=
位置 7 (帮助)
注释
- ↑ 一些(旧的)计算机不知道方括号字符。说真的,这不是个玩笑。相反,使用了一个替代的二字母组:
var signCharacter: array(.Boolean.) of char;
,以及signCharacter(.true.) := '-';
。你可能在一些(旧的)Pascal 教材中遇到过这种表示法。无论如何,使用方括号仍然是首选方法。 - ↑ 只有与
packed array[1..n] of componentType
相关的I/O 是标准化的,其中n
大于1
且componentType
是或是一个char
的子范围。但是,在本书的这部分,你还没有学习打包的概念,即packed
关键字。因此,所展示的行为是非标准的。 - ↑ 更准确地说,
text
文件是(可能为空的)行序列,每行由一个(可能为空的)char
值序列组成。 - ↑ 一些编译器(例如 FPC)允许零大小的数据类型 [在任何 ISO 标准中都不允许]。如果是这种情况,则具有零大小基类型的数组将变得无效,实际上会失去所有数组的特性。
- ↑ 现代
real
算术处理器可以指示精度损失,即当算术运算的结果必须“近似”时。但是,没有标准化的方法可以从 Pascal 源代码中访问此类信息,并且通常这种信号也不利,因为最小的精度损失都会触发警报,从而降低程序速度。相反,如果重要的话,人们会使用允许任意精度算术的软件,例如 GNU 多精度算术库。 - ↑ 这个数字并不完全是任意的。最流行的
real
数字实现 IEEE 754 使用了一个“隐藏位”,使值1.0
变得特殊。 - ↑ 并非所有编译器都符合标准的这个定义。 FPC 的标准
round
实现将在等距情况下向偶数舍入。了解这一点对于统计应用很重要。 GPC 针对其round
实现使用硬件提供的功能。因此,实现依赖于硬件,依赖于其特定配置,并且可能偏离 ISO 7185 标准定义。 - ↑ 鉴于最流行的实现补码 用于
integer
值,以及IEEE 754 用于real
值,你必须考虑这样一个事实,即(实际上)integer
中的所有 位都对它的(数学)值有贡献,而一个real
数存储表达式mantissa * base pow exponent
的值。用非常简单的术语来说,mantissa
存储值的integer
部分,但问题是它占用的位数少于integer
会使用的位数,因此(对于需要更多 位的值)会造成信息的损失(即精度损失)。 - ↑ 精确的技术定义是这样的:
dividend div divisor
的值应使得其中,如果abs(dividend) - abs(divisor) < abs((dividend div divisor) * divisor) <= abs(dividend)
abs(dividend) < abs(divisor)
,则该值为零;否则,[…] - ↑ 此外,两个数组都必须是
packed
或“未打包”的。 - ↑ EP 标准将此特性称为
protected
。 - ↑ 重要的是,两个函数都使用一个共同的基数,在本例中是 .
- ↑ ISO 标准 7185(“标准 Pascal”)将这种不符合数组参数的现象称为“Level 0 兼容性”。“Level 1 兼容性”包括对符合数组参数的支持。在入门中介绍的编译器中,只有GPC 是“Level 1”兼容的编译器。
- ↑ 这种编程风格略微不受欢迎,关键字“全局变量”,但目前我们还不知道合适的语法 (
var
参数) 来避免这样做。 - ↑ 额外说明:你可以利用这样一个事实,即(假设
zMaximum
是正数)。你可以使用这个值来找出所需的最小位数。 - ↑ 在EP 中,还存在一个
real
幂运算符**
。区别类似于除法运算符:pow
仅接受integer
值作为操作数并返回一个integer
值,而**
始终返回一个real
值。你应该根据所需的精度选择其中一个,再次强调,你的选择应该基于所需的精度。