最初的Pascal 語言是以一些簡單的概念為基礎建立起來的,這些概念現在普遍出現在編程語言中。最重要的概念當屬數據類型,數據類型決定了變量可取的值,以及可在這些值上進行的操作。Pascal 數據類型的概念強於C語言及早期的BASIC語言,在C語言中算術數據類型是可以互換的,而早期的BASIC語言中根本沒有與數據類型相似的概念。
變量Pascal 變量在使用前必須聲明,聲明變量時必須指定一種數據類型。下面是變量聲明的例子:
var Value: Integer; IsCorrect: Boolean; A, B: Char;
關鍵字var可以在許多地方使用,例如放在函數或過程的開始部分,用來聲明函數或過程的局部變量;也可以放在單元中,用於聲明全程變量。var關鍵字之后是一組變量名列表,每個變量名后跟一個冒號和數據類型名,一行中可以聲明多個變量,如上例中最后一句。
一旦變量的類型被指定,你只能對變量執行該變量類型支持的操作。例如,在判斷操作中用布爾值,在數字表達式中用整型值,你不能將布爾值和整型值混用(在C語言中可以這樣)。
使用簡單的賦值語句,可寫出下面的代碼:
Value := 10; IsCorrect := True;
但下面的語句是不正確的,因為兩個變量數據類型不同:
Value := IsCorrect; // error
在Delphi中編譯這句代碼,會出現錯誤信息:Incompatible types: 'Integer' and 'Boolean'.(類型不兼容:‘整型’和‘布爾型’)。象這樣的錯誤通常是編程錯誤,因為把一個 True 或 False 的值賦給一個整型變量沒有什么意義。你不該責怪Delphi 提示這樣的錯誤信息,代碼中有不對的地方Delphi當然要提出警告。
把變量的值從一種類型轉換到另一種類型往往不難做到,有些情況下類型轉換會自動實現,不過一般情況下需要調用特殊的系統函數,通過改變數據內部表示來實現類型轉換。
在Delphi 中,當你聲明全程變量時,你可以賦給它一個初值。例如,你可以這樣寫:
var Value: Integer = 10; Correct: Boolean = True;
這種初始化方法只能用於全程變量,不能用於過程或方法的變量。
常量對於在程序運行期間保持不變的值,Pascal 允許通過常量來聲明。聲明常量不必特定數據類型,但需要賦一個初值。編譯器會根據所賦初值自動選用合適的數據類型。例如:
const Thousand = 1000; Pi = 3.14; AuthorName = 'Marco Cantù';
Delphi 根據常量的值來決定它的數據類型。上例中的Thousand 變量,Delphi會選用SmallInt數據類型 (短整型--能容納Thousand變量的最小整數類型)。如果你想告訴Delphi 采用特定的類型,你可在聲明中加入類型名,方法如下:
const Thousand: Integer = 1000;
對於聲名的常量,編譯器有兩種編譯選擇:第一種為常量分配內存,並把常量的值放入內存;第二種在常量每次使用時復制常量值。第二種方法比較適合簡單常量。
注意:16位的Delphi 允許你在程序運行期間改變已定義的常量值,就象一個變量一樣。32位的Delphi為了向后兼容仍容許這種操作,只要你附加 $J 編譯指令,或選擇工程選項對話框中Compiler (編譯器) 頁的Assignable typed constants復選框就行。盡管如此,這里我還是要強烈建議萬不得以不要使用上述操作,因為把新值賦給常量將使編譯器不能對常量進行優化,與其如此不如直接聲明一個變量。
資源串常量當定義字符串常量時,你可這樣寫:
const AuthorName = 'Marco Cantù';
從Delphi 3 開始,你可以用另一種方式寫:
resourcestring AuthorName = 'Marco Cantù';
上面兩個語句都定義了一個常量,也就是定義了一個在程序運行期間保持不變的值,但兩者的實現過程卻不同,用resourcestring 指令定義的字符串變量將被保存到程序資源的字符串表中。從例子ResStr你可了解資源串的實際作用,例子中設置了一個按鈕, 相應代碼如下:
resourcestring AuthorName = 'Marco Cantù'; BookName = 'Essential Pascal'; procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage (BookName + #13 + AuthorName); end;
以上代碼中的兩個字符串將分兩行輸出顯示,因為字符串被分行符 #13 隔開。
有趣的是,當你用資源編輯器打開執行文件時,你會在程序資源中看到你所定義的字符串。這意味着字符串並沒有進入編譯代碼,而是保存在執行文件 (EXE文件) 的一個單獨區域。
注意:簡而言之,采用資源的好處一方面可讓Windows 來完成有效的內存處理,另一方面不用更改源代碼就可實現程序的本地化 (把字符串翻譯成不同的語言)。
數據類型Pascal 中有多種預定義的數據類型,它們可分為三大類:有序數據類型,實數類型和字符串類型。下面我們先討論有序類型和實數類型,字符串類型放在以后討論。同時這一節還將介紹幾種Delphi 庫中定義的類型 (不是編譯器預定義的類型),這些類型也可看作是預定義的類型。
Delphi 還包括一種無類型的可變數據類型,稱作variant,在本書的第十章將討論這一類型。variant是一種無需類型檢測的數據類型,它在Delphi 2 中引入,用於處理OLE Automation(OLE 自動化)。
有序類型有序類型是建立在概念“順序”或“序列”基礎上的數據類型。你不僅可比較兩個有序值的大小,而且可以求取給定有序值的前驅及后繼,或者計算它們的最大或最小值。
三種最重要的預定義有序類型是整數類型、布爾類型和字符類型(Integer,Boolean,Char)。各種類型根據其內部表示和取值范圍不同又可進一步細分。表3.1列出了表示數字的有序數據類型。
表 3.1: 表示數字的有序數據類型
大小 | 有符號值域 | 無符號值域 |
---|---|---|
8 bits | ShortInt -128 to 127 |
Byte 0 to 255 |
16 bits | SmallInt -32768 to 32767 |
Word 0 to 65,535 |
32 bits | LongInt -2,147,483,648 to 2,147,483,647 |
LongWord (從 Delphi 4) 0 to 4,294,967,295 |
64 bits | Int64 | |
16/32 bits | Integer | Cardinal |
從表中可看到,不同數據類型與不同的數據表示法相對應,這要取決於數據值的數位和符號位。有符號類型的數值可正可負,但取值范圍較小,因為符號位占一個數位。下一節在例Range中說明了每種類型的實際取值范圍。
表中最后一組類型標志着16/32,它表明其數值表示方法在16位和32位Delphi中不同,該組的Integer及Cardinal 類型比較常用,因為它們與CPU內部的數字表示法相對應。
Delphi 4中的整數類型在 Delphi 3中,Cardinal類型所表示的32位無符號值實際占31位,取值最高為20億。Delphi 4新增了一種無符號數字類型--LongWord,它是真正的32位值,取值最高達40億。現在Cardinal 類型已成了LongWord類型的別名,只是LongWord能容納大於20億的無符號數,而且它的數值表示法與CPU內部數值表示法一致。
Delphi 4 中新增的另一個數據類型是Int64 類型,這一類型能表示長達18個數字的整數。系統中的有序類型例程(如High 和Low)、數字例程(如Inc 和 Dec)及字符串轉換例程(如IntToStr)都支持這一新類型。反過來,有兩個新增的專用函數StrToInt64 和 StrToInt64Def支持從字符串向數字的轉換。
布爾類型布爾值不同於布爾類型,平時很少用到。ByteBool、 WordBool 和LongBool這三種布爾類型的布爾值比較特殊,只在Windows API 函數中才用到它們。
在Delphi 3 中,為了與Visual Basic 和 OLE Automation兼容,修改了ByteBool、 WordBool 和LongBool的布爾值,將TRUE值設置為1,FALSE值仍為0;Boolean類型布爾值保持不變(TRUE為1,FALSE為0)。如果在Delphi 2代碼中使用了布爾值顯式類型轉換 ,那么在以后的Delphi中可能會出錯。
字符類型字符有兩種不同的表示法:: ANSIChar 和 WideChar。第一種類型代表 8 位的字符,與Windows一直沿用的ANSI(美國國家標准協會)字符集相應;第二種類型代表 16 位的字符,與Windows NT、Windows 95 和 98支持的雙字節字符(Unicode)相應。在Delphi 3 中,Char 類型字符與ANSIChar一致。切記,不管在什么環境,前 256 個Unicode 字符與ANSI 字符是完全一致的。
常量字符可用代表它們的符號表示,如‘k’,也可用數字符號表示,如 #78。后者還可用Chr函數表示為 Chr(78),用Ord函數可作相反的轉換Ord(k)。
一般來說,對字母、數字或符號,用代表它們的符號來表示較好;而涉及到特殊字符時用數字符號較好。下面列出了常用的特殊字符:
- #9 跳格 (Tab 鍵)
- #10 換行
- #13 回車 (Enter 鍵)
為使你對一些有序類型的不同取值范圍有一個認識,我寫了一個名為Range 的Delphi程序簡例。結果見圖3.1。
圖3.1 簡例Range顯示有序數據類型信息(本例中采用整型)
Range 程序基於一個簡單的窗體,上面有六個按扭 (按有序數據類型命名),還有幾個標簽(Label)用於顯示信息,見圖3.1。窗體最左邊的一列標簽顯示的是靜態文本,左邊第二列標簽在每次單擊按扭時顯示數據類型信息。
每當你按一下窗體右邊的一個按鈕,程序就會更新第二列標簽的顯示內容,顯示的內容包括數據類型、字節數、該類型可存儲的最大值和最小值。每個按鈕都帶有各自的OnClick 事件,因為各自的計算代碼略有不同。例如,以下是Integer按鈕(BtnInteger)OnClick 事件的源代碼:
procedure TFormRange.BtnIntegerClick(Sender: TObject); begin LabelType.Caption := 'Integer'; LabelSize.Caption := IntToStr (SizeOf (Integer)); LabelMax.Caption := IntToStr (High (Integer)); LabelMin.Caption := IntToStr (Low (Integer)); end;
如果你有Delphi 編程經驗,你可以看一下程序的源代碼,弄明白程序到底是如何工作的。對於初學者,注意一下SizeOf、 High、 Low這三個函數的使用就可以了。High、 Low兩個函數返回與參數相同的有序類型(這里是整型),SizeOf 函數返回整型數據。函數的返回值先用IntToStr 函數轉成字符串,然后賦給三個標簽的caption屬性 。
其他按鈕事件與上面相似,唯一的不同點在於傳遞給函數的參數類型是不同的。圖3.2 顯示了Windows 95 下的16位Delphi編譯程序的執行結果。比較圖3.1和圖3.2,可以看出16位整型和32位整型之間的差異。
圖3.2 :16位Delphi中Range程序運行結果顯示的整型信息
整型類型的字節大小取決於你所使用的CPU和操作系統。在16位的Windows中,整型變量占兩個字節,在32位的Windows中,整型變量占4個字節。因此,在兩個環境中編譯的Range程序會得到不同的結果。
如果你的程序對整數類型的字節大小沒有特殊要求,Integer 類型在不同版本中的差異並不是個大問題。如果你在一個版本中保存了一個整數,那么在另一個版本中取出這個整數時可能會遇到一些問題,這種情況下,你應該采用平台無關的數據類型如 LongInt 或 SmallInt。對於數學計算或非特殊的代碼中,你最好的選擇是堅持使用平台相應的標准整型,這就是說,使用CPU最喜歡的整型類型。當處理整數時,Integer 應是你的首選,不到迫不得已最好不要采用其他的整型類型。
有序類型系統例程Pascal 語言和Delphi System 單元中定義了一系列有序類型操作例程,見表 3.2。C++ 程序員會注意到其中的Inc 例程,它可與 ++ 和 += 運算符對應(Dec 例程也同樣)。
表 3.2: 有序類型系統例程
例程 | 作用 |
---|---|
Dec | 將例程中的參數值遞減1或一個特定的值,其中特定值可在第二個可選參數中定義 |
Inc | 將例程中的參數值增加1或一個特定的值 |
Odd | 如果參數為奇數返回真 |
Pred | 根據參數在其數據類型定義中的序列,返回參數值的前驅值 |
Succ | 返回參數值的后繼值 |
Ord | 返回參數值在其數據類型值集合中的序號 |
Low | 返回參數對應的有序數據類型的最小取值 |
High | 返回參數對應的有序數據類型的最大取值 |
注意,當有些例程用於常量時,編譯器會自動用計算值替代例程。例如你調用High(X) ,設定X為一個整數,那么編譯器會用整數類型中最大的可能值代替這個表達式。
實數類型實數類型代表不同格式的浮點數。Single類型占的字節數最小,為4個字節;其次是Double 浮點類型,占8個字節;Extended 浮點類型,占10個字節。這些不同精度的浮點數據類型都與IEEE( 電氣和電子工程師協會)標准的浮點數表示法一致,並且 CPU數字協處理器直接支持這些類型,處理速度也最快。
Real 類型在Delphi 2 和 Delphi 3 中的定義與 16 位版本一樣,都占 6 個字節。不過Borland公司一直不提倡使用這種類型,而建議用Single、 Double、 Extended 類型代替。這是由於 Real 這種 6 字節的舊格式既不受 Intel CPU 的支持,又沒有列在官方的IEEE 實型中。為了完全解決這一問題,Delphi 4 不得不修改 Real 類型的定義,將其改成標准的 8 字節浮點型, 由此引起了兼容性問題,不過如果有必要,你可以采用下面編譯指令克服兼容性問題,恢復Delphi 2 和 Delphi 3 的Real 類型定義:
{$REALCOMPATIBILITY ON}
另外還有兩種奇怪的數據類型:Comp 類型和Currency 類型,Comp 類型用 8 個字節描述非常大的整數(這種類型可支持帶有 18 位小數的數字);Currency 類型 (16 位版的Delphi不支持該類型) 表示一個有四位小數位的值,它的小數位長度是固定的,同Comp 類型一樣也占 8 個字節。正如名字所示,Currency 數據類型是為了操作很精確的四位小數貨幣數值才添加的。
對實型數據,我們沒辦法編一個類似Range的程序,因為High 、Low及 Ord函數不能用於實型值。理論上說實型類型代表一個無限的數字集合;有序類型代表一個有限的數字集合。
注意:讓我進一步把上述問題解釋一下。對於整數 23,你能確定23 后面的數是什么 ,因為整型數是有限的,它們有確定的值域范圍及排列順序。而浮點數即使在一個很小的值域范圍內也無限、無序。 事實上,在 23 和 24 之間有多少值? 哪個值是 23.46 后面的值? 23.47 還是 23.461,或者 23.4601? 這是很難說清的。
因此,如問Char 類型字符 w 的順序位置是有意義的, 但同樣的問題對浮點類型數 7134.1562 就毫無意義。對於一個實型數,你能確切知道有沒有比它大的實型數,但是,如想探究給定的實數前到底有多少個實型數(這是Ord 函數的作用),是得不到結果的。
實型類型在用戶界面編程中用得不多,但是Delphi從各方面支持實型類型,包括在數據庫方面的支持。由於支持IEEE浮點數運算標准,Object Pascal 語言完全適合於各類數值計算編程。如果對這部分感興趣,你可以參考Delphi 在System單元中提供的算術函數(詳細見Delphi 幫助)。
注意:Delphi 帶有一個Math 單元,其中定義了一些高級數學例程,這些例程包括三角函數(如ArcCosh 函數)、金融函數(如InterestPayment 函數)和統計函數(如MeanAndStdDev 過程)。有些例程,它的名字聽起來很怪,如MomentSkewKurtosis 例程,它是作什么用的呢? 還是留你自己查吧。
日期和時間Delphi 也用實型數表示日期和時間數據。但為了更准確起見,Delphi 特別定義了TDateTime 數據類型,這是一個浮點類型,因為這個類型必須足夠寬,使變量能容納年、月、日、時、分和秒、甚至毫秒。日期值按天計數,從1899-12-30開始,放在TDateTime 類型的整數部分;時間值則位於十進制數的小數部分。
TDateTime 不是編譯器可直接識別的預定義類型,它在System單元定義:
type TDateTime = type Double;
使用TDateTime 類型很簡單,因為Delphi 為該類型定義了一系列操作函數,表3.3列出了這些函數。
表3.3: TDateTime類型系統例程
例程 | 作用 |
---|---|
Now | 返回當前日期及時間 |
Date | 返回當前日期 |
Time | 返回當前時間 |
DateTimeToStr | 按缺省格式將日期和時間值轉換為字符串;特定格式轉換可用 FormatDateTime函數 |
DateTimeToString | 按缺省格式將日期和時間值拷貝到字符串緩沖區 |
DateToStr | 將TDateTime值的日期部分轉為字符串 |
TimeToStr | 將TDateTime值的時間部分轉為字符串 |
FormatDateTime | 按特定格式將日期和時間值轉換為字符串 |
StrToDateTime | 將帶有日期和時間信息的字符串轉換為TdateTime類型值,如串有誤將引發一個異常 |
StrToDate | 將帶有日期信息的字符串轉換為TDateTime類型格式 |
StrToTime | 將帶有時間信息的字符串轉換為TDateTime類型格式 |
DayOfWeek | 根據傳遞的日期參數計算該日期是一星期中的第幾天 |
DecodeDate | 根據日期值返回年、月、日值 |
DecodeTime | 根據時間值返回時、分、秒、毫秒值 |
EncodeDate | 組合年、月、日值為TDateTime類型值 |
EncodeTime | 組合時、分、秒、毫秒值為TDateTime類型值 |
為了顯示怎樣使用日期時間類型及其相關例程,我建了一個簡單的例子TimeNow。該例子在主窗體中設置了一個按鈕和一個列表框(ListBox)。開始執行時,程序自動計算並顯示當前的時間及日期,以后每次單擊按鈕 ,顯示從程序開始至當前的時間。
下面列出了窗體的OnCreate 事件代碼:
procedure TFormTimeNow.FormCreate(Sender: TObject); begin StartTime := Now; ListBox1.Items.Add (TimeToStr (StartTime)); ListBox1.Items.Add (DateToStr (StartTime)); ListBox1.Items.Add ('Press button for elapsed time'); end;
第一句中調用了Now 函數,這個函數返回當前的日期和時間,它的值保存在StartTime 變量中,StartTime 變量是全程變量,其聲明如下:
var FormTimeNow: TFormTimeNow; StartTime: TDateTime;
我只添加了第二個聲明,第一個是由Delphi自動添加的。默認情況下的代碼如下:
var Form1: TForm1;
窗體名改變后,這個聲明被自動更新。使用全程變量實際上不是最好的辦法,更好的方法是使用窗體類的私有域,這涉及到面向對象的編程技術。
接下來的三個語句向位於窗體左面的列表框添加三個條目,結果見圖3.3。列表框中的第一行顯示了TDateTime 值的時間部分字符串、第二行顯示的是同一值的日期部分,最后一行顯示了一個簡單的提示。
圖 3.3:例TimeNow啟動時的輸出顯示
當用戶單擊Elapsed 按鈕時,上圖第三行字符串被程序的計算結果代替:
procedure TFormTimeNow.ButtonElapsedClick(Sender: TObject); var StopTime: TDateTime; begin StopTime := Now; ListBox1.Items [2] := FormatDateTime ('hh:nn:ss', StopTime - StartTime); end;
這串代碼再次計算當前的時間,並顯示當前與程序開始之時的時間差,其中用到了其它事件中的計算值,為此不得不把該值存入全程變量。實際上,最好是采用基於類的變量。
注意:上面代碼中所用ListBox的索引號為2,,而它代表的是第三行的顯示輸出,其原因是listbox的數據項是從零開始計數的:第一項計為0,第二項為1,第三項為2,依次類推,后面涉及數組時再詳細討論這方面內容。
除了調用TimeToStr和 DateToStr 外,以上例子中還用到了功能強大的FormatDateTime 函數(關於格式化參數詳見Delphi 幫助文件)。需要注意的是:當時間和日期轉換成字符串時,其轉換格式取決於Windows 的系統設置。Delphi 從系統中讀這些值,並把它們拷貝到SysUtils 單元中聲明的幾個全程常量中,例如:
DateSeparator: Char; ShortDateFormat: string; LongDateFormat: string; TimeSeparator: Char; TimeAMString: string; TimePMString: string; ShortTimeFormat: string; LongTimeFormat: string; ShortMonthNames: array [1..12] of string; LongMonthNames: array [1..12] of string; ShortDayNames: array [1..7] of string; LongDayNames: array [1..7] of string;
大部分全程常量與currency 和浮點數格式化有關,在 Delphi 幫助的 Currency and date/time formatting variables 主題下,你可找到完整的清單。
注意:Delphi 中有一個DateTimePicker 控件,它提供了選擇日期的常用途徑,即從一個日歷中選擇日期。
特定的Windows 類型到目前為止,我們所看到的預定義數據類型都是Pascal 語言自身定義的類型。 Delphi 中還包含Windows系統定義的數據類型,這些數據類型不是Pascal語言的組成部分,而是Windows 庫的一部分。Windows 類型包括新增的缺省類型(例如DWORD 或UINT)、各種記錄(或結構)類型及指針類型等。
Windows 定義的數據類型中,最重要的類型是句柄(handle),第九章中將討論這一類型。
類型映射及類型轉換正如所知,你不能把一個變量賦給另一個不同類型的變量,如果你需要這么做,有兩種方法供選擇。第一種方法是采用類型映射(Typecasting),它使用一個帶有目標數據類型名的函數符號:
var N: Integer; C: Char; B: Boolean; begin N := Integer ('X'); C := Char (N); B := Boolean (0);
你可以在字節長度相同的數據類型之間進行類型映射。在有序類型之間或實型數據之間進行類型映射通常是安全的,指針類型及對象之間也可以進行類型映射 ,只要你明白自己在做什么。
然而,一般來說類型映射是一種較危險的編程技術,因為它允許你訪問一個似是而非的值,該值好象是其它值的替身。由於數據類型的內部表示法之間通常互相不匹配,所以當遇到錯誤時會難以追蹤,為此你應盡量避免使用類型映射。
第二種方法是使用類型轉換例程。表3.4中總結了各種類型轉換例程。其中有些例程所涉及的數據類型將在下一節中討論。 注意表中沒有包括特殊類型(如TDateTime 和variant)的轉換例程,也沒包括用於格式化處理的特殊例程,如Format 和FormatFloat 例程。
表3.4:類型轉換系統例程
例程 | 作用 |
---|---|
Chr | 將一個有序數據轉換為一個ANSI字符 |
Ord | 將一個有序類型值轉換為它的序號 |
Round | 轉換一個實型值為四舍五入后的整型值 |
Trunc | 轉換一個實型值為小數截斷后的整型值 |
Int | 返回浮點數的整數部分 |
IntToStr | 將數值轉換為字符串 |
IntToHex | 將數值轉換為十六進制數字符串 |
StrToInt | 將字符串轉換為一個整型數,如字符串不是一個合法的整型將引發異常 |
StrToIntDef | 將字符串轉換為一個整數,如字符串不合法返回一個缺省值 |
Val | 將字符串轉換為一個數字(傳統Turbo Pascal例程用於向后兼容) |
Str | 將數字轉換為格式化字符串(傳統Turbo Pascal例程用於向后兼容) |
StrPas | 將零終止字符串轉換為Pascal類型字符串,在32位Delphi中這種類型轉換是自動進行的 |
StrPCopy | 拷貝一個Pascal類型字符串到一個零終止字符串, 在32位Delphi中這種類型轉換是自動進行的 |
StrPLCopy | 拷貝Pascal類型字符串的一部分到一個零終止字符串 |
FloatToDecimal | 將一個浮點數轉換為包含指數、數字及符號的十進制浮點記錄類型 |
FloatToStr | 將浮點值轉換為缺省格式的字符串 |
FloatToStrF | 將浮點值轉換為特定格式的字符串 |
FloatToText | 使用特定格式,將一個浮點值拷貝到一個字符串緩沖區 |
FloatToTextFmt | 同上面例程,使用特定格式,將一個浮點值拷貝到一個字符串緩沖區 |
StrToFloat | 將一個Pascal字符串轉換為浮點數 |
TextToFloat | 將一個零終止字符串轉換為浮點數 |
注意:在最近版本的Delphi Pascal 編譯器中,Round 函數是以 CPU 的 FPU (浮點部件) 處理器為基礎的。這種處理器采用了所謂的 "銀行家舍入法",即對中間值 (如 5.5、6.5) 實施Round函數時,處理器根據小數點前數字的奇、偶性來確定舍入與否,如 5.5 Round 結果為 6,而 6.5 Round 結果也為6, 因為 6 是偶數。
本章討論了Pascal的基本數據類型。Pascal語言還有一個非常重要的特征:它允許編程者自定義數據類型,稱為“用戶自定義數據類型”,這在下一章進行討論。