資料型態 (Data Type)

    前言

    在程式語言中,資料型態 (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)

    布林型態用來表達布林數,其值只有 truefalse。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)。可以用領域知識重新為型態命名,讓程式碼更易讀。在本範例中,wcharwidechar 的別名。

    整數 (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+} 時,stringANSIString。反之,在預設模式或加入指示詞 {$H-} 時,stringShortString

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

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

    RawByteStringUTF8String 本質上仍是 ANSIString。這兩種型態等同於以下型態宣告:

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

    自訂範圍型態 (Subrange)

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

    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 的一維陣列,該陣列的元素型態為整數,範圍從 110

    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 程式要在運行期解析資料的真正型態。所以,不應過度使用不定型態。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Yahoo
    【追蹤本站】
    Facebook Facebook Twitter Plurk