AFAIK没有内置功能.在网上搜索我发现这个
function并且它适用于我,但我不喜欢使用它,因为它是汇编而我无法理解它在做什么.所以我写了这个函数也有效:
function Cardinality(const PSet: PByteArray; const SizeOfSet(*in bytes*): Integer): Integer; const Masks: array[0..7] of Byte = (1,2,4,8,16,32,64,128); var I,J: Integer; begin Result := 0; for I := 0 to SizeOfSet - 1 do for J := 0 to 7 do if (PSet^[I] and Masks[J]) > 0 then Inc(Result); end;
现在,我想知道我是否可以依赖这个功能?或者也许设置数据类型背后有一个技巧,这就是德尔福没有内置方法的原因.
但如果我的功能可靠,那么我该如何改进它:
>将常量传递给它
>进行类型检查并确保将一个集传递给该函数
>传递值而不是其地址
>摆脱SizeOfSet参数
我想把它称为Cardinality(AnySet)而不是Cardinality(@AnySet,SizeOf(TAnySet)).
顺便说一句,我需要在XE和XE5中编译它.
解决方法
您可以使用泛型和RTTI实现此功能.像这样:
uses SysUtils,TypInfo; type ERuntimeTypeError = class(Exception); TSet<T> = class strict private class function TypeInfo: PTypeInfo; inline; static; public class function IsSet: Boolean; static; class function Cardinality(const Value: T): Integer; static; end; const Masks: array[0..7] of Byte = (1,128); implementation { TSet<T> } class function TSet<T>.TypeInfo: PTypeInfo; begin Result := System.TypeInfo(T); end; class function TSet<T>.IsSet: Boolean; begin Result := TypeInfo.Kind=tkSet; end; function GetCardinality(const PSet: PByteArray; const SizeOfSet(*in bytes*): Integer): Integer; inline; var I,J: Integer; begin Result := 0; for I := 0 to SizeOfSet - 1 do for J := 0 to 7 do if (PSet^[I] and Masks[J]) > 0 then Inc(Result); end; class function TSet<T>.Cardinality(const Value: T): Integer; var EnumTypeData: PTypeData; begin if not IsSet then raise ERuntimeTypeError.Create('Invalid type in TSet<T>,T must be a set'); Result := GetCardinality(PByteArray(@Value),SizeOf(Value)); end;
用法:
Writeln(TSet<SomeSet>.Cardinality(Value));