# 第十章
## Variant類型
為了完全支持OLE,32位Delphi 增加了Variant 數據類型,本節將從宏觀角度來分析這種數據類型。實際上,Variant類型對Pascal語言有普遍而深入的影響,Delphi 控件庫中與OLE 無關的地方也使用到這種類型。
### Variant變量沒有類型
一般說來,你可以用Variant 變量存儲任何數據類型,對它執行各種操作和類型轉換。需要注意的是:這違反了Pascal 語言的一貫原則,有悖于良好的編程習慣。variant 變量的類型檢查和計算在運行期間才進行,編譯器不會提示代碼中的潛在錯誤,這些錯誤在進一步測試中才能發現。總之,你可以認為包含variant變量的代碼是解釋性代碼,正如解釋性代碼一樣,許多操作直到執行時才能知道,這對代碼運行速度會有很大的影響。
上面對Variant 類型的使用提出了警告,現在來看看Variant 類型究竟能干什么。基本上說,如果聲明了一個variant 變量:
~~~
var
V: Variant;
~~~
你就可以把各種不同類型的值賦給它:
~~~
V := 10;
V := 'Hello, World';
V := 45.55;
~~~
一旦得到一個variant 值,你可以把它拷貝給任何兼容或不兼容的數據類型。如果你把值賦給不兼容的數據類型,Delphi 會力盡所能進行轉換,無法轉換則頒布一個運行時間錯誤。實際上,variant變量中不僅包含了數據還包含有類型信息,并允許一系列運行時間操作,這些操作很方便,但運行速度慢且安全性差。
見例VariTest,它是上面代碼的擴展。窗體上有三個編輯框,一對按鈕,第一個按鈕的OnClick 事件代碼如下:
~~~
procedure TForm1.Button1Click(Sender: TObject);
var
V: Variant;
begin
V := 10;
Edit1.Text := V;
V := 'Hello, World';
Edit2.Text := V;
V := 45.55;
Edit3.Text := V;
end;
~~~
很有趣是不是?你可以把一個值為字符串的variant 變量賦給編輯框Text 屬性,還可以把值為整數或浮點數的variant 變量賦給Text屬性。正如你在圖10.1中所看到的,一切正常。
(圖10.1)按Assign按鈕后,例VariTest的輸出結果
**圖 10.1: 例 VariTest 的 Assign 按鈕 Click 事件輸出結果**

更糟糕的是:你還可以用variant變量計算數值,從第二個按鈕的Click事件代碼就可看到這一點:
~~~
procedure TForm1.Button2Click(Sender: TObject);
var
V: Variant;
N: Integer;
begin
V := Edit1.Text;
N := Integer(V) * 2;
V := N;
Edit1.Text := V;
end;
~~~
至少這種代碼帶有一定危險性,如果第一個編輯框包含了一個數字,那么一切運行正常;如果不是,將會引發異常。這里再重申一遍,如果不到萬不得以,不要隨便使用Variant 類型,還是應堅持使用傳統的Pascal 數據類型和類型檢查方法。在Delphi 和 VCL中,variant變量主要是用于 OLE 支持和數據庫域的訪問。
### Variant類型內部結構
Delphi中定義了一個 variant 記錄類型,TVarData,它與Variant 類型有相同的內存布局。你可以通過TVarData訪問variant變量的實際類型。TVarData 結構中包含了Variant類型信息(由Vtype域表示)、一些保留域及當前值。
VType域的取值包括OLE 自動化中的所有數據類型,這些類型通常叫OLE 類型或variant 類型。以下是variant 類型的完整列表,按字母順序排列:
* varArray
* varBoolean
* varByRef
* varCurrency
* varDate
* varDispatch
* varDouble
* varEmpty
* varError
* varInteger
* varNull
* varOleStr
* varSingle
* varSmallint
* varString
* varTypeMask
* varUnknown
* varVariant
你可以在Delphi 幫助系統的variants 主題下找到這些類型的說明。
還有許多操作variant 變量的函數,你可以用它們進行特定的類型轉換,或通過它們獲取variant變量的類型信息(例如VarType 函數),當你用variant變量寫表達式時,Delphi會自動調用這些類型轉換和賦值函數。另外還有操作variant 數組的例程,你可以通過幫助文件的Variant support routines 主題了解相關內容。
### Variant類型運行很慢!
Variant 類型代碼運行很慢,不僅數據類型轉換如此,兩個值為整數的Variant 變量相加也是如此。它們幾乎跟Visual Basic這種解釋性代碼一樣慢!為了比較Variant變量和整型變量的運行速度,請看例VSpeed 。
程序中設置了一個循環,記錄運行時間并在進程條中顯示運行狀態。下面是基于variant類型的一段代碼,基于整型的代碼與此相似:
~~~
procedure TForm1.Button1Click(Sender: TObject);
var
time1, time2: TDateTime;
n1, n2: Variant;
begin
time1 := Now;
n1 := 0;
n2 := 0;
ProgressBar1.Position := 0;
while n1 < 5000000 do
begin
n2 := n2 + n1;
Inc (n1);
if (n1 mod 50000) = 0 then
begin
ProgressBar1.Position := n1 div 50000;
Application.ProcessMessages;
end;
end;
// we must use the result
Total := n2;
time2 := Now;
Label1.Caption := FormatDateTime (
'n:ss', Time2-Time1) + ' seconds';
end;
~~~
記時這段代碼值得一看,因為你可以把它用到任何類型的性能測試中。正如你所看到的,程序用Now 函數獲取當前的時間,用FormatDateTime 函數格式化時間差,輸出結果以分("n")和秒("ss")表示。除此之外,你可以用Windows API的GetTickCount 函數,該函數能精確顯示操作系統啟動后至當前的毫秒數。
從上例可見兩者的速度差異非常之大,以至于不用精確記時也能看到這種差異。圖10.2是在本人計算機上運行程序看到的結果。當然運行結果取決于運行程序的計算機,但是兩者的數值比不會有太大變化。
**圖 10.2: 例Vspeed中整型與Variant類型的計算速度差異**

* * * * *
### 結束語
Variant類型與傳統Pascal 數據類型差別很大,所以本章以短小篇幅單獨闡述了Variant類型的有關內容。盡管Variant類型主要用于OLE 編程,但用來寫一些潦潦草草的程序倒也便利,因為不用考慮數據類型,不過正如以上所述,這樣做會影響程序執行速度。
通過前面各章我們已經介紹了絕大部分的語言特征,下一章將討論程序的總體框架和單元模塊。