2025/12/18

Object Pascal 學習筆記

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.

參考資料

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。