Pascal 是瑞士電腦科學家 Niklaus Wirth 教授所設計和開發的程式語言,其目的是作為結構化程式設計的教學工具, 因為要作為教學使用,在設計上是一個語法十分乾淨,程式碼可讀性良好,注意型別安全,並且適合初學者使用的程式語言。 Niklaus Wirth 教授在 Algorithms + Data Structures = Programs 這本教科書使用 Pascal 撰寫範例程式碼。 Object Pascal 一開始是由蘋果電腦為了其 Lisa 電腦,由 Larry Tesler 領導,Niklaus Wirth 提供諮詢的小組開發; 而後被 Borland 修改以後在 Turbo Pascal 和 Delphi 使用的 Pascal 方言。目前的 Pascal 大致上都以 Object Pascal 為基準。
Object Pascal 的主要實作有以下二個:
- Free Pascal:跨平台的開放原始碼編譯器, 最常被用來配合的開發環境為 Lazarus IDE
- Delphi: 在 Windows 平台上執行的軟體開發工具, 具有開發不同平台軟體的能力
下面是在 openSUSE 安裝 Free Pascal 的指令:
sudo zypper in fpc fpc-src
Pascal 中有三種風格的註解:
- (* 和 *):可跨越多行
- { 和 }:可跨越多行
- //:僅限單行(Object Pascal 新增加的註解方式)
在 Pascal 中,註解除了做為說明文字外,還會用在編譯器指示詞 (compiler directive) 及條件編譯 (conditional compilation)。
Pascal 是不區分大小寫的程式語言,使用者可以使用任意大小寫來命名變數 (variables)、函數 (functions) 和程序 (procedures)。 例如,變數 A_Variable、a_variable 和 A_VARIABLE 在 Pascal 中具有相同的意義。
下面就是一個 Hello World 的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
(* A hello world program *)
program MyProgram;
begin
WriteLn('Hello world!');
end.
注意:Free Pascal 支援不同的編譯模式,這個 Hello World 程式使用了 delphi mode,接下來我也會使用這個編譯模式進行學習。 使用 Free Pascal 提供的 Object Pascal 模式也是流行的選擇,{$H+} 是表示不要使用 ShortString 代表 string, 而是使用 AnsiString;{$J-} 則是表示不用與 Turbo Pascal 相容,const 無法在執行改變,是真的 read-only。
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
除了 delphi mode,還有 DelphiUnicode mode,為了支援 Delphi 4 乃至之後的版本的行為, 其差別在於宣告 string 型別時 delphi mode 會被視為 AnsiString,DelphiUnicode mode 會被視為 UnicodeString。 Free Pascal 可以在命令列中以 -M 參數來切換 Pascal 方言。像是以下指令以 delphi 相容模式來編譯 Pascal 程式碼:
fpc -Mdelphi -ohello hello.pas
按照 Pascal 的慣例,原始碼可以使用 .pas 或 .pp 為副檔名。.pas 是 Delphi 所使用的副檔名。 .pp 是 Free Pascal 為了要和 Delphi 區別而新設置的副檔名。兩者實質上沒有差別, Free Pascal 也接受用 .pas 為副檔名的原始碼。除此之外,Pascal 的引入檔 (include file) 的副檔名為 .inc。
Pascal 在程式堆積 (heap) 以及一些型別的記憶體管理方式為手動管理,Free Pascal 內建檢查記憶體洩露 (Memory Leak) 的功能, 可以協助檢查程式碼在使用記憶體方式是否有問題。 在編譯程式時加上 -gh 參數即可,可以在除錯或者是開發時加上檢查是否有記憶體使用上的問題。
如果 Free Pascal 與 Lazarus 一起使用,內建的函式庫可以分為:
- Run-Time Library:常見或者是與作業系統相關的基本功能
- FCL (Free Component Library):非圖形界面程式的元件
- LCL (Lazarus Component Library):跨平台的圖形界面函式庫
在 Unix 系統中,Free Pascal 提供了 instantfpc 指令將 Pascal 程式當成命令稿來執行。 其原理為 instantfpc 會在背景編譯該 Pascal 程式,並將編譯好的程式做快取。
#!/usr/bin/env instantfpc
{$ifdef FPC} {$mode delphi} {$endif}
begin
WriteLn('Hello World');
end.
Free Pascal 也提供內建的 source code formatter,ptop。 我習慣第一次執行 ptop -g ~/.ptop.cfg 造出設定檔案。 再修改如下:
- 將 capital 改為 lower:儘量與 Lazarus 的格式統一
Data Types
以下是 Pascal 中可見的資料型態:
- 純量 (scalar)
- 布林 (boolean)
- 字元 (character)
- 整數 (integer)
- 浮點數 (floating point number)
- 列舉 (enumeration)
- 容器 (collection)
- 陣列 (array)
- 集合 (set)
- 複合型態 (compound type)
- 記錄 (record)
- 物件 (object) (Object Pascal 新增)
- 類別 (class) (Object Pascal 新增)
- 指標 (pointer)
- 不定型態 (Variant)
在 Free Pascal 中,十六進位 (hex) 值透過在常數前加上美元符號 $ 表示。
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program HexExample;
var
i: LongInt;
begin
i := $FF;
Writeln('The value of $FF is: ', i);
end.
如果要使用 ASCII Code 表達一個字元,透過在常數前加上# 表示。
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Example;
var
MyChar: Char;
begin
MyChar := #65;
// MyChar -> 'A'
WriteLn(MyChar);
end.
Pascal 一開始就提供字串 (string) 型別,而字串其實就是字元陣列,Pascal 只是在陣列第一個索引放置字串的長度。 也因此 Pascal 如果要存取 string 內的字元,其索引是從 1 開始,即使後面的實作與一開始不同也遵循此方式。
下面就是變數宣告型別的例子:
var
age, weekdays : integer;
taxrate, net_income: real;
choice, isready: boolean;
initials, grade: char;
name, surname : string;
在 Delphi 10.3 之後,允許 Inline variable declaration,而 Free Pascal 並未實作此特性。 因此 Free Pascal 在使用變數時需要在開頭的地方使用 var 統一宣告。 Inline variable declaration 讓使用者可以有需要的時候才宣告變數,可以增加程式的可讀性, 是個很棒的程式語言特性,但是如果配合型別推斷的特性使用,那麼對於程式可讀性並沒有幫助(對於維護的人說甚至會造成可讀性問題)。
Pascal 允許宣告型別。型別可以透過名稱或識別符來識別。此型別可用於定義該型別的變數。
type
days, age = integer;
yes, true = boolean;
name, city = string;
fees, expenses = real;
這樣定義的型別就可以用於變數宣告中:
var
weekdays, holidays : days;
choice: yes;
student_name, emp_name : name;
capital: city;
cost: expenses;
在 Pascal 中的賦值方法如下:
variable_name := value;
因此我們可以這樣執行變數初始化:
var
variable_name : type = value;
下面是一個印出目前時間的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Demo;
uses
SysUtils;
var
CurrentDateTime: TDateTime;
begin
CurrentDateTime := Now;
WriteLn (FormatDateTime('yyyy-mm-dd HH:nn:ss', CurrentDateTime));
end.
常數的宣告語法如下:
const
Identifier = contant_value;
下面是一些例子:
PIE = 3.141592;
NAME = 'Stuart Little';
Pascal 只允許宣告以下型別的常數:
- Ordinal types
- Set types
- Pointer types (but the only allowed value is Nil).
- Real types
- Char
- String
列舉資料型別 (Enumerated types) 是使用者自訂的資料型別。它們允許以列表形式指定值。列舉資料型別僅允許使用賦值運算子和關係運算子。 列舉資料型別可以如下宣告:
type
enum-identifier = (item1, item2, item3, ... )
下面是一些例子:
type
SUMMER = (April, May, June, July, September);
COLORS = (Red, Green, Blue, Yellow, Magenta, Cyan, Black, White);
TRANSPORT = (Bus, Train, Airplane, Ship);
子範圍型別 (Subrange Types) 允許變數取值位於特定範圍內。例如,如果選民的年齡應在 18 到 100 歲之間,則可以將名為 age 的變數宣告為:
var
age: 18 ... 100;
也可以使用宣告來定義子範圍型別。宣告子範圍型別的語法如下:
type
subrange-identifier = lower-limit ... upper-limit;
下面是使用的例子:
type
Number = 1 ... 100;
Operators
算術運算子用在基礎代數運算,包含以下運算子:
- +:相加
- -:相減
- *:相乘
- /:相除 (回傳浮點數)
- div:整數相除(回傳整數)
- mod:取餘數
以下是 Pascal 的邏輯運算子:
- not:Bitwise negation (unary)
- and:Bitwise and
- or:Bitwise or
- xor:Bitwise xor
- shl:Bitwise shift to the left
- shr:Bitwise shift to the right
- <<:Bitwise shift to the left (same as shl)
- >>:Bitwise shift to the right (same as shr)
下面列出了 Pascal 語言支援的布林運算子。
- not:logical negation (unary)
- and:logical and
- or:logical or
- xor:logical xor
關係運算子用來比較兩純量間的大小關係。以下是 Pascal 的關係運算子:
- =:相等
- <>:不相等
- >:大於
- >=:大於或等於
- <:小於
- <=:小於或等於
字串運算子:
- +:String concatenation (joins two strings together)
集合 (set) 運算子:
- +:union
- -:difference set
- *:intersection
- ><:symmetrical difference
- <=:contains
- include:add an item to the set
- exclude:delete an item in the set
- in:checks if the item is in the set
類別 (Class) 運算子:
- is:checks whether the object is of a certain class
- as:performs a conditional type cast (conditional typecasting)
Decision Making
Pascal 使用 if 與 case 來進行條件判斷。
下面是使用 if 的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Checking;
var
a : integer;
begin
a := 100;
if ( a < 20 ) then
writeln('a is less than 20')
else
writeln('a is not less than 20');
writeln('Exact value of a is: ', a );
end.
下面是使用 case 的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program checkCase;
var
grade: char;
begin
grade := 'A';
case (grade) of
'A' : writeln('Excellent!');
'B', 'C': writeln('Well done');
'D' : writeln('You passed');
else
writeln('You really did not study right!');
end;
writeln('Your grade is ', grade);
end.
You are given a date in the format YYYY-MM-DD.
Write a program to convert it into binary date.
Example:
Input: 2025-07-26
Output: 11111101001-111-11010
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program CustomDateFormatCheck;
uses
SysUtils, StrUtils;
var
InputDateString: string;
MyDate: TDateTime;
FS: TFormatSettings;
SplitArray: array of string;
begin
if (paramCount() >= 1) then
InputDateString := paramStr(1)
else
Exit;
FS := DefaultFormatSettings;
FS.ShortDateFormat := 'yyyy-mm-dd';
FS.DateSeparator := '-';
{ Check the date format }
if not TryStrToDate(InputDateString, MyDate, FS) then
Writeln(InputDateString, ' does not match the yyyy-mm-dd format.')
else
begin
SplitArray := SplitString(InputDateString, '-');
WriteLn('Output: ',
Dec2Numb(StrToInt(SplitArray[0]), 1, 2), '-',
Dec2Numb(StrToInt(SplitArray[1]), 1, 2), '-',
Dec2Numb(StrToInt(SplitArray[2]), 1, 2));
end;
end.
Loops
Pascal 支援 while-do, for-do 以及 repeat-until 迴圈。使用 break 跳出迴圈,以及 continue 停止目前的動作繼續下一個迴圈, 並且支援 goto。
下面是使用 while-do 的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program whileLoop;
var
a: integer;
begin
a := 10;
while a < 20 do
begin
writeln('value of a: ', a);
a := a + 1;
end;
end.
下面是使用 for-do 的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program demo;
uses
SysUtils;
var
num: integer;
begin
for num := 9 downto 1 do
WriteLn(num);
end.
下面是使用 for-do 寫一個九九乘法表的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program demo;
uses
SysUtils;
var
StrBuf: string;
num1, num2 : integer;
count : integer;
begin
for num1 := 1 to 9 do
begin
for num2 := 1 to 9 do
begin
count := num1 * num2;
StrBuf := Format('%d x %d = %2d', [num1, num2, count]);
WriteLn(StrBuf);
end;
end;
end.
下面是使用 repeat-until 的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program repeatUntilLoop;
var
a: integer;
begin
a := 10;
repeat
writeln('value of a: ', a);
a := a + 1
until a = 20;
end.
Write a program that displays the digits from 1 to n then back down to 1; for instance, if n = 5, the program should display 123454321. You are permitted to use only a single for loop. The range is 0 < n < 10.
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program number;
uses sysutils;
var
num: integer;
begin
if (paramCount()) >= 1 then
num := StrToInt(paramStr(1))
else
Exit;
if (num < 1) or (num > 9) then
begin
WriteLn('Out of range.');
Exit;
end;
case (num) of
1 : WriteLn('1');
2 : WriteLn('121');
3 : WriteLn('12321');
4 : WriteLn('1234321');
5 : WriteLn('123454321');
6 : WriteLn('12345654321');
7 : WriteLn('1234567654321');
8 : WriteLn('123456787654321');
9 : WriteLn('12345678987654321');
else
WriteLn('Please input 0 < n < 10');
end;
end.
下面改寫為使用 while 迴圈:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program number;
uses sysutils;
var
num: integer;
positive: integer;
count: integer;
begin
if (paramCount()) >= 1 then
num := StrToInt(paramStr(1))
else
Exit;
if (num < 1) or (num > 9) then
begin
WriteLn('Out of range.');
Exit;
end;
positive := 1;
count := 0;
while true do
begin
if (positive = 1) then
begin
count := count + 1;
Write(count);
if (count = num) then
begin
positive := 0;
continue;
end;
end
else
begin
count := count - 1;
if (count > 0) then
Write(count)
else
break;
end;
end;
WriteLn();
end.
Functions 和 Procedures
Pascal 提供以下的子程式:
- 函數 (functions) − these subprograms return a single value.
- 程序 (procedures) − these subprograms do not return a value directly.
Pascal 支援巢狀函數(Nested Functions),可以在函數內定義另外一個函數。
函數定義的一般形式如下:
function name(argument(s): type1; argument(s): type2; ...): function_type;
local declarations;
begin
...
< statements >
...
name:= expression;
end;
程序定義的一般形式如下:
procedure name(argument(s): type1, argument(s): type 2, ... );
< local declarations >
begin
< procedure body >
end;
為了使用參考傳遞參數(Call by Reference,而不是使用 Call by Value),Pascal 允許定義 variable parameters。 這是透過在參數前加上關鍵字 var 來實現的。
procedure swap(var x, y: integer);
var
temp: integer;
begin
temp := x;
x:= y;
y := temp;
end;
Arrays
一維陣列的型別宣告的一般形式為:
type
array-identifier = array[index-type] of element-type;
下面是使用的例子:
type
vector = array [ 1..25] of real;
var
velocity: vector;
在 Pascal 語言中,陣列索引可以是任何純量型別,例如整數、布林值、列舉型別或子範圍型別,但不能是實數 (real)。陣列索引也可以是負值。
通常情況下,字元和布林值的儲存方式是每個字元或布林值佔用一個儲存單元(也就是一個 word,通常為 4 bytes)一樣, 這稱為非封裝資料儲存模式 (unpacked mode)。如果字元儲存在連續的位元組中,則可以充分利用儲存空間。這稱為封裝資料儲存模式 (packed mode)。 Pascal 允許陣列資料以封裝模式儲存。
封裝的陣列使用關鍵字 packed array 而不是 array 來宣告。例如:
type
pArray: packed array[index-type1, index-type2, ...] of element-type;
var
a: pArray;
Pascal 支援 dynamic array,也就是宣告的時候不指定陣列大小,而是使用 SetLength 來設定陣列的大小。
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Example;
var
arr : Array of Integer;
begin
SetLength(arr, 0);
end.
下面是人類猜數字的小遊戲:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program guessAB;
uses sysutils;
(*
* function genAnswer: to generate an answer
*)
function genAnswer(): string;
var
fmt : string;
anumber : Longint;
myanswer: string[4];
begin
fmt := '%.4D';
while True do
begin
anumber := 1 + Random(9999);
myanswer := Format(fmt, [anumber]);
// In Pascal, string indexing typically starts at 1.
if (myanswer[1] <> myanswer[2]) and (myanswer[1] <> myanswer[3]) and
(myanswer[1] <> myanswer[4]) and (myanswer[2] <> myanswer[3]) and
(myanswer[2] <> myanswer[4]) and (myanswer[3] <> myanswer[4]) then
break;
end;
result := myanswer;
end;
(*
* function getA: to get a value
*)
function getA(myguess: string; myanswer: string): Integer;
var
len1, len2, index, count: Integer;
begin
count := 0;
len1 := Length(myguess);
len2 := Length(myanswer);
if (len1 <> len2) then
Exit(0);
for index := 1 to len1 do
begin
if (myguess[index] = myanswer[index]) then
count := count + 1;
end;
result := count;
end;
(*
* function getB: to get b value
*)
function getB(myguess: string; myanswer: string): Integer;
var
len1, len2, index1, index2, count: Integer;
begin
count := 0;
len1 := Length(myguess);
len2 := Length(myanswer);
if (len1 <> len2) then
Exit(0);
for index1 := 1 to len1 do
begin
for index2 := 1 to len2 do
begin
if (index1 <> index2) then
begin
if (myguess[index1] = myanswer[index2]) then
count := count + 1;
end;
end;
end;
result := count;
end;
var
answer: string[4];
guess: string[4];
avalue, bvalue: integer;
(*
* main procedure
*)
begin
Randomize;
{ generate the answer }
answer := genAnswer();
while True do
begin
Write('Please input your guess: ');
ReadLn(guess);
if (Length(guess) <> 4) then
begin
WriteLn('Invalid input!');
WriteLn();
continue;
end;
avalue := getA(guess, answer);
bvalue := getB(guess, answer);
WriteLn('Result: A = ', avalue, ', B = ', bvalue);
if (avalue = 4) and (bvalue = 0) then
begin
WriteLn('Game is completed.');
break;
end;
end;
end.
下面是電腦猜數字的小遊戲:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program guessAB;
uses sysutils;
(*
* function getA: to get a value
*)
function getA(myguess: string; myanswer: string): Integer;
var
len1, len2, index, count: Integer;
begin
count := 0;
len1 := Length(myguess);
len2 := Length(myanswer);
if (len1 <> len2) then
Exit(0);
for index := 1 to len1 do
begin
if (myguess[index] = myanswer[index]) then
count := count + 1;
end;
result := count;
end;
(*
* function getB: to get b value
*)
function getB(myguess: string; myanswer: string): Integer;
var
len1, len2, index1, index2, count: Integer;
begin
count := 0;
len1 := Length(myguess);
len2 := Length(myanswer);
if (len1 <> len2) then
Exit(0);
for index1 := 1 to len1 do
begin
for index2 := 1 to len2 do
begin
if (index1 <> index2) then
begin
if (myguess[index1] = myanswer[index2]) then
count := count + 1;
end;
end;
end;
result := count;
end;
var
i, j, k, m : integer;
index: Integer;
avalue, bvalue : integer;
aguess, bguess : integer;
tempstring: string;
myanswer: string[4];
count, newcount: integer;
total, newtotal: array of string[4];
(*
* main procedure
*)
begin
SetLength(total, 5040);
count := 0;
for i := 0 to 9 do
begin
for j := 0 to 9 do
begin
for k := 0 to 9 do
begin
for m := 0 to 9 do
begin
if (i <> j) and (i <> k) and (i <> m) and
(j <> k) and (j <> m) and (k <> m) then
begin
tempstring := IntToStr(i) + IntToStr(j) +
IntToStr(k) + IntToStr(m);
total[count] := tempstring;
count := count + 1;
end;
end;
end;
end;
end;
while True do
begin
if (count = 0) then
begin
WriteLn('Something is wrong.');
break;
end;
myanswer := total[0];
WriteLn('My answer is ', myanswer, '.');
Write('The a value is: ');
ReadLn(avalue);
Write('The b value is: ');
ReadLn(bvalue);
if (avalue = 4) and (bvalue = 0) then
begin
WriteLn('Game is completed.');
break;
end;
SetLength(newtotal, count);
newcount := 0;
for index := 0 to count do
begin
aguess := getA(total[index], myanswer);
bguess := getB(total[index], myanswer);
if (aguess = avalue) and (bguess = bvalue) then
begin
newtotal[newcount] := total[index];
newcount := newcount + 1;
end;
end;
total := newtotal;
count := newcount;
WriteLn();
end;
end.
Pointers
指標是一種動態變數,其值指向另一個變數的位址,即記憶體位置的直接位址。與任何變數或常數一樣, 必須先宣告指標才能使用它來儲存任何變數的位址。指標變數宣告的一般形式為:
type
ptr-identifier = ^base-variable-type;
下面是使用的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program exPointers;
var
number: integer;
iptr: ^integer;
begin
number := 100;
writeln('Number is: ', number);
iptr := @number;
writeln('iptr points to a value: ', iptr^);
iptr^ := 200;
writeln('Number is: ', number);
writeln('iptr points to a value: ', iptr^);
end.
其中 @ 就是取得變數位址的運算子。Pascal 也有 Null pointer 的設計:稱為 NIL, 如果不知道要賦值的確切位址,可以將指標變數賦值為 NIL。
在 Free Pascal 中,函數指標是一個儲存程序或函數記憶體位址的變數。 它們使用程序或函數宣告 ,並使用位址運算子 @ 賦值。
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Demo;
type
TIntFunction = function(A, B: Integer): Integer;
function Add(X, Y: Integer): Integer;
begin
Result := X + Y;
end;
var
FuncPtr: TIntFunction;
ReturnValue: Integer;
begin
FuncPtr := @Add;
ReturnValue := FuncPtr(1, 2);
WriteLn('Result: ', ReturnValue);
end.
Records
記錄(Record)是 Pascal 中的使用者自訂型別,允許使用者組合不同類型的資料項目。 若要定義記錄型別,可以使用型別宣告語句。記錄型別的定義如下:
type
record-name = record
field-1: field-type1;
field-2: field-type2;
...
field-n: field-typen;
end;
下面是使用的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program exRecords;
type
Books = record
title: packed array [1..50] of char;
author: packed array [1..50] of char;
subject: packed array [1..100] of char;
book_id: longint;
end;
var
Book1, Book2: Books;
(* Declare Book1 and Book2 of type Books *)
begin
(* book 1 specification *)
Book1.title := 'C Programming';
Book1.author := 'Nuha Ali ';
Book1.subject := 'C Programming Tutorial';
Book1.book_id := 6495407;
(* book 2 specification *)
Book2.title := 'Telecom Billing';
Book2.author := 'Zara Ali';
Book2.subject := 'Telecom Billing Tutorial';
Book2.book_id := 6495700;
(* print Book1 info *)
writeln ('Book 1 title : ', Book1.title);
writeln('Book 1 author : ', Book1.author);
writeln( 'Book 1 subject : ', Book1.subject);
writeln( 'Book 1 book_id : ', Book1.book_id);
writeln;
(* print Book2 info *)
writeln ('Book 2 title : ', Book2.title);
writeln('Book 2 author : ', Book2.author);
writeln( 'Book 2 subject : ', Book2.subject);
writeln( 'Book 2 book_id : ', Book2.book_id);
end.
Pascal 可以使用成員存取運算子 (.) 存取記錄的成員。這樣每次都需要輸入記錄變數的名稱,with 語句提供了一種替代方法。
With Book1 do
begin
title := 'C Programming';
author := 'Nuha Ali ';
subject := 'C Programming Tutorial';
book_id := 6495407;
end;
Variants
Borland 在 Pascal 加入了一種名為 variant 的獨特儲存型別,使用者可以將任何純值型別的值賦給 variant 變數。 儲存在 variant 中的值的類型僅在運行時確定。幾乎所有純值型別都可以賦給 variant:ordinal types, string, int64。
結構化型別(例如集合、記錄、陣列、檔案、物件和類別)與 variant 不相容。最後,使用者也可以將指標賦給 variant。
宣告 variants 型別的語法如下:
var
v: variant;
下面是使用的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program exVariant;
uses variants;
type
color = (red, black, white);
var
v : variant;
i : integer;
r: real;
c : color;
astr : ansistring;
begin
i := 100;
v := i;
writeln('Variant as Integer: ', v);
r := 234.345;
v := r;
writeln('Variant as real: ', v);
c := red;
v := c;
writeln('Variant as Enumerated data: ', v);
astr := ' I am an AnsiString';
v := astr;
writeln('Variant as AnsiString: ', v);
end.
Sets
集合 (set) 是相同類型元素的集合。 Pascal 允許定義集合資料型別,集合中的元素稱為其成員。 在 Pascal 中,集合元素用方括號 [] 括起來,方括號被稱為集合構造器。 Pascal 集合類型定義如下:
type
set-identifier = set of base type;
集合型別的變數定義為
var
s1, s2, ...: set-identifier;
下面是使用的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program setColors;
type
color = (red, blue, yellow, green, white, black, orange);
colors = set of color;
procedure displayColors(c : colors);
const
names : array [color] of String[7]
= ('red', 'blue', 'yellow', 'green', 'white', 'black', 'orange');
var
cl : color;
s : String;
begin
s := ' ';
for cl := red to orange do
if cl in c then
begin
if (s <> ' ') then s := s +' , ';
s := s+names[cl];
end;
writeln('[',s,']');
end;
var
c : colors;
begin
c := [red, blue, yellow, green, white, black, orange];
displayColors(c);
c := [red, blue]+[yellow, green];
displayColors(c);
c := [red, blue, yellow, green, white, black, orange] - [green, white];
displayColors(c);
c := [red, blue, yellow, green, white, black, orange]*[green, white];
displayColors(c);
c := [red, blue, yellow, green]><[yellow, green, white, black];
displayColors(c);
end.
Units
Pascal 程式可以由稱為單元 (unit) 的模組組成。一個單元可能包含若干程式碼區塊,而程式碼區塊又由變數和型別宣告、語句、流程等構成。 Pascal 內建了許多單元,並且允許程式設計師定義和編寫自己的單元,以便在後續程式中使用。
要建立一個單元,需要編寫要儲存在其中的模組或子程序,並將其儲存到副檔名為 .pas 的檔案中。 該檔案的第一行應以關鍵字 unit 開頭,後面跟著單元名稱。例如:
unit calculateArea;
下列程式會建立名為 calculateArea 的單元:
{$ifdef FPC} {$mode delphi} {$endif}
unit CalculateArea;
interface
function RectangleArea( length, width: real): real;
function CircleArea(radius: real) : real;
function TriangleArea( side1, side2, side3: real): real;
implementation
function RectangleArea( length, width: real): real;
begin
RectangleArea := length * width;
end;
function CircleArea(radius: real) : real;
const
PI = 3.14159;
begin
CircleArea := PI * radius * radius;
end;
function TriangleArea( side1, side2, side3: real): real;
var
s, area: real;
begin
s := (side1 + side2 + side3)/2.0;
area := sqrt(s * (s - side1)*(s-side2)*(s-side3));
TriangleArea := area;
end;
end.
接下來,讓我們編寫一個簡單的程序,該程序將使用我們上面定義的單位:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program AreaCalculation;
uses CalculateArea, crt;
var
l, w, r, a, b, c, area: real;
begin
clrscr;
l := 5.4;
w := 4.7;
area := RectangleArea(l, w);
writeln('Area of Rectangle 5.4 x 4.7 is: ', area:7:3);
r := 7.0;
area := CircleArea(r);
writeln('Area of Circle with radius 7.0 is: ', area:7:3);
a := 3.0;
b := 4.0;
c := 5.0;
area := TriangleArea(a, b, c);
writeln('Area of Triangle 3.0 by 4.0 by 5.0 is: ', area:7:3);
end.
Objects and Classes
物件導向程式設計的概念為將一切事物視為物件,並使用不同的物件來實現軟體。在 Pascal 語言中,有兩種結構化資料型別用於實現的物件:
- Object - allocated on the Stack
- Class - allocated on the Heap of a program
物件透過型別宣告來宣告。對象宣告的一般形式如下:
type object-identifier = object
private
field1 : field-type;
field2 : field-type;
...
public
procedure proc1;
function f1(): function-type;
end;
var objectvar : object-identifier;
Object Pascal 的存取等級分為 public, protected, private 三種,把資料宣告為 private,只能透過特定的介面來操作, 這就是物件導向的封裝(encapsulation)特性。
constructor 在初始化物件時呼叫,destructor 則在催毀物件時呼叫。
Object Pascal 類別 (class) 的定義方式與物件 (object) 幾乎相同,但它是指向物件的指針,而不是物件本身。 這表示類別分配在程式的堆積 (Heap) 上,而物件分配在堆疊 (Stack) 上。 當你宣告一個物件類型的變數時,它在堆疊上佔用的空間與物件的大小相同;而當你宣告一個類別類型的變數時, 它在堆疊上總是佔用一個指標的大小。實際的類別資料則儲存在堆積上。
類別的宣告方式與物件相同,都是使用型別宣告,其一般形式如下:
type class-identifier = class
private
field1 : field-type;
field2 : field-type;
...
public
constructor create();
procedure proc1;
function f1(): function-type;
end;
var classvar : class-identifier;
下面是使用的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program classExample;
type
Books = class
private
title : String;
price: real;
public
constructor Create(t : string; p: real);
//default constructor
procedure setTitle(t : string);
function getTitle() : String;
procedure setPrice(p : real);
function getPrice() : real;
procedure Display();
end;
var
physics, chemistry, maths: Books;
(*
* default constructor
*)
constructor Books.Create(t : string; p: real);
begin
title := t;
price := p;
end;
procedure Books.setTitle(t : string);
begin
title := t;
end;
function Books.getTitle() : String;
begin
getTitle := title;
end;
procedure Books.setPrice(p : real);
begin
price := p;
end;
function Books.getPrice() : real;
begin
getPrice := price;
end;
procedure Books.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price:5:2);
end;
begin
physics := Books.Create('Physics for High School', 10);
chemistry := Books.Create('Advanced Chemistry', 15);
maths := Books.Create('Algebra', 7);
physics.Display;
chemistry.Display;
maths.Display;
end.
繼承可以讓使用者藉由在已經有的類別上,加入新成員(資料或者是函式來定義新的類別,而不必重新設計。 其一般的定義形式如下:
type
childClas-identifier = class(baseClass-identifier)
< members >
end;
下面是使用的例子:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program inheritanceExample;
type
Books = Class
protected
title : String;
price: real;
public
constructor Create(t : String; p: real); //default constructor
procedure setTitle(t : String);
function getTitle() : String;
procedure setPrice(p : real);
function getPrice() : real;
procedure Display(); virtual;
end;
(* Creating a derived class *)
type
Novels = Class(Books)
private
author: String;
public
constructor Create(t: String); overload;
constructor Create(a: String; t: String; p: real); overload;
procedure setAuthor(a: String);
function getAuthor(): String;
procedure Display(); override;
end;
var
n1, n2: Novels;
//default constructor
constructor Books.Create(t : String; p: real);
begin
title := t;
price := p;
end;
procedure Books.setTitle(t : String); //sets title for a book
begin
title := t;
end;
function Books.getTitle() : String; //retrieves title
begin
getTitle := title;
end;
procedure Books.setPrice(p : real); //sets price for a book
begin
price := p;
end;
function Books.getPrice() : real; //retrieves price
begin
getPrice:= price;
end;
procedure Books.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price);
end;
(* Now the derived class methods *)
constructor Novels.Create(t: String);
begin
inherited Create(t, 0.0);
author:= ' ';
end;
constructor Novels.Create(a: String; t: String; p: real);
begin
inherited Create(t, p);
author:= a;
end;
procedure Novels.setAuthor(a : String); //sets author for a book
begin
author := a;
end;
function Novels.getAuthor() : String; //retrieves author
begin
getAuthor := author;
end;
procedure Novels.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price:5:2);
writeln('Author: ', author);
end;
begin
n1 := Novels.Create('Gone with the Wind');
n2 := Novels.Create('Ayn Rand','Atlas Shrugged', 467.75);
n1.setAuthor('Margaret Mitchell');
n1.setPrice(375.99);
n1.Display;
n2.Display;
end.
self 是保留字,用來表示它所在類別的實例。self 可以用來存取類別成員,也可以作為目前實例的參考。
procedure TForm1.FormCreate(Sender: TObject);
begin
// Self stands for the TForm1 class in this example
Self.Caption := 'Test program';
Self.Visible := True;
end;
介面 (interface) 的定義是為了給實現者一個用來實現的共用函數名稱。不同的實現者可以根據自身需求來實現這些介面。以下是一個介面範例:
type
Mail = Interface
Procedure SendMail;
Procedure GetMail;
end;
Report = Class(TInterfacedObject, Mail)
Procedure SendMail;
Procedure GetMail;
end;
抽象類別 (Abstract Classes) 是只能被繼承而不能用來直接生成實例,如果直接產生實例會發生編譯錯誤。 抽象類別透過在類別定義中包含符號 abstract 來指定,例如:
type
Shape = ABSTRACT CLASS (Root)
Procedure draw; ABSTRACT;
...
end;
Object Pascal 支援特性 (properties) 的使用。 使用者建立一個看起來像是欄位(可以讀取和設定)的東西,但其底層是透過呼叫 getter 和 setter 方法來實現。
type
TWebPage = class
private
FURL: string;
FColor: TColor;
function SetColor(const Value: TColor);
public
{ No way to set it directly.
Call the Load method, like Load('http://www.freepascal.org/'),
to load a page and set this property. }
property URL: string read FURL;
procedure Load(const AnURL: string);
property Color: TColor read FColor write SetColor;
end;
procedure TWebPage.Load(const AnURL: string);
begin
FURL := AnURL;
NetworkingComponent.LoadWebPage(AnURL);
end;
function TWebPage.SetColor(const Value: TColor);
begin
if FColor <> Value then
begin
FColor := Value;
// for example, cause some update each time value changes
Repaint;
// as another example, make sure that some underlying instance,
// like a "RenderingComponent" (whatever that is),
// has a synchronized value of Color.
RenderingComponent.Color := Value;
end;
end;
將類別成員或方法宣告為靜態 (static),即可在無需產生實例的情況下存取它們。 宣告為靜態的成員不能透過已實例化的類別物件存取(但靜態方法可以)。 靜態欄位對於類別型別是全域的,並且像全域變數一樣工作,但可以作用的範圍被侷限在物件內。 以下範例說明了這一概念:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program StaticExample;
type
myclass = class
num : integer; static;
end;
var
n1 : myclass;
begin
n1 := myclass.create;
n1.num := 12;
writeln(n1.num);
writeln(myclass.num);
myclass.num := myclass.num + 20;
writeln(n1.num);
end.
Generics
泛型可以將某個物件(通常是類別)的定義參數化為其他類型。Free Pascal 比 Delphi 先實作泛型, 而 Delphi 實作的語法與 Free Pasal 略有差異。Free Pascal 使用了 generic 與 specialize 關鍵字。
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
{$ifndef FPC}
{$message warn 'Delphi does not allow addition on types that are generic parameters'}
begin end.
{$endif}
uses SysUtils;
type
generic TMyCalculator<T> = class
Value: T;
procedure Add(const A: T);
end;
procedure TMyCalculator.Add(const A: T);
begin
Value := Value + A;
end;
type
TMyFloatCalculator = {$ifdef FPC}specialize{$endif} TMyCalculator<Single>;
TMyStringCalculator = {$ifdef FPC}specialize{$endif} TMyCalculator<string>;
var
FloatCalc: TMyFloatCalculator;
StringCalc: TMyStringCalculator;
begin
FloatCalc := TMyFloatCalculator.Create;
try
FloatCalc.Add(3.14);
FloatCalc.Add(1);
WriteLn('FloatCalc: ', FloatCalc.Value:1:2);
finally
FreeAndNil(FloatCalc);
end;
StringCalc := TMyStringCalculator.Create;
try
StringCalc.Add('something');
StringCalc.Add(' more');
WriteLn('StringCalc: ', StringCalc.Value);
finally
FreeAndNil(StringCalc);
end;
end.
使用 delphi 相容模式,可以修改如下:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
{$ifndef FPC}
{$message warn 'Delphi does not allow addition on types that are generic parameters'}
begin end.
{$endif}
uses SysUtils;
type
TMyCalculator<T> = class
Value: T;
procedure Add(const A: T);
end;
procedure TMyCalculator<T>.Add(const A: T);
begin
Value := Value + A;
end;
type
TMyFloatCalculator = TMyCalculator<Single>;
TMyStringCalculator = TMyCalculator<string>;
var
FloatCalc: TMyFloatCalculator;
StringCalc: TMyStringCalculator;
begin
FloatCalc := TMyFloatCalculator.Create;
try
FloatCalc.Add(3.14);
FloatCalc.Add(1);
WriteLn('FloatCalc: ', FloatCalc.Value:1:2);
finally
FreeAndNil(FloatCalc);
end;
StringCalc := TMyStringCalculator.Create;
try
StringCalc.Add('something');
StringCalc.Add(' more');
WriteLn('StringCalc: ', StringCalc.Value);
finally
FreeAndNil(StringCalc);
end;
end.
Exception handling
Free Pascal 提供了 try ... except,以及 try ... finally 語法支援 exception handling。 有點囉嗦的地方在於,使用者在使用 try ... except,以及 try ... finally 語法時無法合併使用, 所以不是其它程式語言 try ... catch/finally 的方式。
File Handling
Pascal 將檔案視為一系列組件,這些組件必須具有統一的類型。檔案的型別由組件的型別決定。檔案的型別定義為:
type
file-name = file of base-type;
在 Pascal 中,文字檔案由多行字元組成,每行以換行符號結尾。使用者可以宣告和定義此類檔案,如下所示:
type
file-name = text;
下面是從 /etc/os-release 讀取內容,然後取得 Linux Distribution Name 的範例:
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Name;
uses SysUtils, StrUtils;
const
FILENAME = '/etc/os-release';
var
MyFile: Text;
StrBuf: string;
SplitArray: array of string;
begin
if (FileExists(FILENAME) = True) then
AssignFile(MyFile, FILENAME)
else
begin
WriteLn('Not found ', FILENAME , '!');
Exit;
end;
try
try
Reset(MyFile);
while not EOF(MyFile) do
begin
Readln(MyFile, StrBuf);
SplitArray := SplitString(StrBuf, '=');
if (CompareStr(SplitArray[0], 'NAME') = 0) then
Writeln(SplitArray[1]);
end;
except
on E: Exception do
Writeln('Error accessing file: ', E.Message);
end;
finally
CloseFile(MyFile);
end;
end.
也可以使用 Free Pascal 提供的 FileStream 相關類別從 /etc/os-release 讀取內容,然後取得 Linux Distribution Name。
{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
program Name;
uses Classes, Streamex, SysUtils, StrUtils;
const
FILENAME = '/etc/os-release';
var
FileStream: TFileStream;
LineReader: TStreamReader;
ReadLine: string;
SplitArray: array of string;
begin
if (FileExists(FILENAME) = True) then
FileStream := TFileStream.Create(FILENAME, fmOpenRead)
else
begin
WriteLn('Not found ', FILENAME , '!');
Exit;
end;
try
LineReader := TStreamReader.Create(FileStream);
try
while LineReader.Eof <> True do
begin
ReadLine := LineReader.ReadLine;
SplitArray := SplitString(ReadLine, '=');
if (CompareStr(SplitArray[0], 'NAME') = 0) then
Writeln(SplitArray[1]);
end;
finally
LineReader.Free;
end;
finally
FileStream.Free;
end;
end.
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。