第四章 用戶自定義數據類型

Pascal 語言的一個重要特徵是它能自定義數據類型。通過各種類型構造器,你可以定義自己的數據類型,如子界類型、數組類型、記錄類型、枚舉類型、指針類型和集合類型。最重要的用戶定義數據類型是類(class),類是Object Pascal的面向對象擴展部分,本書不討論這部分。

命名及不命名的類型

爲了後續使用或直接用於變量,需要給自定義類型命名。如果自定義一個命名的類型,你必須將代碼放在特定的type區,如下所示:

type
  // subrange definition
  Uppercase = 'A'..'Z';

  // array definition
  Temperatures = array [1..24] of Integer;

  // record definition
  Date = record
    Month: Byte;
    Day: Byte;
    Year: Integer;
  end;

  // enumerated type definition
  Colors = (Red, Yellow, Green, Cyan, Blue, Violet);

  // set definition
  Letters = set of Char;

你也可使用類型定義構造器直接定義一個變量,此時無需顯式命名,如下面的代碼:

var
  DecemberTemperature: array [1..31] of Byte;
  ColorCode: array [Red..Violet] of Word;
  Palette: set of Colors;

注意:一般來說,你應該避免使用上述不命名類型,因爲你不能把它們作爲參數傳給例程,也不能用於聲名同一類型的其他變量。實際上,Pascal的類型兼容規則是基於類型名的,而不是基於實際的類型定義。兩個類型相同的變量仍有可能是不兼容的,除非他們的類型有完全相同的名字。對於不命名類型,需要編譯器給它分配一個內部名字,因此對於數據結構複雜的變量,要習慣於定義命名數據類型,你一定不會爲此白費工夫的。

但是上述自定義類型有什麼意義呢?如果你不太熟悉Pascal類型構造器,通過下面內容你會了解它,此外下面還談到了同類構造器在不同語言中的差異,因此如果你已熟知上面例舉的類型定義,不妨往下讀,你會對其中內容感興趣的。最後,我將演示一些Delphi例子,並介紹一些能動態訪問類型信息的工具。

子界類型

子界類型定義了某種類型的取值範圍(因此定名subrange)。你可定義整數類型的子界類型,如取值從1到10或從100到1000,或者定義字符類型的子界類型,如下所示:

type
  Ten = 1..10;
  OverHundred = 100..1000;
  Uppercase = 'A'..'Z';

定義子界類型時,你不需要指定基類的名字,而只需提供該類型的兩個常數。所用基類必須是有序類型,定義結果將是另一種有序類型。

如定義一個子界變量,那麼賦給該變量的值必須是子界定義範圍內的值。下面代碼是正確的:

var
  UppLetter: UpperCase;
begin
  UppLetter := 'F';
枚舉類型 

枚舉類型又是一種自定義有序類型。在枚舉類型中,你列出所有該類

型可能取的值,而不是指定現有類型的範圍。換句話說,枚舉類型是

個可取值的序列。見下例:

type
  Colors = (Red, Yellow, Green, Cyan, Blue, Violet);
  Suit = (Club, Diamond, Heart, Spade);

序列中每個值都對應一個序號,序號從0開始計數。使用Ord 函數,即可得到一個枚舉類型

值的序號。例如,Ord (Diamond) 返回值1。

集合類型

集合類型表示一組值,該組值由集合所依據的有序類型定義。定義集合的常用有序類型不多

,一般爲枚舉類型或子界類型。如果子界類型取值爲1..3,那麼基於它的集合類型值可以

是1、或2、或3、或1和2、或1和3、或2和3、或取所有3個數、或一個數也沒有。

一個變量通常包含該類型對應的一個值,而集合類型可以不包含值、包含一個值、兩個值、

三個值,或更多,它甚至可以包含定義範圍內所有的值。下面定義一個集合:

type
  Letters = set of Uppercase;

現在我可以用上面類型來定義變量,並把原始類型的值賦給變量。爲了在集合中表示一組值

,需要用逗號將值隔開,最後用方括號結尾。下例顯示了多值、單值和空值的變量賦值:

var
  Letters1, Letters2, Letters3: Letters;
begin
  Letters1 := ['A', 'B', 'C'];
  Letters2 := ['K'];
  Letters3 := [];
數組類型

數組類型定義了一組指定類型的元素序列,在方括號中填入下標值就可訪問數組中的元素。

定義數組時,方括號也用來指定可能的下標值。例如,下面的代碼中定義了一個有24個整數

的數組:

type
  DayTemperatures = array [1..24] of Integer;

在數組定義時,你需要在方括號中填入一個子界類型的值,或者用兩個有序類型的常量定義

一個新的子界類型,子界類型指定了數組的有效索引。由於子界類型指定了數組下標值的

上界和下界,那麼下標就不必象C、C++、JAVA和其他語言那樣必須從零開始。

由於數組下標基於子界類型,因此Delphi 能夠對它們進行範圍檢查。不合法的常量子界類型

將導致一個編譯時間錯誤;如果選上編譯器範圍檢查選項,那麼超出範圍的下標值將導致一個

運行時間錯誤。

使用上述數組定義方法,定義一個DayTemperatures 類型的變量如下:

type
  DayTemperatures = array [1..24] of Integer;

var  
  DayTemp1: DayTemperatures;
  
procedure AssignTemp;  
begin  
  DayTemp1 [1] := 54;
  DayTemp1 [2] := 52;
  ...
  DayTemp1 [24] := 66;
  DayTemp1 [25] := 67; // compile-time error

數組可以是多維的,如下例:

type
  MonthTemps = array [1..24, 1..31] of Integer;
  YearTemps = array [1..24, 1..31, Jan..Dec] of Integer;

這兩個數組建立在相同的核心類型上,因此你可用前面定義的數據類型聲明它們,如下面代

碼所示:

type
  MonthTemps = array [1..31] of DayTemperatures;
  YearTemps = array [Jan..Dec] of MonthTemps;

上例的聲明把索引的次序前後調換了一下,但仍允許變量之間整塊賦值。例如:把一月份的

溫度值賦給二月份:

var
  ThisYear: YearTemps;
begin
  ...
  ThisYear[Feb] := ThisYear[Jan];

你也能定義下標從零開始的數組,不過這似乎不太合邏輯,因爲你需要用下標2來訪問數組第

三項。然而,Windows一直沿用了從零開始的數組(因爲它是基於C語言的),並且Delphi

控件庫也在往這方向靠攏。

使用數組時,你總要用標準函數LowHigh來檢測它的邊界,LowHigh返回下標的下界

和上界。強烈建議使用LowHigh操作數組,特別是在循環中,因爲這樣能使代碼與數組範

圍無關,如果你改變數組下標的範圍聲明,LowHigh代碼不會受影響;否則,如果代碼中

有一個數組下標循環體,那麼當數組大小改變時你就不得不更新循環體的代碼。Low High

將使你的代碼更易於維護、更穩定。

注意:順便提一下,使用LowHigh不會增加系統運行額外開銷。因爲在編譯時,他們已被轉

成常數表達式,而不是實際函數調用。其他簡單的系統函數也是這樣。

Delphi主要以數組屬性的形式使用數組。我們已經在 TimeNow 例子中看到過數組屬性,

也就是ListBox控件的Items 屬性。下一章討論Delphi循環時,我將向你介紹更多有關數組

屬性的例子。 記錄類型

記錄類型用於定義不同類型數據項的固定集合。記錄中每個元素,或者說域,有它自己的

類型。記錄類型定義中列出了所有域,每個域對應一個域名,通過域名可以訪問它。

下面簡單列舉了記錄類型的定義、類型變量的聲明以及這類變量的使用:

type
  Date = record
    Year: Integer;
    Month: Byte;
    Day: Byte;
  end;
  
var
  BirthDay: Date;
  
begin
  BirthDay.Year := 1997;
  BirthDay.Month := 2;
  BirthDay.Day := 14;

類和對象可以看作是記錄類型的擴展。Delphi 庫趨向於用類替代記錄類型,不過Windows

API中定義了許多記錄類型。

記錄類型中允許包含variant 域,它表示多個域能公用同一內存區,而且域可以是不同類型

(這相應於C語言中的聯合union)。換句話說,你可以通過variant 域或說是一組域訪問記

錄中同一個內存位置,但是各個值仍需區別對待。variant類型主要用來存貯相似但又不同的

數據,進行與類型映射(typecasting)相似的類型轉換(自從typecasting 引入Pascal,已很

少用到這種方法了)。雖然Delphi在一些特殊情況下還在用variant 記錄類型,但是現在已經

被面向對象技術或其他現代技術代替了。

variant 記錄類型的應用不符合類型安全原則,因此不提倡在編程中使用,初學者更是如此。

實際上,專家級的編程人員確實需要用到variant 記錄類型,Delphi 庫的核心部分就用到

了這一類型。不管怎樣,除非你是個Delphi 專家,否則你應避免使用variant記錄類型。

 

指針

指針是存放指定類型(或未定義類型)變量內存地址的變量,因此指針間接引用一個值。

定義指針不需用特定的關鍵字,而用一個特殊字符,這個特殊字符是脫字符號(^),

見下例:

type
  PointerToInt = ^Integer;

一旦你定義了指針變量,你就可以用@ 符號把另一個相同類型變量的地址賦給它。

見下例:

var
  P: ^Integer;
  X: Integer;
begin
  P := @X;
  // change the value in two different ways
  X := 10;
  P^ := 20;  

如果定義了一個指針P,那麼P表示指針所指向的內存地址,而P^表示內存所存儲的實際

內容。因此,在上面的代碼中, P^ 與X相等。

除了表示已分配內存的地址外,指針還能通過New 例程在堆中動態分配內存,不過當你

不需要這個指針時,你也必須調用Dispose 例程釋放你動態分配的內存。

var
  P: ^Integer;
begin
  // initialization
  New (P);
  // operations
  P^ := 20;
  ShowMessage (IntToStr (P^));
  // termination
  Dispose (P);
end;
文件類型 

另一個Pascal特定的類型構造器是文件類型(file)。文件類型代表物理磁盤文件,無疑

是Pascal語言的一個特殊類型。按下面的方式,你可以定義一個新的數據類型:

type
  IntFile = file of Integer;

然後,你就能打開一個與這個結構相應的物理文件、向文件中寫入整數、或者從文件中

讀取當前的值。

Pascal 文件類型的使用很直觀,而且Delphi 中也定義了一些控件用於文件保存和裝載,

以及對數據流和數據庫的支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章