2026/02/04

Apache Geode

簡介

Apache Geode 是一個數據管理平台, 它為廣泛分佈的雲端架構中的資料密集型應用程式提供即時、一致的存取, 一般而言作為 In-Memory Data Grid (IMDG)、快取 (cache) 以及需要即時處理的場合使用。

下載 Apache Geode 以後解壓縮放到某個目錄。gfsh 為 Apache Geode 用來管理的 shell tool。

執行 gfsh。下面是在 gfsh 執行的指令,資料來自於 Apache Geode in 15 Minutes or Less 的教學。

Locator 是 Geode 行程 (processes),它告訴新連線的成員正在執行的成員在哪裡,並為伺服器使用提供負載平衡。

start locator --name=locator1

Geode 提供了 web 界面的監控界面,下面是啟動的指令。預設使用者為 admin,密碼為 admin

start pulse

Geode server 是一個行程 (process),它作為叢集中一個長期運行且可配置的成員而存在。 Geode server 主要用於託管長期運行的資料區域,以及運行標準的 Geode 行程,例如用戶端/伺服器配置中的伺服器。

start server --name=server1 --server-port=40411

Regions 是 Geode 叢集的核心建置模組,用於組織資料。在此練習中建立的 Region 採用複製機制在叢集成員之間複製數據, 並利用持久化機制將資料儲存到磁碟。

create region --name=regionA --type=REPLICATE_PERSISTENT

列出目前的 regions:

list regions

列出 Geode 叢集的成員:

list members

描述 Geode region regionA 的資料:

describe region --name=regionA

下面使用 put 新增資料以及使用 query 查詢資料。

put --region=regionA --key="1" --value="one"
put --region=regionA --key="2" --value="two"
query --query="select * from /regionA"

如果你需要刪除一個 region,可以這樣做:

destroy region --name=regionA

如果要停止 server:

stop server --name=server1

關閉系統,包括 locator。

shutdown --include-locators=true

REST

Geode 讓使用者能夠使用 REST 介面存取資料。

啟動一個 locator。

start locator --name=locator1

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

然後在啟動 Geode server 時加入 --start-rest-api 選項。

start server --name=server1 --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost

使用 curl 驗證是否可以使用:

curl -i http://localhost:8080/geode/v1

SSL

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

在 Geode 的 config 目錄下建立一個新的檔案 gfsecurity.properties。 Apache Geode 使用 ssl-enabled-components 設定不同組件間的通訊是否需要使用 SSL/TLS。 all 表示全部都要使用,這裡設定為 web,表示使用在 REST 介面。

ssl-enabled-components=web
ssl-protocols=TLSv1.2,TLSv1.3
ssl-ciphers=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384
ssl-keystore=/home/danilo/Programs/geode/config/trusted.keystore
ssl-keystore-password=password
ssl-keystore-type=pkcs12
ssl-truststore=/home/danilo/Programs/geode/config/trusted.keystore
ssl-truststore-password=password
ssl-truststore-type=pkcs12

使用 gfsh 啟動一個 locator。

start locator --name=locator1 --port=12345 \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

使用 gfsh 啟動一個 server。

start server --name=server1 --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties

使用 curl 驗證是否可以使用:

curl -k -i https://localhost:8080/geode/v1

Authentication

以下的方式在 Java 24/25 以後,因為 SecurityManager 被禁止而無法使用。 In Java 17, the Security Manager was deprecated for removal under JEP 411. With JDK 24, its functionality will be effectively disabled. So you could not setup HTTP Basic Authentication support for Apache Geode by using SecurityManager since JDK 24.

將下列的內容儲存為 security.json,並且放到各個 locator 與 server 的目錄下。

{
  "roles": [
    {
      "name": "cluster",
      "operationsAllowed": [
        "CLUSTER:MANAGE",
        "CLUSTER:WRITE",
        "CLUSTER:READ"
      ]
    },
    {
      "name": "data",
      "operationsAllowed": [
        "DATA:MANAGE",
        "DATA:WRITE",
        "DATA:READ"
      ]
    },
    {
      "name": "region1&2Reader",
      "operationsAllowed": [
        "DATA:READ"
      ],
      "regions": ["region1", "region2"]
    }
  ],
  "users": [
    {
      "name": "super-user",
      "password": "1234567",
      "roles": [
        "cluster",
        "data"
      ]
    },
    {
      "name": "joebloggs",
      "password": "1234567",
      "roles": [
        "data"
      ]
    }
  ]
}

使用 gfsh 啟動一個 locator。

start locator --name=locator1 --port=12345 \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties \
--J=-Dgemfire.security-manager=org.apache.geode.examples.security.ExampleSecurityManager \
--classpath=.

連線到 locator 需要驗證:

connect --locator=localhost[12345] --user=super-user --password=1234567

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

使用 gfsh 啟動一個 server。

start server --name=server1 --locators=localhost[12345] --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties \
--J=-Dgemfire.security-manager=org.apache.geode.examples.security.ExampleSecurityManager \
--classpath=. --user=super-user --password=1234567

Memcached

Apache Geode 提供了 memcached 協議相容的介面。

使用 gfsh 啟動一個 locator。

start locator --name=locator1

並且在 config 新增或者修改 cache.xml,使用下列的設定:

<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns="http://geode.apache.org/schema/cache" 
       xsi:schemaLocation="http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd"
       version="1.0">
  <region name="gemcached">
    <region-attributes refid="PARTITION" />
  </region>
</cache>

使用 gfsh 啟動一個 server。

start server --name=server1 --server-port=40411 \
--memcached-port=11211 --memcached-bind-address=localhost \
--memcached-protocol=BINARY \
--cache-xml-file=/home/danilo/Programs/geode/config/cache.xml

--memcached-protocol 可以設為 ASCII 或者是 BINARY。 ASCII 是 libMemcached 的預設值,如果要使用 BINARY 需要設定。

使用 memcached-for-Tcl 進行驗證。

package require Memcache

memcache server add localhost 11211
memcache behavior MEMCACHED_BEHAVIOR_BINARY_PROTOCOL 1
memcache set moo "cows go moo"
memcache get moo result
puts $result

相關連結

2026/02/02

BaseX database

BaseX 是一個使用 Java 撰寫的 XML 資料庫, 也可以作為 XQuery processor 使用,採用 BSD-3-Clause 授權, 使用 XQuery 作為其查詢語言,實作 XQuery 多項相關標準, 並且設計了自己的 Server Protocol

basexserver 預設的 port 為 1984,初次啟動需要使用 -cPASSWORD 設定 admin 的密碼。

basexserver -cPASSWORD

下面使用 basexclient 連線到 server(-U 為 username,-P 為 password)。

basexclient -Uadmin -Padmin

建立一個新的使用者 danilo,並且給予全域的 CREATE 權限(BaseX 將權限分為 global 與 local, global 有 NONE, READ, WRITE, CREATE, ADMIN 的區別,而 local 則是 NONE, READ, WRITE)。

CREATE USER danilo password
ALTER PASSWORD danilo password
GRANT CREATE TO danilo

其中 CREATE USER 可以在建立使用者的時候同時設定密碼,在之後如果要修改密碼可以使用 ALTER PASSWORD 修改。

如果要移除使用者:

DROP USER danilo

使用 basexclient 建立新的資料庫:

  • Create an empty database
    CREATE DB <database_name>
    
  • To create a database with initial data
    CREATE DB <database_name> <path/to/input>
    
  • To add multiple documents to a newly created empty database
    CREATE DB store
    ADD factbook.xml
    ADD https://files.basex.org/xml/xmark.xml
    

如果使用 XQuery 建立資料庫:

db:create('myDatabaseName', 'path/to/input.xml')

如果要列出目前的資料庫:

LIST

如果使用 XQuery 列出目前的資料庫:

db:list()

使用 basexclient 刪除資料庫:

DROP DB [name]

如果使用 XQuery 刪除資料庫:

db:drop($name)

Tools

BaseX 提供了一些工具可以使用。 basexserver 為 BaseX database server,而 basexclient 則是其 client 的程式。 basexgui 使用 Java Swing 撰寫,是 BaseX 的圖型界面管理工具,可以用來管理其資料庫以及測試一些 XQuery 語句。 basex 可以用來執行 BaseX 或者是 XQuery 的指令。

下面是一個使用 basex 的例子,用來解 1-9位數不重複印出來的練習問題,
使用者輸入1 印1-9
使用者輸入2 印1-98 (11, 22, 33等重複的不印)
使用者輸入3 印1-987 (121, 988, 667等有重複的不印)

basex -bnumber=9 -Qcount.xq

參數 -b 用來設定外部變數。count.xq 的內容如下:

declare variable $number external;
if (xs:integer($number) lt 1 or xs:integer($number) gt 9) then (
    <error>
      <message>Invalid data</message>
    </error>
) else (
    let $max := xs:integer(math:exp10($number) - 1)
    for $i in 1 to $max
    return
    if(not(matches(fn:string($i), '1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0'))) then (
        $i
    )
)

相關連結

XQuery 學習筆記

XML 簡介

XML stands for Extensible Markup Language. It is a text-based markup language derived from Standard Generalized Markup Language (SGML).

XML is a markup language that defines set of rules for encoding documents in a format that is both human-readable and machine-readable. Following example shows how XML markup looks, when embedded in a piece of text −

<message>
   <text>Hello, world!</text>
</message>

You can notice there are two kinds of information in the above example −

  • Markup (tag)
  • The text, or the character data

XML 文件可以使用樹 (tree) 來表示。一個 XML 樹開始於 root element, 並且從 root element 開始其 child elements 的分支。 XML elements 可以有屬性 (attributes),例如下面的例子:

<note date="2023/04/28">
  <name>Orange</name>
</note> 

The XML document can optionally have an XML declaration. It is written as follows −

<?xml version = "1.0" encoding = "UTF-8"?>

XPath 簡介

XPath (XML Path Language) is an expression language designed to support the query or transformation of XML documents. It was defined by the World Wide Web Consortium (W3C) in 1999.

In XPath, there are seven kinds of nodes: element, attribute, text, namespace, processing-instruction, comment, and root nodes.

The most important kind of expression in XPath is a location path. A location path consists of a sequence of location steps. Each location step has three components:

  • an axis
  • a node test
  • zero or more predicates.
Axis specifiers in XPath
Full syntax Abbreviated syntax Notes
ancestor

ancestor-or-self

attribute @ @abc is short for attribute::abc
child
xyz is short for child::xyz
descendant

descendant-or-self // // is short for /descendant-or-self::node()/
following

following-sibling

namespace

parent .. .. is short for parent::node()
preceding

preceding-sibling

self . . is short for self::node()

Node tests may consist of specific node names or more general expressions. In the case of an XML document in which the namespace prefix gs has been defined, //gs:enquiry will find all the enquiry elements in that namespace, and //gs:* will find all elements, regardless of local name, in that namespace.

Other node test formats are:

comment()
finds an XML comment node, e.g. <!-- Comment -->
text()
finds a node of type text excluding any children, e.g. the hello in <k>hello<m> world</m></k>
processing-instruction()
finds XML processing instructions such as <?php echo $a; ?>. In this case, processing-instruction('php') would match.
node()
finds any node at all.

Predicates, written as expressions in square brackets, can be used to filter a node-set according to some condition. For example, a returns a node-set (all the a elements which are children of the context node), and a[@href='help.php'] keeps only those elements having an href attribute with the value help.php.

XQuery 簡介

XQuery 是由 W3C 定義的查詢語言,程式風格為函數式程式設計 (functional programming), 建立在 XPath 的基礎上,使用 Xpath 表達要查詢的路徑資訊, 專門用於在結構化或半結構化 XML 資料中進行搜尋、操作和轉換,類似 SQL 之於關聯式資料庫。 在 XQuery 3.1 增加了對於 JSON 的支援,所以 XQuery 也可以用來處理 JSON 資料(如果你想要這樣做的話)。

目前 XQuery 的版本如下:

  • XQuery 1.0 became a W3C Recommendation on January 23, 2007
  • XQuery 3.0 became a W3C Recommendation on April 8, 2014
  • XQuery 3.1 became a W3C Recommendation on March 21, 2017

XQuery 所有用於執行計算的 XQuery 語句都是表達式 (expressions),其核心是 FLWOR,用於更複雜的查詢:

  • For: 循環存取資料項目。
  • Let: 賦值。
  • Where: 設定篩選條件。
  • Order By: 排序結果。
  • Return: 定義輸出結果。

下面是一個 XQuery 的 Hello World 例子:

let $message := 'Hello World!'
return
<results>
   <message>{$message}</message>
</results>

下面是一個 XML 檔案 books.xml:

<?xml version="1.0" encoding="UTF-8"?>
<books>
   
   <book category="JAVA">
      <title lang="en">Learn Java in 24 Hours</title>
      <author>Robert</author>
      <year>2005</year>
      <price>30.00</price>
   </book>
   
   <book category="DOTNET">
      <title lang="en">Learn .Net in 24 hours</title>
      <author>Peter</author>
      <year>2011</year>
      <price>70.50</price>
   </book>
   
   <book category="XML">
      <title lang="en">Learn XQuery in 24 hours</title>
      <author>Robert</author>
      <author>Peter</author> 
      <year>2013</year>
      <price>50.00</price>
   </book>
   
   <book category="XML">
      <title lang="en">Learn XPath in 24 hours</title>
      <author>Jay Ban</author>
      <year>2010</year>
      <price>16.50</price>
   </book>
   
</books>

XQuery 可以使用 doc() 函數取得 XML 檔案的內容。下面就是一個 XQuery 的例子:

(: XQuery Comment :)
let $books := (doc("books.xml")/books/book)
return <results>
{
   for $x in $books
   where $x/price>30
   order by $x/price
   return $x/title
}
</results>

XQuery 可以使用 for 執行迴圈任務,如下面所示:

for $n in 1 to 10
return
    <result>{$n}</result>

Sequences represent an ordered collection of items where items can be of similar or of different types. Sequences are created using parenthesis with strings inside quotes or double quotes and numbers as such. XML elements can also be used as the items of a sequence.

Viewing items in a sequence

let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')
let $count := count($sequence)
return
   <results>
      <count>{$count}</count>
      <items>
       {
         for $item in $sequence
         return
           <item>{$item}</item>
       }
      </items>
   </results>

XQuery 內建支援 Regular Expressions,下面是一個例子:

let $input := 'TutorialsPoint Simply Easy Learning'
return (
  matches($input, 'Hello') =  true(),
  matches($input, 'T.* S.* E.* L.*') =  true()
)

XQuery 使用 if-then-else 支援條件判斷。

<result>
{
   for $book in doc("books.xml")/books/book
   return
   if ($book/@category = "XML") then (
     $book/title
   )
}
</result>

XQuery 3.0 加入 lambda functions 的支援,下面是一個例子:

let $fn := function($x, $y) { $x + $y }
return $fn(99, 2)

XQuery 3.0 加入 switch 的支援,下面是一個例子:

for $fruit in ("Apple", "Pear", "Peach")
return switch ($fruit)
  case "Apple" return "red"
  case "Pear"  return "green"
  case "Peach" return "pink"
  default      return "unknown"

XQuery 3.0 加入 try catch 的支援,下面是一個例子:

try {
  1 + '2'
} catch * {
  'Error [' || $err:code || ']: ' || $err:description
}

XQuery 3.0 加入 || operator 作為 String Concatenations 使用,其實際上為 concat() 函數的快捷方式。

'Hello' || ' ' || 'Universe'

XQuery 3.0 加入 Simple Map Operator !,用於將第一個表達式的結果應用於第二個表達式,下面是一個例子:

(1 to 10) ! element node { . }

XQuery 3.1 加入 Arrow Operator operator =>,提供了一種方便的替代語法,用於將函數傳遞給值。 運算子前面的表達式將提供作為箭頭後面函數的第一個參數。

'w e l c o m e' => upper-case() => tokenize() => string-join('-')

下面則是沒有 Arrow Operator operator 之前的寫法:

string-join(tokenize(upper-case('w e l c o m e')), '-')

XQuery 3.1 加入了 Map 與 Array 支援對於 JSON 資料格式的處理。 Map 是將一組鍵與值關聯起來的函數,從而產生一組鍵/值對,用來處理 JSON 的 object。 Array 是將一組位置(以正整數表示的鍵)與值關聯起來的功能。Array 中的第一個位置對應整數 1,用來處理 JSON 的 array。

let $map := map { 'foo': 42, 'bar': 'baz', 123: 456 }
return for-each(map:keys($map), $map)
let $array := array { 48 to 52 }
for $i in 1 to array:size($array)
return $array($i)

Lookup operator 提供了一種語法糖,用於存取 Map 或 Array 元素的值。它以問號 (?) 開頭,後面跟著一個說明符。說明符可以是:

  1. A wildcard *,
  2. The name of the key,
  3. The integer offset, or
  4. Any other parenthesized expression.
let $map := map { 'R': 'red', 'G': 'green', 'B': 'blue' }
return (
  $map?*           (: returns all values; same as: map:keys($map) ! $map(.) :),
  $map?R           (: returns the value for key 'R'; same as: $map('R') :),
  $map?('G', 'B')  (: returns the values for key 'G' and 'B' :)
)
let $maps := (
  map { 'name': 'Guðrún', 'city': 'Reykjavík' },
  map { 'name': 'Hildur', 'city': 'Akureyri' }
)
return $maps[?name = 'Hildur'] ?city

XQuery 3.1 提供了 JSON Serialization,下面是一個例子:

declare option output:method 'json';
map { "key": "value" }

XQuery 3.1 使用 fn:parse-json() 執行 JSON deserialization 的工作:

let $json-input := '{ "firstName": "John", "lastName": "Smith", "address": { "city": "New York" }, "phoneNumbers": ["212-732-1234", "646-123-4567"] }'
let $json-data := fn:parse-json($json-input)
return
  $json-data

參考資料

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(前面三個檔案為檔案連結)。 預設安裝的位置的為 /usr/lib64,如果想改變安裝的位置,修改 Qt6Pas.pro 內的 target.path 並且再次執行 qmake6。

target.path = /usr/local/lib64

編譯 Lazarus IDE(backend 使用 Qt6):

make clean LCL_PLATFORM=qt6 bigide

安裝 Lazarus IDE:

sudo make INSTALL_PREFIX=/usr/local install

執行 Lazarus(指令為 lazarus-ide),在一開始使用的時候可以選擇是否使用 single window mode。

如果沒有選擇的選項,也可以自行加入。選單選擇 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;
}

相關連結