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.01.001E01E+01E-01E00
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值。你應該根據所需的精度選擇其中一個,再次強調,你的選擇應該基於所需的精度。