Delphi 中的变体(Variant)类型不同于我们前面讨论过的变体记录类型,变体类型是一个系统类型,而变体记录类型则是一个用户定义类型,尽管二者在某些实现方面具有一致性。- 类型可变或编译时刻无法确定类型的情况,此类可称之为“动态类型”;
- 需要额外语义来描述其值为空的情况,此类可称之为“可空值类型”。
可空值类型主要用于与数据库的接口,这个我们以后再谈,今天主要讨论动态类型。program VTest1;{$APPTYPE CONSOLE}var v: Variant;begin v := 'Hello, Variant!'; // 此时,v 是 UnicodeString 类型 Writeln(v); v := 10; // 此时,v 是 Integer 类型 Writeln(v); v := 3.4; // 此时,v 是 Currency 类型 Writeln(v);end.
这段代码展示了变体类型的强大威力:犹如变色龙一般,可以随意变换“色彩”。你可能会是说,这样一身而多任,不好么?原因可以追溯到遥远的18世纪甚至更早,当苏格兰经济学家亚当斯密在其出版的《国富论》中提出“劳动分工”理论时,就已经说明“专业的人做专业的事”,比“一个人做所有的事”,效率要高得多。回到我们的例子。变体类型不仅占用空间大(变体类型是一个很复杂的类型,一个变体类型的变量在32位时需占用 16 字节,在64位时需占用24字节),而且效率低、速度慢。Delphi 的变体类型由 TVarData 实现,TVarData 与 Windows 中的 OLE 实现的 Variant 类型是一致的。TVarData 记录的定义如下:从 TVarData 的类型定义可以看出,其中的 RawData 字段定义了 TVarData 记录的长度,32位时,RawData 是 array[0..3] of Integer,也就是 4 个元素的整型数组,占用 16 个字节。64位时,RawData 是 array of [0..5] of Integer,也就是 6 个元素的整型数组,占用 24 个字节。VType 字段表示当前的数据是什么类型,下面的 case 语句定义了一系列的 varXXX 类型,这些就是 Delphi 的变体类型所支持的具体类型。譬如,varSmallInt 表示 16 位整型,varInteger 表示 32 位整型,varSingle 表示 32 位单精度浮点型,varDouble 表示 64 位双精度浮点型,等等。对于这些简单类型来说,变体类型将保存这些类型的值,如对于开头的例子,当对变体类型 v 赋值 10 时,v 中保存的是 10 这个值本身,3.4 也是一样。但对于字符串,v 中保存的是指向字符串的指针,这可以从上面类型定义中的 VarString: (VString: Pointer); 这一行看出来。前面我们说变体类型是一个很复杂的类型,看过 TVarData 的类型定义之后,就知道所言不虚了。下面我们看看 Delphi 如何实现变体类型的赋值语句:这是 Delphi 为开头的例子生成的代码,可以看到,每一个赋值语句,不论是将字符串赋值给变体类型变量 v,还是将整型数或者浮点型数赋值给 v,都是通过调用 $VarFromXXX 过程实现的。下面以其中的 VarFromCurr 过程为例,来看看变体类型的赋值过程:procedure _VarFromCurr(var V: TVarData; const Value: Currency);begin if (V.VType and varDeepData) <> 0 then VarCleanDeep(V); V.VType := varCurrency; V.VCurrency := Value;end;
首先检查 V 是否含有字符串、数组以及其他需要进一步清理的类型,如果是,则调用 VarCleanDeep 过程对 V 进行进一步的深度清理。接下来才是具体的赋值:对 VType 字段赋予 varCurrency 类型,同时将 Currency 类型值赋予 VCurrency 字段。以上这只是像 Currency 这样的简单类型的赋值,如果像数组、字符串等更复杂类型的赋值,所需要的工作更多。以上只是简单的赋值语句,其他对变体类型的操作也同样需要调用特定的过程来实现。变体类型不仅结构复杂,而且操作同样复杂,这就大大降低了操作变体类型数据的性能。同时,因为变体类型绕过了编译时刻的类型检查,许多类型错误会导致抛出运行时异常。如果所有的操作都能在 Delphi 内完成,确实不需要变体类型。变体类型主要用于与外部世界的接口,譬如 Windows 平台的 COM,譬如数据库系统。