7

在 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_LookupVtableInfofrom 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)))

我已经看过很多次了,但我并不是很确定我知道它的作用。它只是将内存中的位相互比较还是究竟是什么?

问题的第二部分是一个更笼统的情况,您可能实际上想要使用默认比较器,或者您不太可能真正想要使用它?

4

3 回答 3

10

默认比较器为许多常见类型提供了实现。具体来说,它支持以下内容:

  • 积分类型:Byte, Word,Integer
  • 枚举类型。
  • 浮点类型。
  • 字符串。
  • 套。
  • 类实例。
  • 程序变量。
  • 方法。
  • 变体。
  • 静态数组。
  • 动态数组。
  • 接口。
  • 指针。
  • 记录。

对于其中许多类型,默认实现正是您所期望的。例如,对于整数、枚举类型、浮点类型,实现使用<,>=运算符。对于string默认实现调用CompareStr

对于其他类型,默认实现可能不太有用。例如,对于记录,比较是按字节进行的二进制比较。您很可能希望提供自己的比较器实现以作为记录。记录需要注意的一件事是默认比较器将比较记录中的任何填充,而您永远不想这样做。因此,对于具有填充的对齐记录,它永远不会有用。而且我还会质疑包含引用类型的记录的实用程序。

对于动态数组,默认实现首先比较长度,然后,如果长度相等,则比较数组的二进制内容。因此,这对于简单值类型的数组可能是合理的。但是对于多维动态数组,或者引用类型的数组,就没那么多了。

对于类实例、方法、过程变量、接口,默认比较器将操作数视为一个指针(在方法的情况下为两个指针)并执行地址比较。

当你想使用默认比较器?好吧,只要它符合您对比较器的要求,您就会使用它。所以它对于简单的值类型当然是有意义的。除此之外,您需要根据具体情况做出决定。

于 2013-07-31T10:32:55.900 回答
5

您在那里发布的函数实际上不是比较函数,而是一个基于 TypeInfo 和 SizeOf T 返回比较函数的函数。

更深入地了解,我们在 Generics.Defaults 中看到了许多形式的函数:

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;
于 2013-07-31T10:35:23.647 回答
3

David 在文本描述默认比较器如何工作方面做得很好,但是对于你们中的一些人来说,当您看到底层代码的结构(并决定默认比较器是否适用)时,可能会更容易理解。

我将只Compare_介绍比较的风格。这种Equals_风格以类似的方式工作。

发生的情况是_LookupVtableInfo选择一个用于样式比较的IComparer界面(和一个用于样式)。Compare_IEqualityComparerEquals_

在这些接口下面不是普通接口,而是围绕这种形式的全局函数的接口包装器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_样式函数的执行方式对样式函数进行了分组。

  • 序数类型(包括枚举数和 Int64)。
  • 浮点(Real)类型(包括 Comp 和 Currency)。
  • 短字符串(来自 Turbo Pascal/Delphi 1 天)。
  • 宽字符串(OLE 样式的)。
  • 方法。
  • 指针(包括类、接口、类引用和过程)。

(超出 1、2、4、8 字节范围的序数类型和超出 4、8、10 字节范围的实数类型会引发错误,因为它们是非法的)。

第一组只是从右减去左: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是用来:

  • 二进制数据包括未知、Char/WChar、Set、Array、Record。如果二进制数据在 x86 和 x64 中为 1、2 或 4 字节大小以及在 x64 中为 8 字节,则将其作为整数进行比较。
  • 动态数组(多维时要小心!)。
  • 变体作为最后的手段(见下文)

对于可以比较的记录,执行运算符重载并让比较器使用这些运算符是有意义的。

1、2、4 或 8 字节的二进制数据是一个例外,它会在 little-endian 机器(Intel x86 和 x64,以及 little-endian 模式下的 bi-endian Arm)上给出奇怪的结果:

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;
于 2013-08-06T07:50:17.240 回答