位元詩人 [Pascal] 程式設計教學:資料型態 (Data Type)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在程式語言中,資料型態 (data type) 規範資料所占用的記憶體大小及合法的操作。主流的程式語言都有資料型態的概念。本文介紹 Pascal 中可見的資料型態。

Pascal 的資料型態

以下是 Pascal 中可見的資料型態:

  • 基礎型態(primitive type)
    • 布林 (boolean)
    • 字元 (character)
    • 整數 (integer)
    • 浮點數 (floating point number)
    • 列舉 (enumeration)
  • 容器 (collection)
    • 陣列 (array)
    • 集合 (set)
  • 複合型態 (compound type)
    • 記錄 (record)
    • 物件 (object) (Object Pascal)
    • 類別 (class) (Object Pascal)
  • 指標 (pointer)
  • 不定型態 (Variant)

基礎型態代表最簡約的 (atomic) 資料型態。這些資料在概念上無法再拆分成更小的單位。Pascal 支援數種在程式語言中常見的基礎資料型態。

容器用來裝載多個相同型態的資料。Pascal 的容器相當靈活,除了用來裝載純量外,也可用來裝載複合型態,甚至組成多維度的容器,像是多維陣列。

複合型態是用來表達純量以外的資料。由於複合型態是由程式設計者自行宣告,使用者有更多的彈性,利用複合型態表達無法以純量表達的概念。原本 Pascal 的複合型態只有記錄,後來在 Object Pascal 新增物件和類別。後兩者是為了在 Pascal 中導入物件導向特性而新增的特性。

指標是為了操作記憶體而設置的語法特性,算是程式設計中特有的概念。由於指標在現實生活中沒有相對應的概念,一開始較不易上手。

在早期的高階語言中,Pascal 的資料型態相當完整。該語言中有關型態的概念影響了許多後來的程式語言。

預設型態建議表

本節根據實務考量選擇預設型態,建議在程式碼中開啟以下 Compiler Directive:

{$ifdef FPC} {$mode DELPHIUNICODE} {$endif}

由於 Pascal 具有系統語言的特性,實際上有許多內建資料型態。在撰寫一般應用程式時,不需要對資料型態斤斤計較。建議採用以下資料型態:

類別 建議型態 理由
整數 Integer 32 位元,兼顧效能與範圍(約 21 億)。
布林 Boolean 標準邏輯判斷,只有 True 與 False。
小數 Double 現代硬體優化最完整的 64 位元浮點數,精度穩定。
字串 String 自動管理記憶體的長字串。在現代環境下支援動態長度。
字元 Char 對應 String 的基礎字元單位。

實際上 String 和 Char 會因環境而異,無法適用所有字串。建議做法是儘量和 Delphi 對齊。

若需要明確的資料寬度,請自行查閱 Pascal 語言參考手冊。

型態別名

利用 Pascal 的型態別名特性,可以為一些資料型態取更容易記的別名。參考以下宣告:

{$ifndef COMMON_TYPE_INC}
{$define COMMON_TYPE_INC}

Type 
  Int = Integer;

  {$ifdef FPC}
  Int8 = Shortint;
  Int16 = Smallint;
  Int32 = Longint;
  {$endif}

  (* Int64 is builtin in
      both Delphi and Free Pascal. *)

  {$ifdef FPC}
  UInt8 = Byte;
  UInt16 = Word;
  UInt32 = Longword;
  UInt64 = QWord;
  {$endif}

  Bool = Boolean;

  {$ifndef FPC}
  UnicodeChar = WideChar;
  {$endif}

{$endif}

在 Type 區塊的上方加入我們寫好的型態宣告:

{$INCLUDE Type.inc}

之後在程式碼中就可以使用比較易記的別名來標註變數型態。

由於新版的 Delphi 已經內建固定寬度整數別名,所以將重覆的部分用 Compiler Directive 包起來,只給 Free Pascal 用。

布林 (Boolean)

布林型態用來表達布林數,其值只有 true 和 false。Pascal 的布林數可再細分為以下數種型態:

  • 預設型態
    • boolean:8 位元
  • 相容於帶號整數
    • boolean8:8 位元
    • boolean16:16 位元
    • boolean32:32 位元
    • boolean64:64 位元 (視系統支援而定)
  • 相容於無號整數
    • bytebool:8 位元
    • wordbool:16 位元
    • longbool:32 位元
    • qwordbool:64 位元 (視系統支援而定)

在一般情形下,使用第一種 boolean 型態來表達布林數即可。後面數種布林型態是為了和 C 相容而設置的。

字元 (Character)

字元型態用來表達單個字母 (letter) 或符號 (symbol)。在 Pascal 中有兩種字元型態:

  • Char:8 位元,同 AnsiChar
  • AnsiChar:8 位元
  • WideChar:16 位元

Char (或 AnsiChar) 使用 ASCII 編碼,適用於純英語環境。而 WideChar 以 UTF-16 編碼,用於多國語言文字。

透過以下程式可取得 Pascal 中的字元型態的寬度:

{$ifdef FPC} {$mode DELPHIUNICODE} {$endif}

{$ifdef WINDOWS} {$apptype CONSOLE} {$endif}

Program Main;

Uses
SysUtils;

{$INCLUDE Type.inc}

Begin
  WriteLn(Format('Size of Char: %d', [SizeOf(Char)]));
  WriteLn(Format('Size of WideChar: %d', [SizeOf(UnicodeChar)]));
End.

相容性考量

Char 的實際內容會因環境而異。在現代 Delphi 中為 2 變數組的 Unicode;在 FPC 中則依編碼設定而異。

考量相容性,建議將 String / Char 限定在 ASCII 字元,碰到 Unicode 字完時改用 UnicodeString / UnicodeChar。

由於 Delphi 沒有 UnicodeChar 型態,建議參考先前的型態別名,將 UnicodeChar 對應到 WideChar。

我們來看一個用字元接住字串的範例:

{$ifdef FPC} {$mode DELPHIUNICODE} {$endif}

{$ifdef WINDOWS} {$apptype CONSOLE} {$endif}

program Main;

{$INCLUDE Type.inc}

var
  s: UnicodeString;
  c: UnicodeChar;

begin
  s := '你好,世界';
  c := s[1];
  Assert('你' = c);

  {$ifdef DEBUG} readln; {$endif}
end.

Pascal 的字串是 1 index,但 C / C++ 的 0 index 相異。這裡用 c := s[1] 把字元接比來,然後確認字元。

整數 (Integer)

整數型態用來表達整數。在 Pascal 中的整數型態可依是否有帶正負號分為兩大類:

  • 無號整數 (unsigned integer)
    • cardinal:16 位元或 32 位元,依系統而定
    • byte:8 位元
    • word:16 位元
    • longword:32 位元
    • qword:64 位元 (視系統支援而定)
  • 帶號整數 (signed integer)
    • integer:16 位元或 32 位元,依系統而定
    • shortint:8 位元
    • smallint:16 位元
    • longint:32 位元
    • int64:64 位元 (視系統支援而定)

在電腦程式中細分整數型態的目的是節約系統資源,能用寬度小的整數型態就不用寬度大的。早期的電腦運算資源相對昂貴,所以會對運算資源斤斤計較。

此外,Pascal 的整數型態的寬度是固定的,當我們想要使用特定範圍的整數資料時,就可以選取相對應的整數型態。

但對一般使用來說,不需要分那麼細。現在的個人電腦相對於電腦程式來說,運算資源算是相當充沛。無號整數用 cardinal,帶號整數用 integer 即可。

使用預設模式 (fpc mode) 編譯 Pascal 程式時,integer 對應到 smallint 型態。使用 Object Pascal 模式 (objfpc mode) 或 Delphi 模式 (delphi mode) 編譯 Pascal 程式時,integer 對應到 longint 型態。

以下程式可取得系統上的整數型態的寬度:

{$ifdef FPC} {$mode DELPHIUNICODE} {$endif}

{$ifdef WINDOWS} {$apptype CONSOLE} {$endif}

Program Main;

Uses
SysUtils;

{$INCLUDE Type.inc}

Begin
  WriteLn(format('Size of Cardinal: %d', [sizeOf(Cardinal)]));
  WriteLn(format('Size of Byte: %d', [sizeOf(UInt8)]));
  WriteLn(format('Size of Word: %d', [sizeOf(UInt16)]));
  WriteLn(format('Size of LongWord: %d', [sizeOf(UInt32)]));
  WriteLn(format('Size of Qword: %d', [sizeof(UInt64)]));

  WriteLn('');  (* Separator. *)

  WriteLn(format('Size of Integer: %d', [sizeOf(Int)]));
  WriteLn(format('Size of ShortInt: %d', [sizeOf(Int8)]));
  WriteLn(format('Size of SmallInt: %d', [sizeOf(Int16)]));
  WriteLn(format('Size of LongInt: %d', [sizeOf(Int32)]));
  Writeln(format('Size of Int64: %d', [sizeOf(Int64)]));
End.

以下是筆者在自己的電腦上試跑的結果:

Size of Cardinal: 4
Size of Byte: 1
Size of Word: 2
Size of LongWord: 4
Size of Qword: 8

Size of Integer: 4
Size of ShortInt: 1
Size of SmallInt: 2
Size of LongInt: 4
Size of Int64: 8

在不同系統上得到的整數型態寬度可能相異,不要背誦這個結果。

以下程式可取得整數型態的範圍:

{$ifdef FPC} {$mode DELPHIUNICODE} {$endif}

{$ifdef WINDOWS} {$apptype CONSOLE} {$endif}

Program Main;

Uses
SysUtils;

{$INCLUDE Type.inc}

Begin
  WriteLn('Range of Cardinal between ' + IntToStr(low(Cardinal)) + ' and ' + IntToStr(high(Cardinal)
  ));
  WriteLn('Range of Byte between ' + IntToStr(low(UInt8)) + ' and ' + IntToStr(high(UInt8)));
  WriteLn('Range of Word between ' + IntToStr(low(UInt16)) + ' and ' + IntToStr(high(UInt16)));
  WriteLn('Range of LongWord between ' + IntToStr(low(UInt32)) + ' and ' + IntToStr(high(UInt32)));
  WriteLn('Range of Qword between ' + IntToStr(low(UInt64)) + ' and ' + IntToStr(high(UInt64)));

  WriteLn('');  (* Separator. *)

  WriteLn('Range of Integer between ' + IntToStr(low(Int)) + ' and ' + IntToStr(high(Int)));
  WriteLn('Range of ShortInt between ' + IntToStr(low(Int8)) + ' and ' + IntToStr(high(Int8)));
  WriteLn('Range of SmallInt between ' + IntToStr(low(Int16)) + ' and ' + IntToStr(high(Int16)));
  WriteLn('Range of LongInt between ' + IntToStr(low(Int32)) + ' and ' + IntToStr(high(Int32)));
  WriteLn('Range of Int64 between ' + IntToStr(low(Int64)) + ' and ' + IntToStr(high(Int64)));
End.

以下是筆者在自己的電腦上試跑的結果:

Range of Cardinal between 0 and 4294967295
Range of Byte between 0 and 255
Range of Word between 0 and 65535
Range of LongWord between 0 and 4294967295
Range of Qword between 0 and 18446744073709551615

Range of Integer between -2147483648 and 2147483647
Range of ShortInt between -128 and 127
Range of SmallInt between -32768 and 32767
Range of LongInt between -2147483648 and 2147483647
Range of Int64 between -9223372036854775808 and 9223372036854775807

了解整數型態的範圍是重要的,因為電腦和數學還是有一段差距。用電腦進行運算時,會因超出範圍導致溢位 (overflow) 或下溢 (underflow)。Free Pascal 編譯器可開啟邊界檢查 (range check),在整數越界時引發錯誤。

如果需要更大範圍的整數時,就要使用大數運算函式庫。大數運算是以軟體模擬的,不受到硬體的限制,但速度較慢。

浮點數 (Floating Point Number)

浮點數型態用來表達數學上的小數。依據其資料寬度,可分為以下數種:

  • real:視系統而定
  • single:32 位元
  • double:64 位元
  • extended:80 位元
  • comp:64 位元
  • currency:64 位元

一般情形下,用 real 型態即可。若需要特定精確度才改用其他型態。

以下程式可取得系統上的浮點數型態的寬度:

program main;

uses
  SysUtils;

begin
  WriteLn(Format('Size of real: %d', [SizeOf(real)]));
  WriteLn(Format('Size of single: %d', [SizeOf(single)]));
  WriteLn(Format('Size of double: %d', [SizeOf(double)]));
  WriteLn(Format('Size of extended: %d', [SizeOf(extended)]));
  WriteLn(Format('Size of comp: %d', [SizeOf(comp)]));
end.

以下是在筆者的主機上試跑的結果:

Size of real: 8
Size of single: 4
Size of double: 8
Size of extended: 8
Size of comp: 8

可發現其實好幾個浮點數型態的寬度是相同的。

字串 (String)

Pascal 的字串是字元陣列。又再細分為以下數種型態:

  • string:依模式而定
  • ShortString:有長度限制,有頁碼 (code page)
  • AnsiString:無長度限制,有頁碼
  • RawByteString:無長度限制,無頁碼
  • UTF8String:無長度限制,指定頁碼為 UTF8

string 是預設的字串型態,實際的型態會依模式而變。若在原始碼中加入編譯器指示詞 {$H+} 時,string 為 AnsiString。反之,在預設模式或加入指示詞 {$H-} 時,string 為 ShortString。

ShortString 是長度最長為 255 的字元陣列。但尾端沒有 null,和 C 字串相異。未指定頁碼時,該型態會自動使用系統預設的頁碼。

AnsiString 長度沒有限制,尾端有 null 字元,類似於 C 字串。未指定頁碼時,該型態也會自動使用系統預設的頁碼。

RawByteString 和 UTF8String 本質上仍是 AnsiString。這兩種型態等同於以下型態宣告:

type
  RawByteString = AnsiString(CP_NONE);
  UTF8String = AnsiString(CP_UTF8);

自訂範圍型態 (Subrange)

Pascal 可以自訂資料的範圍,像是以下的片段宣告 age 型態,該型態的範圍為 0 至 150:

type
  age = 0..150;

自訂範圍型態的好處是讓編譯器幫我們檢查資料是否符合範圍,在開發時期提早發現錯誤。

列舉 (Enumeration)

列舉型態用來表達離散 (discrete)、有限數量的 (finite) 資料。這類資料在程式中當成符號來用,其內部數字不是重點。以下是實例:

type
  direction = (NORTH, SOUTH, EASH, WEST);

在這個 Pascal 片段中,我們宣告了列舉型態 direction,該型態有四個值。

Pascal 的列舉型態是獨立的型態,和整數型態是相異的。也就是說,Pascal 的列舉型態具有型態安全 (type safety)。相對來說,C 語言的列舉型態無法和整數型態區分,不具有型態安全。

陣列 (Array)

陣列型態是線性 (linear)、連續 (continuous)、同質的 (homogeneous) 容器 (collection) 或資料結構 (data structure)。陣列中的元素在記憶體中會連續排列,可透過索引值 (index) 快速存取。

以下 Pascal 片段建立一個長度為 10 的一維陣列,該陣列的元素型態為整數,範圍從 1 至 10:

var
  arr : array[1..10] of integer;

Pascal 陣列可以自訂範圍,不一定要從 1 開始。

除了一維陣列外,也可以建立多維陣列。以下 Pascal 片段建立二維陣列:

var
  mtx : array[0..5, 0..3] of real;

除了以整數為索引值外,還可以用其他的型態。像是以下 Pascal 片段以列舉型態當成陣列的索引值:

type
  direction = (NORTH, SOUTH, EASH, WEST);

var
  directions : array[direction] of integer;

集合 (Set)

集合型態用來表達數學上的集合。以下的 Pascal 片段建立以 char (字元) 為元素的集合:

var
  letters : set of char;

同樣地,我們可以用其他型態來建立集合。像是以下的片段建立以列舉為元素的集合:

type
  direction = (NORTH, SOUTH, EASH, WEST);

var
  directions : set of direction;

記錄 (Record)

記錄是使用者自訂型態,用來建立無法以純量表達的資料。例如,以下 Pascal 片段建立 TPoint 型態,用來表達平面座標的點 (point):

type
  TPoint = record
    x : real;
    y : real;
  end;

程式語言在開發時,無法預先知道所有的需求。讓程式語言的使用者 (即程式設計師) 有自訂型態的彈性是相當重要的。Pascal 算是早期就支援這項特性的語言。

物件 (Object) 和類別 (Class)

Object Pascal 限定

Pascal 在剛問世時,物件導向程式設計的概念還不興盛,所以當時 Pascal 未在語言中加入物件導向相關的特性。雖然我們可以用記錄寫擬物件,和真正的物件導向程式還是有一些差別。

物件和類別是為了讓 Pascal 支援物件導向程式才加進去的型態,兩者的差別是記憶體層級相異。實務上,物件甚少使用,幾乎都是使用類別來寫物件導向程式。

指標 (Pointer)

指標型態的資料用來儲存記憶體的位址 (address)。該型態是用來操作記憶體的語言特性。由於指標在現實生活中沒有相對應的概念,一開始要花一點時間來適應指標的使用方式。

不定型態 (Variant)

不定型態可用來儲存任意的純量或指標。在 Pascal 這類靜態型態語言中,不定型態讓程式更有彈性。但不定型態的資料處理起來會比靜態型態資料來得慢,這是因為 Pascal 程式要在運行期解析資料的真正型態。所以,不應過度使用不定型態。

關於作者

位元詩人 (ByteBard) 是資訊領域碩士,專注於從探索到產品的開發過程,並以工具驅動的方式改善專案。喜歡以開源專案作為成果,回饋社群。

主要方向包括:自用工具的打磨 (dogfooding)、編譯器技術在工具開發中的應用,以及將研究轉化為可維護的開源成果。

除了技術之外,也喜歡日本料理和黑咖啡,偶爾自助旅行,將生活中的靈感融入技術隨筆。