Pascal 程式設計/記錄
成功程式設計的關鍵是找到資料的“正確”結構和程式。
—尼克勞斯·維爾特[1]
在你學會使用 array 之後,本章介紹了另一種資料型別結構概念,稱為 record。像 array 一樣,使用 record 的主要目的是允許你編寫乾淨的、結構化的 程式。否則它是可選的。
概念
[edit | edit source]你在 第一章 中簡要地看到了 record。雖然 array 是同質的資料聚合,這意味著所有成員都必須具有相同的基本資料型別,但 record 可能,但並非一定是具有各種不同資料型別的聚合。 [2]
宣告
[edit | edit source]一個 record 資料型別宣告看起來很像一個集合的變數宣告
program recordDemo;
type
(* a standard line on a text console *)
line = string(80);
(* 1st grade through 12th grade *)
grade = 1..12;
(* encapsulate all administrative data in one structure *)
student = record
firstname: line;
lastname: line;
level: grade;
end;
宣告以 record 一詞開始,並以 end 結束。在兩者之間,你聲明瞭欄位,或成員,整個 record 的成員元素。
所有 record 成員必須在 record 宣告本身內具有不同的名稱。例如,在上面的示例中,宣告兩個名為 level 的“變數”,成員元素將被拒絕。
對要宣告的欄位數量沒有要求。一個“空” record 也是可能的:[fn 1]
type
emptyRecord = record
end;
多個相同資料型別的欄位
[edit | edit source]與 變數宣告 類似,你可以透過用逗號分隔識別符號來定義多個相同資料型別的欄位。之前的 sphere 宣告也可以寫成
type
sphere = record
radius, volume, surface: real;
end;
然而,大多數 Pascal 老手和風格指南不鼓勵使用這種簡寫符號(用於變數和 record 宣告,以及在形式引數列表中)。它只有在所有宣告的識別符號絕對始終具有相同資料型別時才合理;幾乎可以保證你永遠不想改變逗號分隔列表中僅一個欄位的資料型別。如有疑問,請使用長格式。在程式設計中,便利起著間接的作用。
使用
[edit | edit source]透過宣告一個 record 變數,你立即獲得了整個集合的“子”變數。訪問它們是透過指定record 變數的名稱,加上一個點 (.),然後是 record 欄位的名稱
var
posterStudent: student;
begin
posterStudent.firstname := 'Holden';
posterStudent.lastname := 'Caulfield';
posterStudent.level := 10;
end.
你已經在上一章關於 字串 中看到了點符號,其中在 .capacity 上附加一個 string(…) 變數的名稱指的是相應變數的字元容量。這不是巧合。
program dotNoGo(output); { This program does not compile. }
type
line = string(80);
quizItem = record
question: line;
answer: line;
end;
var
response: line;
challenge: quizItem;
begin
writeLn(line.capacity); { ↯ `line` is not a variable }
writeLn(response.capacity); { ✔ correct }
writeLn(quizItem.question); { ↯ `quizItem` refers to a data type }
{ Data type declarations (as per definition) do not reserve any memory }
{ thus you cannot “read/write” from/to a data type. }
writeLn(challenge.question); { ✔ correct }
end.
.) 只有在有記憶體的情況下才有效。 [fn 2]
優點
[edit | edit source]但是為什麼以及何時我們想要使用 record?乍一看,在給出的示例中,它似乎是宣告和使用多個變數的麻煩方式。然而, record 被作為一個單位處理這一事實包含一個很大的優勢
- 您可以透過簡單的賦值(
:=)來複製整個record的值。 - 這意味著您可以一次傳遞大量資料:一個
record可以作為例程的引數,在EP函式中也可以作為返回值。[fn 3]
顯然,您希望將始終一起出現的資料分組。將無關的資料分組沒有意義,僅僅因為我們可以這樣做。另一個非常有用的優點將在下面關於變體記錄部分中介紹。
正如您之前看到的,引用record的成員可能會有點繁瑣,因為我們一遍又一遍地重複變數名。幸運的是,Pascal 允許我們稍微縮寫一下。
with-子句允許我們消除重複公共字首,特別是record變數的名稱。[3]
begin
with posterStudent do
begin
firstname := 'Holden';
lastname := 'Caulfield';
level := 10;
end;
end.
所有標識值的識別符號首先在record的posterStudent範圍內進行查詢。如果找不到匹配項,則也會考慮給定record外部的所有變數識別符號。
當然,仍然可以使用完整名稱來表示record成員。例如,在上面的原始碼中,在with-子句內仍然可以完全合法地寫posterStudent.level。誠然,這會違背with-子句的目的,但有時為了文件目的,強調特定的record變數可能仍然是有益的。但是,重要的是要理解,FQI(完全限定識別符號),即帶有一個點的識別符號,不會失去其“有效性”。
原則上,所有包含“點”的結構化值元件都可以用with來縮寫。這也適用於你在上一章學過的資料型別string。
program withDemo(input, output);
type
{ Deutsche Post „Maxi-Telegramm“ }
telegram = string(480);
var
post: telegram;
begin
with post do
begin
writeLn('Enter your telegram. ',
'Maximum length = ',
capacity, ' characters.');
readLn(post);
{ … }
end;
end.
這裡,在with-子句capacity內,同樣在post.capacity內,都指的是post.capacity。
如果需要巢狀多個with-子句,可以使用簡短的表示法
with snakeOil, sharpTools do
begin
…
end;
它等效於
with snakeOil do
begin
with sharpTools do
begin
…
end;
end;
重要的是要牢記,首先在sharpTools中搜索識別符號,如果找不到匹配項,其次,考慮snakeOil中的識別符號。
在 Pascal 中,record是唯一允許您在執行時(在program執行時)改變其結構的資料型別結構概念。這種record的超實用屬性允許我們編寫涵蓋許多情況的通用程式碼。
讓我們看一個例子
type
centimeter = 10..199;
// order of female, male has been chosen, so `ord(sex)`
// returns the [minimum] number of non-defective Y chromosomes
sex = (female, male)
// measurements according EN 13402 size designation of clothes [incomplete]
clothingSize = record
shoulderWidth: centimeter;
armLength: centimeter;
bustGirth: centimeter;
waistSize: centimeter;
hipMeasurement: centimeter;
case body: sex of
female: (
underbustMeasure: centimeter;
);
male: (
);
end;
record的變體部分以關鍵字case開頭,您已經從選擇中瞭解過。之後跟著一個record成員宣告,變體選擇器,但您不要使用分號,而是使用關鍵字of。在此之後,所有可能的變體都在下面。每個變體都用變體選擇器域中的一個值來標記,這裡分別是female和male。用冒號(:)分隔,之後跟著用括號括起來的變體指示符。在這裡,您可以列出只有在某個變體“啟用”時才可用的其他record成員。請注意,所有變體中的所有識別符號都必須是唯一的。各個變體用分號分隔,最多可以有一個變體部分,它必須出現在最後。因為您需要能夠列出所有可能的變體,所以變體選擇器必須是序數資料型別。
使用變體記錄要求您首先選擇一個變體。透過將一個值賦給變體選擇器來“啟用”變體。請注意,變體不是“建立”的;它們都在program啟動時就已存在。您只需要做出選擇。
boobarella.body := female;
boobarella.underbustMeasure := 69;
只有在將值賦給變體選擇器之後,並且只要該值保持不變,您才能訪問相應變體的任何欄位。反轉前兩行程式碼並嘗試訪問underbustMeasure欄位是非法的,即使body尚未定義,更重要的是,它不具有值female。
在您的program中稍後更改變體選擇器是完全可以的,然後使用不同的變體,但是變體部分中所有先前儲存的值都將失效,您無法恢復它們。如果您將變體切換回先前,原始的值,則需要重新分配該變體中的所有值。
這個概念開闢了新的視野:您可以以簡潔的方式更互動地設計您的程式。現在,您可以根據執行時資料(在program執行時讀取的資料)來選擇變體。因為在任何時候(在第一次將值賦給變體選擇器之後),只有一個變體是“啟用”的,所以如果您的program嘗試讀取/寫入“非啟用”變體的值,它就會崩潰。這是期望的行為,因為這就是擁有不同變體的目的。它保證了您的程式整體完整性。
Pascal 還允許使用匿名變體選擇器,即不帶任何名稱的選擇器。其含義是
- 您無法顯式選擇(或查詢)任何變體,因此
- 反過來,所有變體都被認為是同時“啟用”的。
“但是,這不是練習的目的嗎?”你可能會問。是的,確實,因為沒有命名選擇器,你的 program 無法跟蹤哪個變體應該工作,哪個變體是“有缺陷的”。你 有責任確定目前你可以合理地讀/寫哪個變體。
program anonymousVariantsDemo(output);
type
bitIndex = 0..(sizeOf(integer) * 8 - 1);
exposedInteger = record
case Boolean of
false: (
value: integer;
);
true: (
bit: set of bitIndex;
);
end;
var
i: exposedInteger;
begin
i.bit := [4];
writeLn(i.value);
end.
16
16 是(這應該被認為是“巧合”). 我們強調,所有 Pascal 標準都沒有對內部記憶體結構做出任何宣告。高階程式語言不關心資料如何儲存,它甚至不知道“位”,“高電壓”/“低電壓”的概念。| 因此,如果你(有意地)使用這裡演示的任何行為,你不能再說是“我在用 Pascal 程式設計”,而是在專門針對編譯器如此這般進行程式設計。資料結構的記憶體佈局在 Pascal 實現之間有所不同。 |
這個概念也存在於許多其他程式語言中。例如,在程式語言 C 中,它被稱為 聯合。
條件迴圈
[edit | edit source]到目前為止,我們一直只使用計數 迴圈。如果你可以提前預測迭代次數,迴圈體需要執行多少次,這將非常棒。但很多時候,無法事先制定一個適當的表示式來確定迭代次數。
條件迴圈 允許你讓下一次迭代的執行取決於一個Boolean 表示式。它們有兩種形式
- 頭部控制迴圈,以及
- 尾部控制迴圈。
區別在於,尾部控制迴圈的迴圈體至少執行一次,而頭部控制迴圈可能根本不執行迴圈體。在任何情況下,都會反覆評估一個條件,並且必須保持該條件才能使迴圈繼續。
頭部控制迴圈
[edit | edit source]頭部控制迴圈通常被稱為 while 迴圈,因為它的語法。
program characterCount(input, output);
type
integerNonNegative = 0..maxInt;
var
c: char;
n: integerNonNegative;
begin
n := 0;
while not EOF do
begin
read(c);
n := n + 1;
end;
writeLn('There are ', n:1, ' characters.');
end.
$ cat ./characterCount.pas | ./characterCount
There are 240 characters.
$ printf '' '' | ./characterCount
There are 0 characters.
Boolean 表示式,用 while 和 do 兩個詞框起來。對於任何(後續)迭代,該條件必須評估為 true。從輸出中可以看出,在第二種情況下,它甚至可能為零次:顯然,對於空輸入,n := n + 1 從未執行。EOF 是 EOF(input) 的簡寫。這個標準 function 如果沒有更多資料可供讀取,則返回 true,通常稱為檔案結束。如果相應的 EOF 函式呼叫返回 true,則從檔案中 read是非法的,並且會非常糟糕地失敗。
與計數迴圈不同,你可以修改條件迴圈的條件所依賴的資料。
const
(* instead of a hard-coded length `64` *)
(* you can write `sizeOf(integer) * 8` in Delphi, FPC, GPC *)
wordWidth = 64;
type
integerNonNegative = 0..maxInt;
wordStringIndex = 1..wordWidth;
wordString = array[wordStringIndex] of char;
function binaryString(n: integerNonNegative): wordString;
var
(* temporary result *)
binary: wordString;
i: wordStringIndex;
begin
(* initialize `binary` with blanks *)
for i := 1 to wordWidth do
begin
binary[i] := ' ';
end;
(* if n _is_ zero, the loop's body won't be executed *)
binary[i] := '0';
(* reverse Horner's scheme *)
while n >= 1 do
begin
binary[i] := chr(ord('0') + n mod 2);
n := n div 2;
i := i - 1;
end;
binaryString := binary;
end;
迴圈條件所依賴的 n 將被反覆除以二。由於除法運算子是整數除法 (div),因此在某個時候,值 1 將被除以二,並且算術上正確的結果 0.5 被截斷 (trunc) 到零。但是,值 0 不再滿足迴圈的條件,因此將不會有任何後續迭代。
尾部控制迴圈
[edit | edit source]在尾部控制迴圈中,條件出現在迴圈體下方,在尾部。迴圈體始終在條件評估之前被執行一次。
program repeatDemo(input, output);
var
i: integer;
begin
repeat
begin
write('Enter a positive number: ');
readLn(i);
end
until i > 0;
writeLn('Wow! ', i:1, ' is a quite positive number.');
end.
迴圈體被 repeat 和 until 關鍵字封裝起來。[fn 4] 在 until 後面跟一個 Boolean 表示式。與 while 迴圈相反,尾部控制迴圈始終繼續,始終保持執行,until 指定的條件變為 true。一個 true 條件表示結束。在上面的示例中,使用者將被反覆提示,直到他最終服從並輸入一個正數。
日期和時間
[edit | edit source]本節向你介紹 ISO 標準 10206 中定義的擴充套件 Pascal 的功能。你需要一個符合 EP 的編譯器才能使用這些功能。
時間戳
[edit | edit source]在 EP 中,有一個名為 timeStamp 的標準資料型別。它被宣告如下:[fn 5]
type
timeStamp = record
dateValid: Boolean;
timeValid: Boolean;
year: integer;
month: 1..12;
day: 1..31;
hour: 0..23;
minute: 0..59;
second: 0..59;
end;
從宣告中可以看出,timeStamp 還包含用於日曆日期的資料欄位,而不僅僅是標準時鍾指示的時間。
處理器(即通常是編譯器)可能會提供額外的(因此是非標準的)欄位。例如,GPC 提供了包括 timeZone 在內的其他欄位,該欄位指示相對於 UTC(“世界時間”)的秒數偏移量。 |
獲取時間戳
[edit | edit source]EP 還定義了一個一元的 procedure,它將值填充到 timeStamp 變數中。 GetTimeStamp 將值分配給傳遞到第一個(也是唯一)引數中的 timeStamp record 的所有成員。這些值代表呼叫此過程時的“當前日期”和“當前時間”。但是,在 1980 年代,並非所有(個人/家庭)計算機都具有內建的“即時”時鐘。因此,ISO 標準 10206 在 21 世紀之前制定,指出“當前”一詞是“實現定義的”。dateValid 和 timeValid 欄位專門用於解決某些計算機根本不知道當前日期和/或時間的問題。從 timeStamp 變數中讀取值時,在讓 getTimeStamp 填充它們之後,仍建議先檢查其有效性。
如果 getTimeStamp 無法獲得“有效”值,它將設定
day、month和year為表示 公元 1 年 1 月 1 日 的值,但同時也將dateValid設定為false。- 對於時間,
hour、minute和second都會變為0,表示午夜的值。timeValid欄位變為false。
兩者相互獨立,因此完全有可能只確定時間,但日期無效。
請注意,公曆是在公元 1582 年引入的,因此 timeStamp 資料型別通常對公元 1583 年之前的任何日期都無用。
獲得 timeStamp 後,EP 還會提供兩個一元函式
date返回day、month和year的人類可讀的string表示形式,以及time返回hour、minute和second的人類可讀的string表示形式。
如果 dateValid 或 timeValid 分別指示資料無效,則這兩個函式都將失敗並終止 program。請注意,string 表示形式的確切格式未由 ISO 標準 10206 定義。
program
program dateAndTimeFun(output);
var
ts: timeStamp;
begin
getTimeStamp(ts);
if ts.dateValid then
begin
writeLn('Today is ', date(ts), '.');
end;
if ts.timeValid then
begin
writeLn('Now it is ', time(ts), '.');
end;
end.
Today is 10 Oct 2024. Now it is 13:42:42.
dateValid 和 timeValid 都為 false。現在是盤點並重申各種迴圈的好時機。
如果你無法預測總迭代次數,條件迴圈是首選工具。
| 頭部控制迴圈 | 尾部控制迴圈 |
|---|---|
while condition do
begin
…
end;
|
repeat
begin
…
end
until condition;
|
condition 必須評估為 true 才能發生任何(包括後續)迭代。 |
condition 必須為 false 才能發生任何後續迭代。 |
可以將任一迴圈表示為另一個迴圈,但通常其中一個更合適。尾部控制迴圈特別適合在還沒有任何資料可以判斷的情況下使用,以便在第一次迭代之前評估合適的 condition。
如果你可以在進入迴圈之前預測總迭代次數,則計數迴圈很合適。
| 向上計數迴圈 | 向下計數迴圈 |
|---|---|
for controlVariable := initialValue to finalValue do
begin
…
end;
|
for controlVariable := initialValue downto finalValue do
begin
…
end;
|
在每次非最終迭代之後,controlVariable 都會變為 succ(controlVariable)。 controlVariable 必須小於或等於 finalValue 才能發生另一次迭代。 |
在每次非最終迭代之後,controlVariable 都會變為 pred(controlVariable)。 controlVariable 必須大於或等於 finalValue 才能發生另一次迭代。 |
initialValue 和 finalValue 表示式都會被精確評估一次。 [4] 這與條件迴圈有很大不同。 |
在計數迴圈的迴圈體內,你不能修改計數變數,只能讀取它。這可以防止任何意外操作,並確保計算的預測總迭代次數確實會發生。
不能保證 controlVariable 在迴圈“之後”為 finalValue。如果有正好零次迭代,則對 controlVariable 不會進行任何賦值。因此,通常假定 controlVariable 在 for 迴圈之後無效/未初始化,除非你絕對確定至少進行了一次迭代。 |
如果你使用的是支援 EP 的編譯器,還可以選擇對集合使用 for … in 迴圈。
program forInDemo(output);
type
characters = set of char;
var
c: char;
parties: characters;
begin
parties := ['R', 'D'];
for c in parties do
begin
write(c:2);
end;
writeLn;
end.
你已經走到這一步了,你所知道的知識已經相當令人印象深刻。由於本章關於 record 的概念應該不難理解,以下練習主要側重於訓練。一個專業的計算機程式設計師大部分時間都花在思考什麼樣的實現,使用哪些工具(例如 array “vs.” set),是最有用/合理的。鼓勵你在開始輸入任何內容之前先思考。儘管如此,有時(尤其是由於你缺乏經驗)你需要嘗試一下,如果這是有意的,那就沒關係。漫無目的地找到解決方案並不能體現真正的程式設計師。
record 可以包含另一個 record 嗎?array 可以包含另一個 array 一樣,record 也完全可以做到。寫一個測試 program 來驗證這一點。重要的是要注意,點符號可以無限擴充套件(myRecordVariable.topRecordFieldName.nestedRecordFieldName.doubleNestedRecordFieldName)。顯然,在某個時候它變得難以閱讀,因此請明智地使用它。
while true do
begin
…
end;
在 repeat … until 迴圈 中需要否定條件
repeat
begin
…
end
until false;
true 的表示式,或者永遠無法滿足的表示式(在 repeat … until 迴圈 的情況下),則不然。例如,假設 i 是一個 integer,則迴圈 while i <= maxInt do 將無限期地執行,因為 i 永遠不會超過 maxInt[fn 6],從而破壞迴圈條件。因此,請記住仔細為條件迴圈制定表示式,並確保它最終會達到終止狀態。否則,這可能會讓你的 program 的使用者感到沮喪。
while 迴圈repeat
begin
imagineJumpingSheep;
sheepCount := sheepCount + 1;
waitTwoSeconds;
end
until asleep;
while 迴圈開始之前就被重複了imagineJumpingSheep;
sheepCount := sheepCount + 1;
waitTwoSeconds;
while not asleep do
begin
imagineJumpingSheep;
sheepCount := sheepCount + 1;
waitTwoSeconds;
end;
repeat … until 迴圈 在這種情況下更合適。
program,它將命令 getent passwd 的輸出作為輸入,並且只打印每行中的第一個欄位/列。在 檔案中,欄位用冒號 (:) 分隔。你的 program 將列出所有已知的使用者名稱。getent passwd | ./cut1 執行以下程式(你的可執行程式的檔名可能不同)。program cut1(input, output);
const
separator = ':';
var
line: string(80);
begin
while not EOF(input) do
begin
{ This reads the _complete_ line, but at most}
{ line.capacity characters are actually saved. }
readLn(line);
writeLn(line[1..index(line, separator)-1]);
end;
end.
index 將返回冒號字元的索引,你不想列印它,因此你需要從它的結果中減去 1。如果一行不包含冒號,則此 program 顯然會失敗。
program,以便僅列印 UID 大於或等於 1000 的使用者名稱。UID 儲存在第三個欄位中。program cut2(input, output);
const
separator = ':';
minimumID = 1000;
var
line: string(80);
nameFinalCharacter: integer;
uid: integer;
begin
while not EOF do
begin
readLn(line);
nameFinalCharacter := index(line, separator) - 1;
{ username:encryptedpassword:usernumber:… }
{ ↑ `nameFinalCharacter + 1` }
{ ↑ `… + 2` is the index of the 1st password character }
uid := index(subStr(line, nameFinalCharacter + 2), separator);
{ Note that the preceding `index` did not operate on `line` }
{ but an altered/different/independent “copy” of it. }
{ This means, we’ll need to offset the returned index once again. }
readStr(subStr(line, nameFinalCharacter + 2 + uid), uid);
{ Read/readLn/readStr automatically terminate reading an integer }
{ number from the source if a non-digit character is encountered. }
{ (Preceding blanks/space characters are ignored and }
{ the _first_ character still may be a sign, that is `+` or `-`.)}
if uid >= minimumID then
begin
writeLn(line[1..nameFinalCharacter]);
end;
end;
end.
subStr 中的第三個引數可以省略,這實際上意味著“給我一個 string 的剩餘部分”。注意,此程式設計任務模擬了 的(部分)行為。使用已經為你程式設計的程式/原始碼,只要有可能。沒有必要重新發明輪子。儘管如此,這個基本任務是一個很好的練習。在 RHEL 系統上,你可能更希望將 minimumID 設定為 500。
program 滿足所有要求。注意,使用 array[1..limit] of Boolean 的實現也完全沒問題,儘管所示的 set of natural 實現原則上是首選的。program eratosthenes(output);
type
{ in Delphi or FPC you will need to write 1..255 }
natural = 1..4095;
{$setLimit 4096}{ only in GPC }
naturals = set of natural;
const
{ `high` is a Borland Pascal (BP) extension. }
{ It is available in Delphi, FPC and GPC. }
limit = high(natural);
{ Note: It is important that `primes` is declared }
{ in front of `sieve` and `list`, so both of these }
{ routines can access the _same_ variable. }
var
primes: naturals;
{ This procedure sieves the `primes` set. }
{ The `primes` set needs to be fully populated }
{ _before_ calling this routine. }
procedure sieve;
var
n: natural;
i: integer;
multiples: naturals;
begin
{ `1` is by definition not a prime number }
primes := primes - [1];
{ find the next non-crossed number }
for n := 2 to limit do
begin
if n in primes then
begin
multiples := [];
{ We do _not_ want to remove 1 * n. }
i := 2 * n;
while i in [n..limit] do
begin
multiples := multiples + [i];
i := i + n;
end;
primes := primes - multiples;
end;
end;
end;
{ This procedures lists all numbers in `primes` }
{ and enumerates them. }
procedure list;
var
count, n: natural;
begin
count := 1;
for n := 2 to limit do
begin
if n in primes then
begin
writeLn(count:8, '.:', n:22);
count := count + 1;
end;
end;
end;
{ === MAIN program === }
begin
primes := [1..limit];
sieve;
list;
end.
sieve 任務與 list 任務分離,因此例程定義和 program 底部的主要部分都保持相當短,因此更容易理解。
program,它從 input 讀取無限數量的數值,並在最後將算術平均值列印到 output。program arithmeticMean(input, output);
type
integerNonNegative = 0..maxInt;
var
i, sum: real;
count: integerNonNegative;
begin
sum := 0.0;
count := 0;
while not eof(input) do
begin
readLn(i);
sum := sum + i;
count := count + 1;
end;
{ count > 0: do not do division by zero. }
if count > 0 then
begin
writeLn(sum / count);
end;
end.
請注意,使用不包含負數的資料type(這裡我們將其命名為integerNonNegative)可以減輕count可能翻轉符號的問題,這種情況被稱為溢位。如果count := count + 1變得太大,就會導致program失敗,實際上超出了範圍0..maxInt。
maxReal,但沒有程式設計方法可以判斷sum是否變得太大或太小,使其變得極不準確,因為無論如何任何sum的值都可能是合法的。
time function,它返回一個string,以“美國”時間格式9:04 PM。乍一看這似乎很簡單,但它會變得非常具有挑戰性。玩得開心!time本身。但是,time本身的輸出沒有標準化,所以我們需要自己定義所有內容。type
timePrint = string(8);
function timeAmerican(ts: timeStamp): timePrint;
const
hourMinuteSeparator = ':';
anteMeridiemAbbreviation = 'AM';
postMeridiemAbbreviation = 'PM';
type
noonRelation = (beforeNoon, afterNoon);
letterPair = string(2);
var
{ contains 'AM' and 'PM' accessible via an index }
m: array[noonRelation] of letterPair;
{ contains a leading zero accessible via a Boolean expression }
z: array[Boolean] of letterPair;
{ holds temporary result }
t: timePrint;
begin
{ fill `t` with spaces }
writeStr(t, '':t.capacity);
此回退值(在ts.timeValid為false的情況下)允許此function的程式設計師/“使用者”“盲目”地列印其返回值。輸出中將會有一個明顯的空白。另一個合理的“回退”值是一個空的string。
with ts do
begin
if timeValid then
begin
m[beforeNoon] := anteMeridiemAbbreviation;
m[afterNoon] := postMeridiemAbbreviation;
z[false] := '';
z[true] := '0';
writeStr(t,
((hour + 12 * ord(hour = 0) - 12 * ord(hour > 12)) mod 13):1,
hourMinuteSeparator,
z[minute < 10], minute:1, ' ',
m[succ(beforeNoon, hour div 12)]);
這是這個問題中最複雜的部分。首先,所有傳遞給writeStr的數字引數都明確地以:1作為最小寬度規範字尾,因為有一些編譯器會假設,例如,:20作為預設值。由於我們知道timeStamp.hour的範圍是0..23,我們可以使用div和mod操作,如示例所示。但是,我們需要考慮hour值為0的情況,通常表示為 12:00 AM(而不是零)。使用所示的Boolean表示式和ord進行條件“偏移” 12 可以解決這個問題。此外,這裡簡要提醒一下,在EP 中,succ 函式接受第二個引數。
end;
end;
timeAmerican := t;
end;
來源
- ↑ Wirth, Niklaus (1979). "The Module: a system structuring facility in high-level programming languages". proceedings of the symposium on language design and programming methodology. Berlin, Heidelberg: Springer. Abstract. doi:10.1007/3-540-09745-7_1. ISBN 978-3-540-09745-7. https://link.springer.com/content/pdf/10.1007%2F3-540-09745-7_1.pdf. Retrieved 2021-10-26.
- ↑ Cooper, Doug. "Chapter 11. The
recordType". Oh! Pascal! (third edition ed.). p. 374. ISBN 0-393-96077-3.[…] records have two unique aspects:
First, the stored values can have different types. This makes records potentially heterogeneous—composed of values of different kinds. Arrays, in contrast, hold values of just one type, so they're said to be homogeneous.
[…]{{cite book}}: |edition= has extra text (help); line feed character in|quote=at position 269 (help); syntaxhighlight stripmarker in|chapter=at position 17 (help) - ↑ Wirth, Niklaus (1973-07-00). The Programming Language Pascal (Revised Report ed.). p. 30.
Within the component statement of the with statement, the components (fields) of the record variable specified by the with clause can be denoted by their field identifier only, i.e. without preceding them with the denotation of the entire record variable.
{{cite book}}: Check date values in:|date=(help) - ↑ Jensen, Kathleen; Wirth, Niklaus. Pascal – user manual and report (4th revised ed.). p. 39. doi:10.1007/978-1-4612-4450-9. ISBN 978-0-387-97649-5.
The initial and final values are evaluated only once.
筆記
- ↑ 這種
record將無法儲存任何內容。在下一章中,您將學習它可能在唯一一個例項中有用。 - ↑ 實際上大多數編譯器將點視為解引用指示符,並且欄位名稱表示從基本記憶體地址的靜態偏移量。
- ↑ 在標準(“未擴充套件”)Pascal 中,ISO 標準 7185 中,
function只能返回“簡單資料型別”和“指標資料型別”的值。 - ↑ 實際上,顯示的
begin … end是多餘的,因為repeat … until本身就構成了一個框架。出於教學目的,我們建議您在通常出現語句序列的地方始終使用begin … end。否則,您可能會將repeat … until迴圈 更改為while … do迴圈,忘記用適當的begin … end框架包圍迴圈體語句。 - ↑ 為了簡便起見,省略了
packed指示符。 - ↑ 根據大多數編譯器對
maxInt的定義。ISO 標準只要求所有在-maxInt..maxInt範圍內的算術運算都能完全正確地工作,但理論上(雖然不太可能)支援更多值。
