在Delphi XE2的System.Generics.Collections.TArray.Sort
帮助中,它说
Note: If the Comparer parameter is provided, it is used to compare elements; otherwise the default comparator for the array elements is used.
我稍微挖了一下,发现TArray.Sort
的默认比较器来自_LookupVtableInfo
System.Generics.Defaults
。这个代码是
function _LookupVtableInfo(intf: TDefaultGenericInterface; info: PTypeInfo; size: Integer): Pointer;
var
pinfo: PVtableInfo;
begin
if info <> nil then
begin
pinfo := @VtableInfo[intf, info^.Kind];
Result := pinfo^.Data;
if ifSelector in pinfo^.Flags then
Result := TTypeInfoSelector(Result)(info, size);
if ifVariableSize in pinfo^.Flags then
Result := MakeInstance(Result, size);
end
else
begin
case intf of
giComparer: Result := Comparer_Selector_Binary(info, size);
giEqualityComparer: Result := EqualityComparer_Selector_Binary(info, size);
else
System.Error(reRangeError);
Result := nil;
end;
end;
end;
它被称为
IComparer<T>(_LookupVtableInfo(giComparer, TypeInfo(T), SizeOf(T)))
我已经仔细研究了这一点,而且我并不确定我知道它的作用。它只是将内存中的位相互比较或者究竟是什么?
问题的第二部分是一个更普遍的问题,你可能真正想要使用默认比较器,或者你不太可能真的想要使用它?
答案 0 :(得分:9)
默认比较器提供许多常见类型的实现。具体来说,它支持以下内容:
Byte
,Word
,Integer
等。对于其中许多类型,默认实现正是您所期望的。例如,对于整数,枚举类型,浮点类型,实现使用<
,>
和=
运算符。对于string
,默认实现会调用CompareStr
。
对于其他类型,默认实现可能不太有用。例如,对于记录,比较是逐字节二进制比较。您很可能希望为记录提供自己的比较器实现。注意记录的一件事是默认比较器将比较记录中的任何填充,你永远不想这样做。因此,对于具有填充的对齐记录,它永远不会有用。而且我还会询问包含引用类型的记录的实用程序。
对于动态数组,默认实现首先比较长度,然后,如果length相等,则比较数组的二进制内容。因此,对于简单值类型的数组而言,这可能是合理的。但对于多维动态数组或引用类型数组,并非如此。
对于类实例,方法,过程变量,接口,默认比较器将操作数视为指针(在方法的情况下为两个指针)并执行地址比较。
当您想使用默认比较器时?那么,只要符合比较器的要求,就可以使用它。因此,简单的值类型肯定有意义。除此之外,您需要根据具体情况做出决定。
答案 1 :(得分:5)
你在那里发布的函数实际上并不是比较函数,而是一个基于TypeInfo和SizeOf T返回比较函数的函数。
在更深层次之后,我们在Generics中看到。默认了形式的许多功能:
function Compare_
类型(Inst: Pointer; const Left, Right:
的名称类型): Integer;
它们都具有相同的主体(但左右注释具有不同的类型)
begin
if Left < Right then
Result := -1
else if Left > Right then
Result := 1
else
Result := 0;
end;
最后还剩下一切
function BinaryCompare(const Left, Right: Pointer; Size: Integer): Integer;
var
pl, pr: PByte;
len: Integer;
begin
pl := Left;
pr := Right;
len := Size;
while len > 0 do
begin
Result := pl^ - pr^;
if Result <> 0 then
Exit;
Dec(len);
Inc(pl);
Inc(pr);
end;
Result := 0;
end;
答案 2 :(得分:3)
David在文本上描述了默认比较器的工作方式做得非常出色,但是对于你们中的一些人来说,当你看到底层代码的结构如何(并决定默认的比较器是否适用)时,可能会更容易理解。
我将只介绍Compare_
比较风格。 Equals_
样式的工作方式类似。
_LookupVtableInfo
会为IComparer
样式比较选择Compare_
界面(IEqualityComparer
样式选择Equals_
。
这些接口下面不是普通的接口,而是围绕Compare_
样式的这种形式的全局函数的接口包装:
function Compare_t<T>(Inst: Pointer; const Left, Right: T): Integer;
以及Equals_
样式的此表单的全局过程:
function Equals_t<T>(Inst: Pointer; const Left, Right: T): Integer;
function GetHashCode_t<T>(Inst: Pointer; const Left, Right: T): Integer;
Compare_
样式函数的结果很简单,但与某些人可能期望的-1,0,+ 1略有不同:
< 0 for Left < Right
= 0 for Left = Right
> 0 for Left > Right
对于大多数情况,实施非常简单:
我按照他们的方式对Compare_
样式函数进行了分组。
(1,2,4,10字节范围之外的序数类型以及4,8,10字节范围之外的实数类型会引发错误,因为它们是非法的。)
第一组只是从右减去Left:有符号/无符号整数,长度为1或2字节
function Compare_I1(Inst: Pointer; const Left, Right: Shortint): Integer;
function Compare_I2(Inst: Pointer; const Left, Right: Smallint): Integer;
function Compare_U1(Inst: Pointer; const Left, Right: Byte): Integer;
function Compare_U2(Inst: Pointer; const Left, Right: Word): Integer;
Result := Left - Right;
第二组进行比较:
function Compare_I4(Inst: Pointer; const Left, Right: Integer): Integer;
function Compare_I8(Inst: Pointer; const Left, Right: Int64): Integer;
function Compare_U4(Inst: Pointer; const Left, Right: LongWord): Integer;
function Compare_U8(Inst: Pointer; const Left, Right: UInt64): Integer;
function Compare_R4(Inst: Pointer; const Left, Right: Single): Integer;
function Compare_R8(Inst: Pointer; const Left, Right: Double): Integer;
function Compare_R10(Inst: Pointer; const Left, Right: Extended): Integer;
function Compare_RI8(Inst: Pointer; const Left, Right: Comp): Integer;
function Compare_RC8(Inst: Pointer; const Left, Right: Currency): Integer;
function Compare_WString(Inst: PSimpleInstance; const Left, Right: WideString): Integer;
function Compare_Pointer(Inst: PSimpleInstance; Left, Right: NativeUInt): Integer;
type
{$IFNDEF NEXTGEN}
TPS1 = string[1];
TPS2 = string[2];
TPS3 = string[3];
{$ELSE NEXTGEN}
OpenString = type string;
TPS1 = string;
TPS2 = string;
TPS3 = string;
{$ENDIF !NEXTGEN}
function Compare_PS1(Inst: PSimpleInstance; const Left, Right: TPS1): Integer;
function Compare_PS2(Inst: PSimpleInstance; const Left, Right: TPS2): Integer;
function Compare_PS3(Inst: PSimpleInstance; const Left, Right: TPS3): Integer;
// OpenString allows for any String[n], see http://my.safaribooksonline.com/book/programming/borland-delphi/1565926595/5dot-language-reference/ch05-openstring
function Compare_PSn(Inst: PSimpleInstance; const Left, Right: OpenString): Integer;
if Left < Right then
Result := -1
else if Left > Right then
Result := 1
else
Result := 0;
function Compare_Method(Inst: PSimpleInstance; const Left, Right: TMethodPointer): Integer;
var
LMethod, RMethod: TMethod;
begin
LMethod := TMethod(Left);
RMethod := TMethod(Right);
if LMethod < RMethod then
Result := -1
else if LMethod > RMethod then
Result := 1
else
Result := 0;
end;
现在我们开始讨论有趣的事情:不那么直截了当的结果。
字符串使用CompareStr
。如果您想要不同的内容,可以使用TOrdinalIStringComparer
function Compare_LString(Inst: PSimpleInstance; const Left, Right: AnsiString): Integer;
function Compare_UString(Inst: PSimpleInstance; const Left, Right: UnicodeString): Integer;
Result := CompareStr(Left, Right);
BinaryCompare
用于:
对于可以比较的记录,执行运算符重载是有意义的,并让比较器使用这些运算符。
1,2,4或8字节的二进制数据是一个例外,它会在小端机器(Intel x86和x64,以及小端模式的双端臂)上产生奇怪的结果:
function Comparer_Selector_Binary(info: PTypeInfo; size: Integer): Pointer;
begin
case size of
// NOTE: Little-endianness may cause counterintuitive results,
// but the results will at least be consistent.
1: Result := @Comparer_Instance_U1;
2: Result := @Comparer_Instance_U2;
4: Result := @Comparer_Instance_U4;
{$IFDEF CPUX64}
// 64-bit will pass const args in registers
8: Result := @Comparer_Instance_U8;
{$ENDIF}
else
Result := MakeInstance(@Comparer_Vtable_Binary, size);
end;
end;
其余的是纯二进制:
function Compare_Binary(Inst: PSimpleInstance; const Left, Right): Integer;
begin
Result := BinaryCompare(@Left, @Right, Inst^.Size);
end;
function Compare_DynArray(Inst: PSimpleInstance; Left, Right: Pointer): NativeInt;
var
len, lenDiff: NativeInt;
begin
len := DynLen(Left);
lenDiff := len - DynLen(Right);
if lenDiff < 0 then
Inc(len, lenDiff);
Result := BinaryCompare(Left, Right, Inst^.Size * len);
if Result = 0 then
Result := lenDiff;
end;
像往常一样,Variants
属于他们自己的联盟。首先尝试了VarCompareValue
。如果失败,则尝试Compare_UString
。如果失败,则尝试BinaryCompare
。如果失败了:运气不好。
function Compare_Variant(Inst: PSimpleInstance; Left, Right: Pointer): Integer;
var
l, r: Variant;
lAsString, rAsString: string;
begin
Result := 0; // Avoid warning.
l := PVariant(Left)^;
r := PVariant(Right)^;
try
case VarCompareValue(l, r) of
vrEqual: Exit(0);
vrLessThan: Exit(-1);
vrGreaterThan: Exit(1);
vrNotEqual:
begin
if VarIsEmpty(L) or VarIsNull(L) then
Exit(1)
else
Exit(-1);
end;
end;
except // if comparison failed with exception, compare as string.
try
lAsString := PVariant(Left)^;
rAsString := PVariant(Right)^;
Result := Compare_UString(nil, lAsString, rAsString);
except // if comparison fails again, compare bytes.
Result := BinaryCompare(Left, Right, SizeOf(Variant));
end;
end;
end;