跳轉到內容

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/readLnwrite/writeLn 通常支援寫入和讀取 array[] of char 變數。

非常早期的 Pascal 編譯器支援這種人類可讀的 IO

程式碼:

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 檔案,如標準檔案 inputoutput,被理解為無限的 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 標準還定義了 EQNE 函式,以及更多其他函式。與 =<> 的區別在於,空白填充(即 FPC 中的 #0 或 GPC 中的 ' ')在 EQNE 中沒有意義。因此,這意味著使用這些函式,您可以比較字串和 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 分支會檢查這一點。嘗試訪問不存在的陣列值,即透過提供非法索引,可能會導致程式崩潰,或者更糟糕的是,無法檢測到錯誤,從而導致難以發現的錯誤。

Note 使用 GPC 編譯的程式將以以下方式終止:
./a.out: value out of range (error #300 at 402a76)
如果您嘗試訪問不存在的陣列項(最終的十六進位制數可能會有所不同)。但是,FPC 預設情況下會忽略這一點。您需要專門請求檢測錯誤,方法是指定 ‑Cr 命令列開關,或者在原始碼中放置編譯器指令註釋:
{$rangeChecks on}
在您的原始碼中(在您訪問任何陣列之前,在您的原始碼中寫一次就足夠了)。

另請參閱 第“列舉”章,小節“限制”

注意,xy 的“不尋常”順序是為了方便繪製直立的圖形。

	// print graph, note reverse `downto` direction
	for y := rowMaximum downto rowMinimum do
	begin
		writeLn(curve[y]);
	end;
end.

這意味著,仍然可以將整個“子”陣列作為一個整體引用。不必寫出陣列值的所有維度,只要有意義就行。

具有正好兩個維度的陣列資料型別也稱為矩陣,單數為矩陣

Note 在數學中,一個矩陣不必一定是同構的,但可以包含不同的“資料型別”。

第一章中介紹的,資料型別 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 ),或者兩者都有。
  • .(如果有)之前至少有一個西阿拉伯數字,之後也有一個西阿拉伯數字。
  • 整個數字和指數之前都有符號,但是符號是可選的。
Note 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 資料型別實現的精度會向極端值衰減,當接近(並超過)-maxRealmaxReal 時。因此,通常情況下,您使用,如果有的話,一個合理的 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]

Note 保證所有可能的integer 值都可以儲存為real 變數。[fn 8] 這主要涉及非小值,但重要的是要理解,integer 資料型別儲存整數、整數值的最佳選擇。

列印real

[編輯 | 編輯原始碼]

預設情況下,write/writeLn 使用“科學計數法”列印real 值。

Borland Pascal 定義了一個 real 值常量 pi。它被許多編譯器採用。

程式碼:

program printPi(output);
begin
	writeLn(pi);
end.

輸出:

 3.141592653589793e+00
此輸出是由使用 GPC 編譯的程式生成的(不強制 ISO 標準相容性)。寬度可能會有所不同,但總體樣式
  1. 符號,其中正號用空格代替
  2. 一位數字
  3. 一個點
  4. 一個正數的十進位制後數字
  5. 一個 E(大寫或小寫)
  6. 指數的符號,但這次始終寫正號,零指數前也加正號
  7. 指數值,具有固定的最小寬度(此處為 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、FPCGPC 都提供適當的實用程式來克服此限制。有關更多詳細資訊,請參閱其手冊。此問題不應阻止我們繼續學習 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 運算子執行整數除法,並丟棄(如果適用)任何餘數。表示式的結果資料型別始終為 integerdiv 運算子僅在兩個運算元都是 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 將接收。在本例中,它們被命名為 minimummaximum,透過中間的 .. 連線,表示一個範圍

minimummaximumprintTableRows 的定義中成為變數,但您不允許為它們賦值。[fn 11]不允許宣告與陣列邊界變數具有相同名稱的新識別符號。

在 Pascal 中,所有常量隱式地具有一個明確可確定的資料型別。由於我們的陣列限制識別符號實際上是變數,因此它們需要資料型別。: integer 表示 minimummaximum 都有資料型別 integer

Note 在一致陣列引數中,巢狀陣列的簡短表示法使用 ; 來分隔多個維度,因此類似於常規引數列表。

一旦我們宣告並定義了 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))

請記住,xy 必須為正數才能成為 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
第 10 行中 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 變數必須在定義 populateTableprintTable *之前* 和 *外部* 宣告。這樣,這兩個例程都引用了 *同一個* 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.
以下是一個直接的實現。
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 變數必須在定義 populateTableprintTable *之前* 和 *外部* 宣告。這樣,這兩個例程都引用了 *同一個* 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. 1.0
  2. +1.0
  3. 1.00
  4. 1E0
  5. 1E+0
  6. 1E-0
  7. 1E00
在現實生活中,你主要使用的是 1.0。本練習旨在讓你意識到,與 integer 值不同,real 數值可以有多種有效的表示形式。注意,一些編譯器也會接受諸如 1. 的字面量,但這是非標準的。GPC 僅在非 ISO 相容模式下接受它,但仍會發出警告。
無論它們的可使用性如何,以下所有都是表示值“正一”的合法 real 值文字
  1. 1.0
  2. +1.0
  3. 1.00
  4. 1E0
  5. 1E+0
  6. 1E-0
  7. 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]
“計算”意味著我們想要精確的結果。雖然你已經在這節課中學過對數,但最好始終保持在整數範圍內。對數可能會產生近似值。以下是一種可接受的實現
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;
已突出顯示更改的行。
{**
	\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;


編寫一個程式,接受“chirps”,即由最多 320 個字元組成的微博訊息。
  • 使用者將用空行終止其輸入。事先列印此說明。
  • 完成後,列印訊息。
  • 列印時,一行最多可以有 80 個字元(或對你來說合理的長度)。你可以假設使用者的輸入行最多為 80 個字元。
  • 確保你只在空格字元處換行(除非一行中沒有空格字元)。
提示:為了測試目的,你可能希望縮小你的輸入大小。


你可以在以下 Wikibook 頁面找到更多可以解決的任務


來源

  1. 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)
  2. 這個限制來自 Pascal: ISO 7185:1990 (報告). ISO/IEC. 1991. p. 16. "real-type. 所需的型別識別符號 real 應表示 real-type。 […] 這些值應為實數的實現定義子集,如 6.1.5 中所述,用帶符號實數表示。"  最後一句話的末尾暗示,只有可以寫入的那些值,即你在原始碼中可以指定的那些值,才是合法的 real 值。例如,值 不同,後者的小數表示將無限長(也稱為無理數),因此 的實際正確值不能在原始碼中以 “real” 值的形式出現。
  3. a b Joslin, David A. (1989-06-01). "擴充套件 Pascal – 數值特性". Sigplan Notices. 24 (6): 77–80. doi:10.1145/71052.71063. 程式設計師可以透過實現定義的常量 MINREALMAXREALEPSREAL (均為正數)瞭解實數範圍和精度。在 [-maxreal,-minreal]0[minreal,maxreal] 這些範圍內,“可以預期算術運算將以合理的近似值進行”,而在這些範圍之外則不能。然而,由於什麼是“合理的近似值”是一個見仁見智的問題,並且在標準中沒有定義,因此此宣告可能不如乍一看那麼有用。精度的衡量標準則更加明確:EPSREAL 是常用的精度度量(通常是浮點精度),即最小的值,使得 1.0 + epsreal > 1.0 {{cite journal}}: 換行符在 |quote= 位置 259 (幫助)
  4. 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 (幫助)

註釋

  1. 一些(舊的)計算機不知道方括號字元。說真的,這不是個玩笑。相反,使用了一個替代的二字母組:var signCharacter: array(.Boolean.) of char;,以及 signCharacter(.true.) := '-';。你可能在一些(舊的)Pascal 教材中遇到過這種表示法。無論如何,使用方括號仍然是首選方法。
  2. 只有與 packed array[1..n] of componentType 相關的I/O 是標準化的,其中 n 大於 1componentType 是或是一個 char 的子範圍。但是,在本書的這部分,你還沒有學習打包的概念,即 packed 關鍵字。因此,所展示的行為是非標準的。
  3. 更準確地說,text 檔案是(可能為空的)序列,每行由一個(可能為空的)char 值序列組成。
  4. 一些編譯器(例如 FPC)允許零大小的資料型別 [在任何 ISO 標準中都不允許]。如果是這種情況,則具有零大小基型別的陣列將變得無效,實際上會失去所有陣列的特性。
  5. 現代 real 算術處理器可以指示精度損失,即當算術運算的結果必須“近似”時。但是,沒有標準化的方法可以從 Pascal 原始碼中訪問此類資訊,並且通常這種訊號也不利,因為最小的精度損失都會觸發警報,從而降低程式速度。相反,如果重要的話,人們會使用允許任意精度算術的軟體,例如 GNU 多精度算術庫
  6. 這個數字並不完全是任意的。最流行的 real 數字實現 IEEE 754 使用了一個“隱藏位”,使值 1.0 變得特殊。
  7. 並非所有編譯器都符合標準的這個定義。 FPC 的標準 round 實現將在等距情況下向偶數舍入。瞭解這一點對於統計應用很重要。 GPC 針對其 round 實現使用硬體提供的功能。因此,實現依賴於硬體,依賴於其特定配置,並且可能偏離 ISO 7185 標準定義。
  8. 鑑於最流行的實現補碼 用於integer 值,以及IEEE 754 用於real 值,你必須考慮這樣一個事實,即(實際上)integer 中的所有 位都對它的(數學)值有貢獻,而一個real 數儲存表示式 mantissa * base pow exponent 的值。用非常簡單的術語來說,mantissa 儲存值的integer 部分,但問題是它佔用的位數少於integer 會使用的位數,因此(對於需要更多 位的值)會造成資訊的損失(即精度損失)。
  9. 精確的技術定義是這樣的:dividend div divisor 的值應使得
    abs(dividend) - abs(divisor) < abs((dividend div divisor) * divisor) <= abs(dividend) 
    其中,如果abs(dividend) < abs(divisor),則該值為零;否則,[…]
  10. 此外,兩個陣列都必須是packed 或“未打包”的。
  11. EP 標準將此特性稱為protected
  12. 重要的是,兩個函式都使用一個共同的基數,在本例中是 .
  13. ISO 標準 7185(“標準 Pascal”)將這種不符合陣列引數的現象稱為“Level 0 相容性”。“Level 1 相容性”包括對符合陣列引數的支援。在入門中介紹的編譯器中,只有GPC 是“Level 1”相容的編譯器。
  14. 這種程式設計風格略微不受歡迎,關鍵字“全域性變數”,但目前我們還不知道合適的語法 (var 引數) 來避免這樣做。
  15. 額外說明:你可以利用這樣一個事實,即(假設zMaximum 是正數)。你可以使用這個值來找出所需的最小位數。
  16. EP 中,還存在一個real 冪運算子**。區別類似於除法運算子pow 僅接受integer 值作為運算元並返回一個integer 值,而** 始終返回一個real 值。你應該根據所需的精度選擇其中一個,再次強調,你的選擇應該基於所需的精度。
下一頁: 字串 | 上一頁: 集合
首頁: Pascal 程式設計
華夏公益教科書