前言
在程式語言中,資料型態 (data type) 規範資料所占用的記憶體大小及合法的操作。主流的程式語言都有資料型態的概念。本文介紹 Pascal 中可見的資料型態。
Pascal 的資料型態
以下是 Pascal 中可見的資料型態:
- 純量 (scalar)
- 布林 (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 的資料型態相當完整。該語言中有關型態的概念影響了許多後來的程式語言。
布林 (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 中的字元型態的寬度:
program main;
uses
SysUtils;
type
wchar = widechar;
begin
WriteLn(Format('Size of char: %d', [SizeOf(char)]));
WriteLn(Format('Size of widechar: %d', [SizeOf(wchar)]));
end.
保留字 type
用來為型態宣告別名 (alias)。可以用領域知識重新為型態命名,讓程式碼更易讀。在本範例中,wchar
是 widechar
的別名。
整數 (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
型態。
以下程式可取得系統上的整數型態的寬度:
program main;
uses
SysUtils;
type
uint = cardinal;
u8 = byte;
u16 = word;
u32 = longword;
u64 = qword;
int = integer;
i8 = shortint;
i16 = smallint;
i32 = longint;
i64 = int64;
begin
WriteLn(format('Size of cardinal: %d', [sizeOf(uint)]));
WriteLn(format('Size of byte: %d', [sizeOf(u8)]));
WriteLn(format('Size of word: %d', [sizeOf(u16)]));
WriteLn(format('Size of longword: %d', [sizeOf(u32)]));
WriteLn(format('Size of qword: %d', [sizeof(u64)]));
WriteLn(''); (* Separator. *)
WriteLn(format('Size of integer: %d', [sizeOf(int)]));
WriteLn(format('Size of shortint: %d', [sizeOf(i8)]));
WriteLn(format('Size of smallint: %d', [sizeOf(i16)]));
WriteLn(format('Size of longint: %d', [sizeOf(i32)]));
Writeln(format('Size of int64: %d', [sizeOf(i64)]));
end.
以下是筆者在自己的電腦上試跑的結果:
Size of cardinal: 4
Size of byte: 1
Size of word: 2
Size of longword: 4
Size of integer: 2
Size of shortint: 1
Size of smallint: 2
Size of longint: 4
Size of int64: 8
在不同系統上得到的整數型態寬度可能相異,不要背誦這個結果。
以下程式可取得整數型態的範圍:
program main;
uses
sysUtils;
type
uint = cardinal;
u8 = byte;
u16 = word;
u32 = longword;
u64 = qword;
int = integer;
i8 = shortint;
i16 = smallint;
i32 = longint;
i64 = int64;
begin
WriteLn('Range of cardinal between ' + IntToStr(low(uint)) + ' and ' + IntToStr(high(uint)));
WriteLn('Range of byte between ' + IntToStr(low(u8)) + ' and ' + IntToStr(high(u8)));
WriteLn('Range of word between ' + IntToStr(low(u16)) + ' and ' + IntToStr(high(u16)));
WriteLn('Range of longword between ' + IntToStr(low(u32)) + ' and ' + IntToStr(high(u32)));
WriteLn('Range of qword between ' + IntToStr(low(u64)) + ' and ' + IntToStr(high(u64)));
WriteLn(''); (* Separator. *)
WriteLn('Range of integer between ' + IntToStr(low(int)) + ' and ' + IntToStr(high(int)));
WriteLn('Range of shortint between ' + IntToStr(low(i8)) + ' and ' + IntToStr(high(i8)));
WriteLn('Range of smallint between ' + IntToStr(low(i16)) + ' and ' + IntToStr(high(i16)));
WriteLn('Range of longint between ' + IntToStr(low(i32)) + ' and ' + IntToStr(high(i32)));
WriteLn('Range of int64 between ' + IntToStr(low(i64)) + ' and ' + IntToStr(high(i64)));
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 integer between -32768 and 32767
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 程式要在運行期解析資料的真正型態。所以,不應過度使用不定型態。