<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 第四章 ## 用戶自定義數據類型 Pascal 語言的一個重要特征是它能自定義數據類型。通過各種類型構造器,你可以定義自己的數據類型,如子界類型、數組類型、記錄類型、枚舉類型、指針類型和集合類型。最重要的用戶定義數據類型是類(class),類是Object Pascal的面向對象擴展部分,本書不討論這部分。 你可能會認為其它編程語言也有諸如此類的類型構造器,確實如此,但是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'; ~~~ 以下代碼則是不正確的: ~~~ var UppLetter: UpperCase; begin UppLetter := 'e'; // compile-time error ~~~ 以上代碼將導致一個編譯錯誤:**“Constant expression violates subrange bounds”。** 如果代之以下面代碼: ~~~ var UppLetter: Uppercase; Letter: Char; begin Letter :='e'; UppLetter := Letter; ~~~ Delphi 編譯會通過,但在運行時,如果你開啟了范圍檢查編譯選項(在工程選項對話框的編譯器頁設置),你將得到 Range check error (范圍檢測錯誤)信息。 注意:建議你在開發程序時開啟上述編譯選項,以使程序更健壯并易于調試。這樣即使遇上錯誤,你也會得到一個明確的信息而不是難以琢磨的行為。最終完成程序時你可以去掉這個選項,使程序運行得快一些,不過影響很小。因此我建議你開啟所有運行時的檢測選項,如溢出檢查和堆棧檢查,甚至提交程序時仍然保留它們。 ### 枚舉類型 枚舉類型又是一種自定義有序類型。在枚舉類型中,你列出所有該類型可能取的值,而不是指定現有類型的范圍。換句話說,枚舉類型是個可取值的序列。見下例: ~~~ type Colors = (Red, Yellow, Green, Cyan, Blue, Violet); Suit = (Club, Diamond, Heart, Spade); ~~~ 序列中每個值都對應一個序號,序號從0開始計數。使用Ord 函數,即可得到一個枚舉類型值的序號。例如,Ord (Diamond) 返回值1。 > 注意:枚舉類型有多種內部表示法。缺省時,Delphi 用8位表示法;如果有多于256個不同的值,則用16位表示法。還有一種32位表示法,需要與C、C++庫兼容時會用到。使用$Z 編譯指令可改變缺省設置,請求更多位的表示法。 Delphi VCL(可視控件庫)在很多地方用了枚舉類型。例如,窗體邊框類型定義如下: ~~~ type TFormBorderStyle = (bsNone, bsSingle, bsSizeable, bsDialog, bsSizeToolWin, bsToolWindow); ~~~ 當屬性值是枚舉類型時,你可以從Object Inspector顯示的下拉列表框中選值,如圖4.1所示。 **圖 4.1 Object Inspector 中的枚舉類型屬性** ![](https://box.kancloud.cn/2869c5a590e442df51c36f9b9be21cbe_201x193.png) Delphi 幫助文件中列出了各種Delphi VCL枚舉類型的可能值。你也可以通過OrdType程序(可從www.marcocantu.com下載)查看Delphi 枚舉類型、集合類型、子界類型及任何其他有序類型的取值列表。圖4.2為這個例子的輸出結果。 **圖 4.2: 程序 OrdType 顯示的枚舉類型詳細信息** ![](https://box.kancloud.cn/66306065188a6d350c51ac802358413f_451x227.png) ### 集合類型 集合類型表示一組值,該組值由集合所依據的有序類型定義。定義集合的常用有序類型不多,一般為枚舉類型或子界類型。如果子界類型取值為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 := []; ~~~ 在Delphi中,集合一般用于表示有多種選擇的標記。例如下面兩行代碼(摘自Delphi庫)聲明了一個枚舉類型,其中列出了窗口條上可選的圖標,并聲明了相應的集合類型: ~~~ type TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp); TBorderIcons = set of TBorderIcon; ~~~ 實際上,給定的窗口中可以沒有圖標,也可以有一個或多個圖標。用Object Inspector設置時(見圖4.3),雙擊屬性名,或單擊屬性左邊的加號,自行選擇,從而添加或刪除集合中的值。 **圖 4.3: Object Inspector中的集合類型屬性** ![](https://box.kancloud.cn/d6a7b6d4551937cdeebbf2957a300eb0_309x178.png) 另一個基于集合類型的屬性是字體。字體類型值可以是粗體、斜體、帶下畫線、帶刪除線等,一種字型可以既是斜體又是粗體,也可以沒有屬性,或者帶有全部的屬性。因此用集合類型來表示它。你可以象下面代碼那樣,在程序中給集合賦值: ~~~ Font.Style := []; // no style Font.Style := [fsBold]; // bold style only Font.Style := [fsBold, fsItalic]; // two styles ~~~ 你也能對一個集合進行許多不同方式的操作,包括把兩個相同類型的集合變量相加(或更準確地說,計算兩個集合變量的并集): ~~~ Font.Style := OldStyle + [fsUnderline]; // two sets ~~~ 此外,你可以通過OrdType 查閱Delphi 控件庫中定義的集合類型取值列表。OrdType 放在本書源代碼的TOOLS 目錄中。 ### 數組類型 數組類型定義了一組指定類型的元素序列,在方括號中填入下標值就可訪問數組中的元素。定義數組時,方括號也用來指定可能的下標值。例如,下面的代碼中定義了一個有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 控件庫也在往這方向靠攏。 使用數組時,你總要用標準函數Low和 High來檢測它的邊界,Low和 High返回下標的下界和上界。強烈建議使用Low和 High操作數組,特別是在循環中,因為這樣能使代碼與數組范圍無關,如果你改變數組下標的范圍聲明,Low和 High代碼不會受影響;否則,如果代碼中有一個數組下標循環體,那么當數組大小改變時你就不得不更新循環體的代碼。Low和 High將使你的代碼更易于維護、更穩定。 > 注意:順便提一下,使用Low和 High不會增加系統運行額外開銷。因為在編譯時,他們已被轉換成常數表達式,而不是實際函數調用。其他簡單的系統函數也是這樣。 Delphi主要以數組屬性的形式使用數組。我們已經在 TimeNow 例子中看到過數組屬性,也就是ListBox控件的Items 屬性。下一章討論Delphi循環時,我將向你介紹更多有關數組屬性的例子。 > 注意:Delphi 4 的Object Pascal中增加了動態數組,所謂動態數組是在運行時動態分配內存改變數組大小。使用動態數組很容易,不過我認為在這里討論這類數組不合適。你將在第八章看到對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; ~~~ 如果指針沒有值,你可以把nil 賦給它。這樣,你可以通過檢查指針是否為nil 判斷指針當前是否引用一個值。這經常會用到,因為訪問一個空指針的值會引起一個訪問沖突錯誤,也就是大家知道的“一般保護錯”(GPF)。見下例: ~~~ procedure TFormGPF.BtnGpfClick(Sender: TObject); var P: ^Integer; begin P := nil; ShowMessage (IntToStr (P^)); end; ~~~ 通過運行例GPF,或者看圖4.4,你可以看到上述這種結果。 **圖 4.4: 訪問nil指針引起的系統錯誤** ![mk:@](https://box.kancloud.cn/1218066a6fcfbc8ebc432c17b4129a02_528x246.png) 將上面程序加以修改,訪問數據就安全了。現在將一個已存在的局部變量賦給指針,指針使用就安全了,雖然如此,我還是加上了一個安全檢查語句: ~~~ procedure TFormGPF.BtnSafeClick(Sender: TObject); var P: ^Integer; X: Integer; begin P := @X; X := 100; if P <> nil then ShowMessage (IntToStr (P^)); end; ~~~ Delphi 還定義了一個Pointer 數據類型,它表示無類型的指針(就象C語言中的void* )。如果你使用無類型指針,你應該用GetMem 例程,而不是New例程,因為GetMem 例程能用于內存分配大小不確定的情況。 實際上,Delphi 中必須使用指針的情況很少,這是Delphi開發環境一個誘人的優點。雖然如此,若要進行高級編程和完全理解Delphi 對象模型,理解指針是很重要的,因為Delphi 對象模型在幕后使用了指針。 注意:雖然在Delphi中不常使用指針,但是你經常會用一個極為相似的結構--引用(references)。每個對象實例實際上是一個隱含的指針,或說是對其實際數據的引用,利用引用,你能象用其他數據類型一樣使用對象變量。 ### 文件類型 另一個Pascal特定的類型構造器是文件類型(file)。文件類型代表物理磁盤文件,無疑是Pascal語言的一個特殊類型。按下面的方式,你可以定義一個新的數據類型: ~~~ type IntFile = file of Integer; ~~~ 然后,你就能打開一個與這個結構相應的物理文件、向文件中寫入整數、或者從文件中讀取當前的值。 Pascal 文件類型的使用很直觀,而且Delphi 中也定義了一些控件用于文件保存和裝載,以及對數據流和數據庫的支持。 * * * * * ### 結束語 這一章討論了自定義數據類型,完成了對Pascal 數據類型體系的介紹,為下一章“語句”作好了準備。語句用于操作我們所定義的變量。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看