2025/12/18

Object Pascal Lazarus

Object Pascal 的自由軟體實作 Free Pascal 最流行的開發環境選擇為 Lazarus IDE。 openSUSE 可以使用 zypper 安裝已經編譯好的套件,預設使用的 backend 為 GTK2。

Lazarus IDE 使用 Lazarus Component Library 提供了一個統一的介面, Lazarus Component Library 包裝了各作業平台不同的 GUI toolkit。


下面是從 source code 自己編譯的方式,作業環境為 openSUSE,backend 要使用 Qt6,需要已安裝 Qt6 (版本 >= 6.2) 相關檔案, 並且已下載 source code package 並且解壓縮以後放置檔案到 lazarus-src。

在 lazarus-src 目錄下執行指令:

cd lcl/interfaces/qt6/cbindings
qmake6
make
sudo make install

會安裝 libQt6Pas.so, libQt6Pas.so.6, libQt6Pas.so.6.2 與 libQt6Pas.so.6.2.10(前面三個檔案為檔案連結)。

編譯 Lazarus IDE(backend 使用 Qt6):

make clean LCL_PLATFORM=qt6 bigide

安裝 Lazarus IDE:

make INSTALL_PREFIX=/home/danilo/Programs/lazarus install

執行 Lazarus,選單選擇 Package > Install/Uninstall Packages
檢查 AnchorDockingDsgn 是否有安裝,如果沒有那麼就安裝此套件。安裝後選擇 Rebuild IDE。

Rebuild IDE 成功後,重新啟動 Lazarus,選單選擇 Tools > Options,如果有 Docking / Anchordocking 選項表示安裝成功。 然後允許或者取消選項(看使用者是否要使用 single window mode)。

參考連結

Object Pascal Containers

Object Pascal 的自由軟體實作 Free Pascal 目前有幾個通用的 containers 實作:

  • Generics.Collections (since FPC >= 3.2.0)
  • FGL unit
  • GVector unit (together in fcl-stl)

一般而言建議使用 Generics.Collections,因為相容於 Delphi 與 Free Pascal。 Generis.Collections 單元中最重要的類別有:

  • TList
    A generic list of types.
  • TObjectList
    A generic list of object instances. It can "own" children, which means that it will free them automatically.
  • TDictionary
    A generic dictionary.
  • TObjectDictionary
    A generic dictionary, that can "own" the keys and/or values.

下面是 TList 的例子。

{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program demo;

uses
  Generics.Collections,
  SysUtils;

var
  MyList: TList<string>;
  S: string;

begin
  MyList := TList<string>.Create;
  try
    MyList.Add('Apple');
    MyList.Add('Banana');
    MyList.Add('Cherry');

    writeln('Items in the list:');
    for S in MyList do
      writeln(S);

    writeln('First item: ' + MyList[0]);

  finally
    MyList.Free;
  end;
end.

下面是電腦猜數字的小遊戲:

{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program guessAB;

uses Generics.Collections, 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;

(*
 * procedure genSolutions: to generate solutions
 *)
procedure genSolutions(var solutions: TList<string>);

var
  i, j, k, m : integer;
  tempstring : string;
begin
  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);
            solutions.Add(tempstring);
          end;
        end;
      end;
    end;
  end;
end;

var   
  index : Integer;
  avalue, bvalue : integer;
  aguess, bguess : integer;
  myanswer : string[4];
  total : TList<string>;

(*
 * main procedure
 *)
begin
  total := TList<string>.Create;
  genSolutions(total);

  while True do
    begin
      if (total.Count = 0) then
        begin
          WriteLn('Something is wrong.');
          break;
        end;

      myanswer := total[total.Count - 1];
      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;

      for index := total.Count - 1 downto 0 do
        begin
          aguess := getA(total[index], myanswer);
          bguess := getB(total[index], myanswer);

          if (aguess <> avalue) or (bguess <> bvalue) then
              total.Delete(index);
        end;

      WriteLn();
    end;

    total.Free;
end.

下面是 TObjectList 的例子。

{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program Apples;

uses SysUtils, Generics.Collections;

type
  TApple = class
    Name: string;
  end;

  TAppleList = TObjectList<TApple>;

var
  A: TApple;
  Apples: TAppleList;

begin
  Apples := TAppleList.Create(true);
  try
    A := TApple.Create;
    A.Name := 'my apple';
    Apples.Add(A);

    A := TApple.Create;
    A.Name := 'another apple';
    Apples.Add(A);

    Writeln('Count: ', Apples.Count);
    Writeln(Apples[0].Name);
    Writeln(Apples[1].Name);
  finally
    FreeAndNil(Apples)
  end;
end.

請注意,某些操作需要比較兩個項目,例如排序和搜尋(例如,透過 Sort 和 IndexOf 方法)。 Generics.Collections 使用 comparer 來實現這一點。預設 comparer 適用於所有類型, 甚至適用於記錄(在這種情況下,它會比較記憶體內容,至少對於使用 IndexOf 進行搜尋而言,這是一個合理的預設值)。

{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program Apples;

uses SysUtils, Generics.Defaults, Generics.Collections;

type
  TApple = class
    Name: string;
  end;

  TAppleList = TObjectList<TApple>;

function CompareApples(constref Left, Right: TApple): Integer;
begin
  Result := AnsiCompareStr(Left.Name, Right.Name);
end;

type
  TAppleComparer = TComparer<TApple>;

var
  A: TApple;
  L: TAppleList;

begin
  L := TAppleList.Create(true);
  try
    A := TApple.Create;
    A.Name := '11';
    L.Add(A);

    A := TApple.Create;
    A.Name := '33';
    L.Add(A);

    A := TApple.Create;
    A.Name := '22';
    L.Add(A);

    L.Sort(TAppleComparer.Construct({$ifdef FPC}@{$endif} CompareApples));

    Writeln('Count: ', L.Count);
    Writeln(L[0].Name);
    Writeln(L[1].Name);
    Writeln(L[2].Name);
  finally
    FreeAndNil(L)
  end;
end.

參考連結

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.

參考資料

2025/12/17

Object Pascal SQLdb

Object Pascal 的自由軟體實作 Free Pascal 可以使用 SQLdb 套件存取 RDBMS(支援的資料庫包含 Oracle, MSSQL, MySQL, PostgreSQL, Firebird, SQLite 等, 以及通用的介面 ODBC) 。

下面連線到 PostgreSQL 取得版本資訊的程式:

{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program MyProgram;

uses 
SysUtils, SQLdb, PQConnection;


var 
  Conn: TSQLConnection;
  Transaction1: TSQLTransaction;
  Query1: TSQLQuery;

begin
  Conn := TPQConnection.Create(nil);
  try
    try
      Conn.DatabaseName := 'danilo';
      Conn.HostName := 'localhost';
      Conn.UserName := 'danilo';
      Conn.Password := 'danilo';
      Conn.Params.Add('port=5432');

      // Open the connection
      Conn.Connected := True;

      Transaction1 := TSQLTransaction.Create(nil);
      Transaction1.DataBase := Conn;

      Query1 := TSQLQuery.Create(nil);
      Query1.DataBase := Conn;
      Query1.Transaction := Transaction1;

      // Example query execution
      Transaction1.StartTransaction;
      Query1.SQL.Text := 'SELECT version() as version';
      Query1.Open;

      while not Query1.EOF do
        begin          
          WriteLn(Query1.FieldByName('version').AsString);
          Query1.Next;
        end;

      Query1.Close;
      Transaction1.Commit;
      Conn.Connected := False;

    except
      on E: Exception do
            writeln('An error occurred: ', E.Message);
    end;

  finally
    // Clean up resources
    if Assigned(Query1) then Query1.Free;
    if Assigned(Transaction1) then Transaction1.Free;
    if Assigned(Conn) then Conn.Free;
  end;
end.

我在編譯時遇到 "crtbegin.o" not found 的問題, 這是 /etc/fpc.cfg 設定的 C runtime library 的路徑找不到檔案(通常是因為 GCC 版本更新)。 解決方法是搜尋 crtbegin.o 的路徑,然後更新 /etc/fpc.cfg 的設定。

#ifdef cpux86_64
-Fl/usr/lib64/gcc/x86_64-suse-linux/15
#endif

下面是使用 ODBC 連線到 PostgreSQL 取得版本資訊的程式:

{$ifdef FPC} {$mode delphi} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program MyProgram;

uses 
SysUtils, SQLdb, ODBCConn;


var 
  Conn: TSQLConnection;
  Transaction1: TSQLTransaction;
  Query1: TSQLQuery;

begin
  Conn := TODBCConnection.Create(nil);
  try
    try
      Conn.Params.Add('DSN=PostgreSQL');
      Conn.Params.Add('UID=danilo');
      Conn.Params.Add('PWD=danilo');

      // Open the connection
      Conn.Connected := True;

      Transaction1 := TSQLTransaction.Create(nil);
      Transaction1.DataBase := Conn;

      Query1 := TSQLQuery.Create(nil);
      Query1.DataBase := Conn;
      Query1.Transaction := Transaction1;

      // Example query execution
      Transaction1.StartTransaction;
      Query1.SQL.Text := 'SELECT version() as version';
      Query1.Open;

      while not Query1.EOF do
        begin          
          WriteLn(Query1.FieldByName('version').AsString);
          Query1.Next;
        end;

      Query1.Close;
      Transaction1.Commit;
      Conn.Connected := False;

    except
      on E: Exception do
            writeln('An error occurred: ', E.Message);
    end;

  finally
    // Clean up resources
    if Assigned(Query1) then Query1.Free;
    if Assigned(Transaction1) then Transaction1.Free;
    if Assigned(Conn) then Conn.Free;
  end;
end.

參考連結

2025/12/13

Object Pascal Regular Expression

Object Pascal 的自由軟體實作 Free Pascal 內建 Regular Expression 的支援,實作的部份在 RegExpr unit。

下面是使用 Free Pascal 解 1-9 位數不重複印出來的練習問題:

{$ifdef FPC} {$mode delphi} {$endif}

{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program MyNumber;

uses sysutils, math, RegExpr;

var 
  num : integer;
  max : longint;
  index: longint;
  numstr: string;
  RegexObj: TRegExpr;

begin

  Write('Please give a number: ');
  ReadLn(num);

  if (num < 1) or (num > 9) then
    begin
      WriteLn('Out of range.');
      Exit;
    end;

  max := round(intpower(10.0,  num)) - 1;

  RegexObj := TRegExpr.Create;
  RegexObj.Expression := '1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0';

  try
    for index := 1 to max do
      begin
        numstr := IntToStr(index);
        if RegexObj.Exec(numstr) then
          continue
        else
          WriteLn(numstr);
      end;
  finally
    RegexObj.Free;
  end;
end.

參考連結

2025/11/29

Boost.JSON

Boost.JSON 是一個 C++ JSON parser 函式庫, 提供了雖然不是最快但是也足夠快的執行效率、以及雖然不是最方便但是足以滿足使用者需要的便利使用方式, 就綜合條件來說,我認為是十分優秀的 C++ JSON parser 函式庫。 他有二個使用方式,第一種需要連結函式庫:

#include <boost/json.hpp>

第二種是 header-only:

#include <boost/json/src.hpp>

下面是從一個字串分析 JSON 的測試:

#include <boost/json.hpp>
#include <iostream>
#include <string>

namespace json = boost::json;

int main() {
    const std::string json_str = R"(
        {
            "user": "johndoe",
            "id": 12345,
            "active": true,
            "numbers": [1, 2, 3, 4, 5]
        }
    )";

    // Parse the JSON string
    json::value data = json::parse(json_str);

    // Access the values
    std::string username = json::value_to<std::string>(data.at("user"));
    int user_id = json::value_to<int>(data.at("id"));
    bool is_active = json::value_to<bool>(data.at("active"));

    std::cout << "Username: " << username << std::endl;
    std::cout << "ID: " << user_id << std::endl;
    std::cout << "Active: " << (is_active ? "Yes" : "No") << std::endl;

    // For array
    json::array &arr = data.at("numbers").as_array();
    std::vector<int> numbers;
    for (auto const &value : arr) {
        numbers.push_back(json::value_to<int>(value));
    }

    std::cout << "Parsed Numbers: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(parse)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS json)

add_executable(parse parse.cpp)
target_link_libraries(parse PRIVATE Boost::json)

如果採用 header-only 的方式,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(parse)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(parse parse.cpp)
target_link_libraries(parse)

下面是建立 JSON 內容的測試:

#include <boost/json.hpp>
#include <iostream>
#include <string>

namespace json = boost::json;

int main() {
    // Create a JSON object
    json::object obj;
    obj["user"] = "johndoe";
    obj["id"] = 12345;
    obj["active"] = true;

    // Create a JSON array
    json::array numbers;
    numbers.push_back(1);
    numbers.push_back(2);
    numbers.push_back(3);
    numbers.push_back(4);
    numbers.push_back(5);

    obj["numbers"] = numbers;

    // Serialize the object to a string
    std::string serialized_json = json::serialize(obj);

    std::cout << "Generated JSON: " << serialized_json << std::endl;

    return 0;
}

參考連結

Asio C++ Library

Asio C++ Library 是一個免費、開放原始碼、跨平台的 C++ 網路程式庫。 它為開發者提供一致的非同步 I/O 模型(包含 Timer、File、Pipe、Serial Port 以及網路協定 TCP, UDP 與 ICMP), Boost.Asio 在 20 天的審查後,於 2005 年 12 月 30 日被 Boost 函式庫接納。 目前 Asio C++ Library 提供二種函式庫,一種可以獨立使用的 Asio C++ library,一種是與 Boost 函式庫整合的 Boost.Asio, 二種函式庫的核心相同,差別在於 Boost.Asio 跟隨 Boost 函式庫的發佈時程(這表示當 bugs 修正的時候, 有時候會慢一點才會隨著 Boost 的新版更正)。因為已經有安裝 Boost 函式庫,所以我使用的是 Boost.Asio。

Asio 在設計上使用 Proactor pattern。 Proactor 是一種用於事件處理的軟體設計模式,其中耗時較長的活動在非同步部分運行(在 Asio 就是 I/O 處理的部份)。 非同步部分終止後,會呼叫完成處理程序。 所有使用 asio 的程式都需要至少一個 I/O execution context,例如 io_context 或 thread_pool 物件。 I/O execution context 提供對 I/O 功能的存取。如果是非同步的操作,那麼需要實作 completion handler 來提供工作完成之後的通知目標。

下面是一個測試的程式,來自 Asio 教學網頁的 Using a timer synchronously。 boost::asio::io_context 就是執行 I/O 的部份。

#include <boost/asio.hpp>
#include <iostream>

int main() {
    boost::asio::io_context io;

    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(3));
    t.wait();

    std::cout << "Hello, world!" << std::endl;

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(timer)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(timer timer.cpp)

Using a timer asynchronously

使用 asio 的非同步功能意味著需要一個 completion token,該 token 決定了非同步操作完成後如何將結果傳遞給完成處理程序。 在這裡使用 print 函數,該函數將在非同步等待結束後被呼叫。

務必記住,在呼叫 boost::asio::io_context::run() 之前,要先給 io_context 一些工作。 如果沒指定一些工作(在本例中是 steady_timer::async_wait()),boost::asio::io_context::run() 會立即返回。

#include <boost/asio.hpp>
#include <iostream>

void print(const boost::system::error_code & /*e*/) {
    std::cout << "Hello, world!" << std::endl;
}

int main() {
    boost::asio::io_context io;

    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(3));
    t.async_wait(&print);

    io.run();

    return 0;
}

Binding arguments to a completion handler

要使用 asio 實作重複定時器,需要在完成處理程序中更改定時器的過期時間,然後啟動新的非同步等待。 這意味著 completion handler 需要能夠存取定時器物件。

#include <boost/asio.hpp>
#include <functional>
#include <iostream>

void print(const boost::system::error_code & /*e*/,
           boost::asio::steady_timer *t, int *count) {
    if (*count < 5) {
        std::cout << *count << std::endl;
        ++(*count);

        t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
        t->async_wait(
            std::bind(print, boost::asio::placeholders::error, t, count));
    }
}

int main() {
    boost::asio::io_context io;

    int count = 0;
    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
    t.async_wait(
        std::bind(print, boost::asio::placeholders::error, &t, &count));

    io.run();

    std::cout << "Final count is " << count << std::endl;

    return 0;
}

Using a member function as a completion handler

std::bind 函式對類別成員函式和函式同樣有效。由於所有非靜態類別成員函數都有一個隱式的 this 參數,我們需要將 this 綁定到函數上。 std::bind 將我們的 completion handler(現在是成員函數)轉換為函數對象。

#include <boost/asio.hpp>
#include <functional>
#include <iostream>

class printer {
public:
    printer(boost::asio::io_context &io)
        : timer_(io, boost::asio::chrono::seconds(1)), count_(0) {
        timer_.async_wait(std::bind(&printer::print, this));
    }

    ~printer() { std::cout << "Final count is " << count_ << std::endl; }

    void print() {
        if (count_ < 5) {
            std::cout << count_ << std::endl;
            ++count_;

            timer_.expires_at(timer_.expiry() +
                              boost::asio::chrono::seconds(1));
            timer_.async_wait(std::bind(&printer::print, this));
        }
    }

private:
    boost::asio::steady_timer timer_;
    int count_;
};

int main() {
    boost::asio::io_context io;
    printer p(io);
    io.run();

    return 0;
}

Synchronising completion handlers in multithreaded programs

strand class template 是 executor adapter,它保證透過它分發的處理程序,在下一個處理程序啟動之前, 目前正在執行的處理程序必須完成。無論呼叫 boost::asio::io_context::run() 的執行緒數是多少,此保證都有效。 當然,這些處理程序仍然可能與其他未透過 strand 分發的處理程序,或透過不同 strand 物件分發的處理程序並發執行。


#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <thread>

class printer {
public:
    printer(boost::asio::io_context &io)
        : strand_(boost::asio::make_strand(io)),
          timer1_(io, boost::asio::chrono::seconds(1)),
          timer2_(io, boost::asio::chrono::seconds(1)), count_(0) {
        timer1_.async_wait(boost::asio::bind_executor(
            strand_, std::bind(&printer::print1, this)));

        timer2_.async_wait(boost::asio::bind_executor(
            strand_, std::bind(&printer::print2, this)));
    }

    ~printer() { std::cout << "Final count is " << count_ << std::endl; }

    void print1() {
        if (count_ < 10) {
            std::cout << "Timer 1: " << count_ << std::endl;
            ++count_;

            timer1_.expires_at(timer1_.expiry() +
                               boost::asio::chrono::seconds(1));

            timer1_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&printer::print1, this)));
        }
    }

    void print2() {
        if (count_ < 10) {
            std::cout << "Timer 2: " << count_ << std::endl;
            ++count_;

            timer2_.expires_at(timer2_.expiry() +
                               boost::asio::chrono::seconds(1));

            timer2_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&printer::print2, this)));
        }
    }

private:
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
    boost::asio::steady_timer timer1_;
    boost::asio::steady_timer timer2_;
    int count_;
};

int main() {
    boost::asio::io_context io;
    printer p(io);
    std::thread t([&] { io.run(); });
    io.run();
    t.join();

    return 0;
}

File

Linux io_uring 在 Kernel 5.1 加入,其主要目標是透過高效率的非同步 I/O 框架,解決傳統 I/O 模型中系統呼叫和上下文切換的效能瓶頸, 移除傳統同步I/O 與 epoll 就緒通知模型需要頻繁切換使用者空間與核心空間的負擔,進而大幅提升系統在處理大量並發 I/O 操作時的效能。 liburing 是 Jens Axboe 維護的輔助函式庫,其主要目的是簡化 io_uring 的使用。 Asio 對於 Linux liburing 提供了包裝(目前需要使用者使用 flag 啟用),下面是我測試的程式, 讀取 /etc/os-release 取得 Linux Distribution Name:

#include <boost/asio.hpp>
#include <boost/asio/stream_file.hpp>
#include <filesystem>
#include <iostream>
#include <vector>

namespace asio = boost::asio;
namespace fs = std::filesystem;

std::vector<std::string> split(const std::string &str,
                               const std::string &delim) {
    std::vector<std::string> tokens;
    size_t prev = 0, pos = 0;
    do {
        pos = str.find(delim, prev);
        if (pos == std::string::npos)
            pos = str.length();
        std::string token = str.substr(prev, pos - prev);
        if (!token.empty())
            tokens.push_back(token);
        prev = pos + delim.length();
    } while (pos < str.length() && prev < str.length());

    return tokens;
}

void read_next_line(asio::stream_file &file, asio::streambuf &buffer) {
    asio::async_read_until(file, buffer, '\n',
                           [&](const boost::system::error_code &ec,
                               std::size_t bytes_transferred) {
                               if (!ec) {
                                   std::istream is(&buffer);
                                   std::string line;
                                   std::getline(is, line);

                                   auto splitArray = split(line, "=");
                                   if (splitArray[0].compare("NAME") == 0) {
                                       std::cout << splitArray[1] << std::endl;
                                   } else {
                                       read_next_line(file, buffer);
                                   }
                               } else if (ec == asio::error::eof) {
                                   std::cout << "End of file reached."
                                             << std::endl;
                               } else {
                                   std::cerr
                                       << "Error reading file: " << ec.message()
                                       << std::endl;
                               }
                           });
}

int main() {
    fs::path test_file_path = "/etc/os-release";

    asio::io_context io_context;

    boost::system::error_code ec_open;
    asio::stream_file file(io_context);
    file.open(test_file_path.string(), asio::stream_file::read_only, ec_open);

    if (ec_open) {
        std::cerr << "Failed to open file: " << ec_open.message() << std::endl;
        return 1;
    }

    asio::streambuf buffer;
    read_next_line(file, buffer);

    io_context.run();
    file.close();

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(name)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(PkgConfig REQUIRED)
pkg_check_modules(uring REQUIRED IMPORTED_TARGET liburing)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(name name.cpp)
target_link_libraries(name PRIVATE PkgConfig::uring)
target_compile_definitions(name PRIVATE BOOST_ASIO_HAS_IO_URING BOOST_ASIO_DISABLE_EPOLL)

Tcp

A synchronous TCP daytime client

我們需要將作為參數傳遞給應用程式的伺服器名稱轉換為 TCP 端點。為此,我們使用 ip::tcp::resolver 物件。 resolver 接收主機名稱和服務名,並將它們轉換為端點列表。 程式接下來建立並連接 Socket。上面獲得的端點列表可能同時包含 IPv4 和 IPv6 端點,因此我們需要逐一嘗試,直到找到可用的端點。 這樣可以確保客戶端程式與特定的 IP 版本無關。boost::asio::connect() 函數會自動執行此操作。

#include <array>
#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints =
           resolver.resolve(argv[1], "daytime");

        asio::ip::tcp::socket socket(io_context);
        asio::connect(socket, endpoints);

        for (;;) {
            std::array<char, 128> buf;
            boost::system::error_code error;

            size_t len = socket.read_some(asio::buffer(buf), error);

            if (error == asio::error::eof)
                break; // Connection closed cleanly by peer.
            else if (error)
                throw boost::system::system_error(error); // Some other error.

            std::cout.write(buf.data(), len);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

A synchronous TCP daytime server

需要建立一個 ip::tcp::acceptor 物件來監聽新連線。它被初始化為監聽 TCP 連接埠 13,支援 IP 版本 6。

#include <boost/asio.hpp>
#include <ctime>
#include <iostream>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

int main() {
    try {
        asio::io_context io_context;

        asio::ip::tcp::acceptor acceptor(
            io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v6(), 13));

        for (;;) {
            asio::ip::tcp::socket socket(io_context);
            acceptor.accept(socket);

            std::string message = make_daytime_string();

            boost::system::error_code ignored_error;
            asio::write(socket, boost::asio::buffer(message), ignored_error);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

An asynchronous TCP daytime server

#include <boost/asio.hpp>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

class tcp_connection : public std::enable_shared_from_this<tcp_connection> {
public:
    typedef std::shared_ptr<tcp_connection> pointer;

    static pointer create(asio::io_context &io_context) {
        return pointer(new tcp_connection(io_context));
    }

    asio::ip::tcp::socket &socket() { return socket_; }

    void start() {
        message_ = make_daytime_string();

        asio::async_write(socket_, asio::buffer(message_),
                          std::bind(&tcp_connection::handle_write,
                                    shared_from_this(),
                                    asio::placeholders::error,
                                    asio::placeholders::bytes_transferred));
    }

private:
    tcp_connection(asio::io_context &io_context) : socket_(io_context) {}

    void handle_write(const boost::system::error_code & /*error*/,
                      size_t /*bytes_transferred*/) {}

    asio::ip::tcp::socket socket_;
    std::string message_;
};

class tcp_server {
public:
    tcp_server(asio::io_context &io_context)
        : io_context_(io_context),
          acceptor_(io_context,
                    asio::ip::tcp::endpoint(asio::ip::tcp::v6(), 13)) {
        start_accept();
    }

private:
    void start_accept() {
        tcp_connection::pointer new_connection =
            tcp_connection::create(io_context_);

        acceptor_.async_accept(new_connection->socket(),
                               std::bind(&tcp_server::handle_accept, this,
                                         new_connection,
                                         asio::placeholders::error));
    }

    void handle_accept(tcp_connection::pointer new_connection,
                       const boost::system::error_code &error) {
        if (!error) {
            new_connection->start();
        }

        start_accept();
    }

    asio::io_context &io_context_;
    asio::ip::tcp::acceptor acceptor_;
};

int main() {
    try {
        asio::io_context io_context;
        tcp_server server(io_context);
        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

下面是我的練習程式,將 client 改寫為 asynchronous:

#include <boost/asio.hpp>
#include <iostream>
#include <vector>

namespace asio = boost::asio;

const int BUFFER_SIZE = 128;

void handle_read(const boost::system::error_code &error,
                 std::size_t bytes_transferred, asio::ip::tcp::socket &socket,
                 std::vector<char> &buffer) {
    if (!error) {
        for (std::size_t i = 0; i < bytes_transferred; ++i) {
            std::cout << buffer[i];
        }
    } else {
        std::cerr << "Error during read: " << error.message() << std::endl;
    }
}

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints =
            resolver.resolve(argv[1], "daytime");

        asio::ip::tcp::socket socket(io_context);
        asio::connect(socket, endpoints);

        std::vector<char> buffer(BUFFER_SIZE);
        socket.async_read_some(asio::buffer(buffer),
                               std::bind(handle_read, std::placeholders::_1,
                                         std::placeholders::_2,
                                         std::ref(socket), std::ref(buffer)));

        io_context.run();
        socket.close();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

UDP

A synchronous UDP daytime client

我們使用 ip::udp::resolver 物件,根據主機名稱和服務名稱尋找要使用的正確遠端端點。 透過 ip::udp::v6() 參數,查詢被限制為僅傳回 IPv6 端點。 如果 ip::udp::resolver::resolve()函數沒有失敗,則保證至少會傳回清單中的一個端點。這意味著直接解引用回傳值是安全的。

#include <boost/asio.hpp>
#include <array>
#include <iostream>

namespace asio = boost::asio;

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::udp::resolver resolver(io_context);
        asio::ip::udp::endpoint receiver_endpoint =
            *resolver.resolve(asio::ip::udp::v6(), argv[1], "daytime").begin();

        asio::ip::udp::socket socket(io_context);
        socket.open(asio::ip::udp::v6());

        std::array<char, 1> send_buf = {{0}};
        socket.send_to(asio::buffer(send_buf), receiver_endpoint);

        std::array<char, 128> recv_buf;
        asio::ip::udp::endpoint sender_endpoint;
        size_t len =
            socket.receive_from(asio::buffer(recv_buf), sender_endpoint);

        std::cout.write(recv_buf.data(), len);
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

A synchronous UDP daytime server

#include <boost/asio.hpp>
#include <array>
#include <ctime>
#include <iostream>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

int main() {
    try {
        asio::io_context io_context;

        asio::ip::udp::socket socket(
            io_context, asio::ip::udp::endpoint(asio::ip::udp::v6(), 13));

        for (;;) {
            std::array<char, 1> recv_buf;
            asio::ip::udp::endpoint remote_endpoint;
            socket.receive_from(asio::buffer(recv_buf), remote_endpoint);

            std::string message = make_daytime_string();

            boost::system::error_code ignored_error;
            socket.send_to(asio::buffer(message), remote_endpoint, 0,
                           ignored_error);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

An asynchronous UDP daytime server

#include <boost/asio.hpp>
#include <array>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

class udp_server {
public:
    udp_server(asio::io_context &io_context)
        : socket_(io_context,
                  asio::ip::udp::endpoint(asio::ip::udp::v6(), 13)) {
        start_receive();
    }

private:
    void start_receive() {
        socket_.async_receive_from(
            asio::buffer(recv_buffer_), remote_endpoint_,
            std::bind(&udp_server::handle_receive, this,
                      asio::placeholders::error,
                      asio::placeholders::bytes_transferred));
    }

    void handle_receive(const boost::system::error_code &error,
                        std::size_t /*bytes_transferred*/) {
        if (!error) {
            std::shared_ptr<std::string> message(
                new std::string(make_daytime_string()));

            socket_.async_send_to(
                asio::buffer(*message), remote_endpoint_,
                std::bind(&udp_server::handle_send, this, message,
                          asio::placeholders::error,
                          asio::placeholders::bytes_transferred));

            start_receive();
        }
    }

    void handle_send(std::shared_ptr<std::string> /*message*/,
                     const boost::system::error_code & /*error*/,
                     std::size_t /*bytes_transferred*/) {}

    asio::ip::udp::socket socket_;
    asio::ip::udp::endpoint remote_endpoint_;
    std::array<char, 1> recv_buffer_;
};

int main() {
    try {
        asio::io_context io_context;
        udp_server server(io_context);
        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

下面是我的練習程式,將 client 改寫為 asynchronous:

#include <array>
#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;

class udp_client {
public:
    udp_client(asio::io_context &io_context, const std::string &host)
        : socket_(io_context) {
        asio::ip::udp::resolver resolver(io_context);
        remote_endpoint_ =
            *resolver.resolve(asio::ip::udp::v6(), host, "daytime").begin();

        socket_.open(asio::ip::udp::v6());

        start_send();
    }

private:
    void start_send() {
        socket_.async_send_to(asio::buffer(send_buffer_), remote_endpoint_,
                              std::bind(&udp_client::handle_send, this,
                                        asio::placeholders::error,
                                        asio::placeholders::bytes_transferred));
    }

    void handle_send(const boost::system::error_code &error,
                     std::size_t /*bytes_transferred*/) {
        if (!error) {
            socket_.async_receive_from(
                asio::buffer(recv_buffer_), remote_endpoint_,
                std::bind(&udp_client::handle_receive, this,
                          asio::placeholders::error,
                          asio::placeholders::bytes_transferred));
        } else {
            std::cerr << "Error during send: " << error.message() << std::endl;
        }
    }

    void handle_receive(const boost::system::error_code &error,
                        std::size_t bytes_transferred) {

        if (!error) {
            std::cout.write(recv_buffer_.data(), bytes_transferred);
        } else {
            std::cerr << "Error during receive: " << error.message()
                      << std::endl;
        }
    }

    asio::ip::udp::socket socket_;
    asio::ip::udp::endpoint remote_endpoint_;
    std::array<char, 1> send_buffer_ = {{0}};
    std::array<char, 128> recv_buffer_;
};

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        udp_client client(io_context, argv[1]);

        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

相關連結

2025/11/07

SOCI

SOCI (Simple Oracle Call Interface) 一開始是由 Maciej Sobczak 在 CERN 工作時開發, 作為 Oracle 資料庫函式庫的 abstraction layer 並且在 CERN 的工作環境中使用,之後則又加入了數個資料庫的支援。

SOCI 目前支援 Oracle, MySQL, PostgreSQL, SQLite3 等資料庫以及 ODBC 作為通用的 backend。 下面是在 openSUSE 安裝 SOCI core 以及 SOCI SQLite3 開發檔案的指令:
sudo zypper in soci-devel soci-sqlite3-devel

(注意:SOCI 本身的體積並不大,但是安裝時需要 boost-devel,而 boost-devel 是個包含很多模組的函式庫, 如果之前就有安裝 boost-devel 那麼這就不是一個問題)

下面是一個使用 SOCI 取得 SQLite3 版本的測試程式:

#include <soci/soci.h>
#include <soci/sqlite3/soci-sqlite3.h>

#include <iostream>
#include <string>

int main() {
    try {
        soci::session sql("sqlite3", "db=:memory:");

        std::string version;
        sql << "select sqlite_version()", soci::into(version);
        std::cout << version << std::endl;
    } catch (soci::soci_error const &e) {
        std::cerr << "Failed: " << e.what() << std::endl;
    } catch (std::runtime_error const &e) {
        std::cerr << "Unexpected standard exception occurred: " << e.what()
                  << std::endl;
    } catch (...) {
        std::cerr << "Unexpected unknown exception occurred." << std::endl;
    }

    return 0;
}

使用下列的指令編譯:

g++ version.cpp -lsoci_core -lsoci_sqlite3 -o version

下面是在 openSUSE 安裝 SOCI core 以及 SOCI ODBC 開發檔案的指令:

sudo zypper in soci-devel soci-odbc-devel

下面是一個使用 SOCI ODBC 取得 PostgreSQL 版本的測試程式:

#include <soci/odbc/soci-odbc.h>
#include <soci/soci.h>

#include <iostream>
#include <string>

int main() {
    try {
        soci::session sql("odbc",
                          "DSN=PostgreSQL; UID=postgres; PWD=postgres;");

        std::string version;
        soci::rowset<std::string> rs = (sql.prepare << "select version()");

        for (soci::rowset<std::string>::const_iterator it = rs.begin();
             it != rs.end(); ++it) {
            std::cout << *it << std::endl;
        }
    } catch (soci::soci_error const &e) {
        std::cerr << "Failed: " << e.what() << std::endl;
    } catch (std::runtime_error const &e) {
        std::cerr << "Unexpected standard exception occurred: " << e.what()
                  << std::endl;
    } catch (...) {
        std::cerr << "Unexpected unknown exception occurred." << std::endl;
    }

    return 0;
}

使用下列的指令編譯:

g++ version.cpp -lsoci_core -lsoci_odbc -o version

參考連結

2025/10/30

Beyond 國語歌曲歌單

歌單的歌曲不固定,這只是我現在的歌單。

  • 大地

    收錄在 1990 年發行的專輯《大地》中。劉卓輝作詞,黃家駒作曲,黃貫中擔任主唱,粵語版為《大地》。大地來自於黃家駒看到兩岸開放,國民黨老兵回到中國大陸探親的新聞報導,結果產生了相關的靈感。歌曲的主角可以視為在台灣的國民黨老兵,或者是因為戰亂而離開中國大陸,在改革開放後又重新回到中國大陸的老一輩,是一首十分有時代意義的歌曲。

  • 漆黑的空間

    收錄在 1990 年發行的專輯《大地》中。粵語版為《灰色軌跡》。

  • 九十年代的憂傷

    收錄在 1990 年發行的專輯《大地》中。粵語版為《相依的心》。

  • 你知道我的迷惘

    收錄在 1990 年發行的專輯《大地》中。由劉卓輝重新填詞,粵語版為《真的愛你》。

  • 送給不懂環保的人(包括我)

    收錄在 1990 年發行的專輯《大地》中。粵語版為《送給不知怎去保護環境的人(包括我)》。

  • 和自己的心比賽

    收錄在 1990 年發行的專輯《大地》中。粵語版為《戰勝心魔》。

  • 破壞遊戲的孩子

    收錄在 1990 年發行的專輯《大地》中。粵語版為《衝開一切》。

  • 今天有我

    收錄在 1990 年發行的專輯《大地》中。粵語版為《秘密警察》。

  • 光輝歲月

    收錄在 1991 年發行的專輯《光輝歲月》中。周治平/何啟弘作詞,黃家駒作曲,粵語版為《光輝歲月》。 主打歌《光輝歲月》的粵語版是一首讚美南非的非洲人國民大會主席納爾遜·曼德拉的歌曲,以歌頌他在南非種族隔離時期為黑人所付出的努力, 當時曼德拉在監禁 28 年後剛被釋放,光輝歲月表達他的一生。在國語版裡面,這首《光輝歲月》是為激勵年輕人努力拼搏而作, 而當中的種族議題被淡化。

  • 午夜怨曲

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《午夜怨曲》。

  • 撒旦的咀咒

    收錄在 1991 年發行的專輯《光輝歲月》中。何惠晶作詞,黃貫中作曲,粵語版為《撒旦的詛咒》。

  • 射手座咒語

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《亞拉伯跳舞女郎》。

  • 歲月無聲

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《歲月無聲》。

  • 曾經擁有

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《曾是擁有》。

  • 心中的太陽

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《又是黃昏》。

  • 兩顆心

    收錄在 1991 年發行的專輯《光輝歲月》中。何惠晶作詞,黃家強作曲,粵語版為《兩顆心》。

  • 長城

    收錄在 1992 年發行的專輯《信念》中。詹德茂作詞,黃家駒作曲,粵語版為《長城》。喜多郎創作的前奏十分優秀,我非常喜歡的其中一首國語歌。

  • 關心永遠在

    收錄在 1992 年發行的專輯《信念》中。粵語版為《遙望》。

  • 愛過的罪

    收錄在 1992 年發行的專輯《信念》中。陳昇作詞,黃貫中作曲,粵語版為《繼續沉醉》。

  • 今天就做

    收錄在 1992 年發行的專輯《信念》中。狗毛作詞,黃家駒作曲,粵語版為《不可一世》。我非常喜歡的其中一首國語歌。

  • 農民

    收錄在 1992 年發行的專輯《信念》中。粵語版為《農民》。《農民》的內容大抵是描述一個中國農民的生活,如何在艱困的生活中逆境自強。但廣東話和國語的版本卻有著極大不同。廣東話版本是由劉卓輝填詞,是對山區農民生活的影射;而國語版則由姚若龍填詞,內容更為廣泛,是描述北方人重視固有生活的個性。這是 Beyond 創作中十分嚴肅的作品。

  • 最想念妳

    收錄在 1992 年發行的專輯《信念》中。粵語版為《早班火車》。

  • 可否衝破

    收錄在 1992 年發行的專輯《信念》中。粵語版為《可否衝破》。

  • 問自己

    收錄在 1992 年發行的專輯《信念》中。粵語版為《無語問蒼天》。

  • 年輕

    收錄在 1992 年發行的專輯《信念》中。粵語版為《快樂王國》。

  • 愛不容易說

    收錄在 1993 年發行的專輯《海闊天空》中。《海闊天空》是 Beyond 發行的第 4 張國語專輯,1993 年 6 月 24 日黃家駒發生意外昏迷,6 月 30 日逝世, 原定計畫無法實現,滾石唱片公司以這張專輯保留黃家駒原聲以及詞曲創作來完成黃家駒未完成的計畫和心願。黃家駒逝世後, 其餘 3 名成員決定進入錄音室,由黃家強和黃貫中演唱完成計畫中的國語專輯。歌曲結合粵語專輯《樂與怒》和日語專輯《This Is Love 1》。 雖然維基百科上說這是第 4 張國語專輯,但我個人感覺更像是個精選輯。

  • 身不由己

    收錄在 1993 年發行的專輯《海闊天空》中。

  • Paradise

    收錄在 1994 年發行的專輯《Paradise》中。《Paradise》是 Beyond 樂隊在主音黃家駒逝世後,三人復出發行的首張國語專輯。 《Paradise》這首歌為黃貫中作詞作曲並且擔任主唱,為懷念黃家駒之作。

  • 一輩子陪我走

    收錄在 1994 年發行的專輯《Paradise》中。《一輩子陪我走》則是以一個輕快的伴奏,高歌四子手足之情。

  • 無名英雄

    收錄在 1994 年發行的專輯《Paradise》中。鄭智化填詞,黃家強與黃貫中作曲,黃家強擔任主唱。

  • 和平世界

    收錄在 1994 年發行的專輯《Paradise》中。Michael 填詞,黃家強作曲,黃家強擔任主唱,這是一首反戰歌曲,粵語版為《超級武器》。

  • 因為有你有我

    收錄在 1994 年發行的專輯《Paradise》中。厲曼婷填詞,黃家強作曲,黃家強擔任主唱。

  • 對嗎

    收錄在 1994 年發行的專輯《Paradise》中。

  • 祝您愉快

    收錄在 1994 年發行的專輯《Paradise》中,為黃家強懷念黃家駒之作。

  • 一廂情願

    收錄在 1995 年發行的專輯《愛與生活》中。黃貫中作詞作曲,歌曲以直白的歌詞展現了單方面情感付出帶來的無奈與疲憊。

  • 唯一

    收錄在 1995 年發行的專輯《愛與生活》中。《唯一》《Love》《活得精彩》《夢》四首歌都是國語歌曲創作, 而只有《活得精彩》後來有錄製粵語版本。

  • Love

    收錄在 1995 年發行的專輯《愛與生活》中。

  • 活得精彩

    收錄在 1995 年發行的專輯《愛與生活》中。


  • 收錄在 1995 年發行的專輯《愛與生活》中。

  • 荒謬世界

    收錄在 1995 年發行的專輯《愛與生活》中。粵語版為《教壞細路》。

  • 忘記你

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 緩慢

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 候診室

    收錄在 1998 年發行的專輯《這裡那裡》中。粵語版為《不再猶豫》。我非常喜歡的其中一首國語歌。

  • 情人

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 熱情過後

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 十字路口

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • Amani

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 命運是我家

    收錄在 1998 年發行的專輯《這裡那裡》中。《這裡那裡》當中有七首是把黃家駒的作品,重新填上國語詞及編曲, 《命運是我家》這首的編曲是保留黃家駒原曲,為的是要保留黃家駒彈奏吉他的部份,是對黃家駒的敬意與尊重。