2013-07-30 21 views
7

Trong sự giúp đỡ Delphi XE2 cho System.Generics.Collections.TArray.Sort, nó nóiTrình so sánh TArray.Sort mặc định thực sự làm gì và khi nào bạn sẽ sử dụng nó?

Note: If the Comparer parameter is provided, it is used to compare elements; otherwise the default comparator for the array elements is used. 

tôi đào lại một chút và thấy rằng so sánh mặc định cho TArray.Sort_LookupVtableInfo từ System.Generics.Defaults. Mã cho điều này là

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; 

Nó được gọi như

IComparer<T>(_LookupVtableInfo(giComparer, TypeInfo(T), SizeOf(T))) 

Tôi đã nhìn qua này khá nhiều và tôi không thực sự tất cả những gì tôi biết chắc chắn những gì nó làm. Liệu nó có so sánh bit trong bộ nhớ với nhau hay chính xác không?

Phần thứ hai của câu hỏi là khái quát hơn về tình huống nào bạn có thể thực sự muốn sử dụng trình so sánh mặc định hoặc bạn không bao giờ thực sự muốn sử dụng nó?

+2

FYI, bạn không có câu trả lời cho đến bây giờ vì bạn không sử dụng thẻ delphi. Thẻ phiên bản cụ thể phải luôn được sử dụng cùng với thẻ delphi chung. Cảm ơn bạn đã đến @SirRufo để tìm câu hỏi và gắn thẻ lại. –

+1

Vâng, điều này là nhận được một chút meta, nhưng tôi sẽ chỉ nói rằng câu hỏi delphi cuối cùng tôi đăng được gắn thẻ với thẻ delphi, nhưng một người dùng với danh tiếng cực cao đã quyết định rằng không đúng, loại bỏ nó và gắn thẻ nó delphi -xe2 (và tất nhiên đã xóa thẻ delphi của tôi) mặc dù tôi đã tuyên bố rõ ràng trong câu hỏi rằng mặc dù tôi đã sử dụng XE2, nó có thể không liên quan đến câu hỏi. Tôi đã cố gắng tránh nó trước khi emptively nhưng bây giờ tôi có thể thấy rằng mọi người dường như có những cách yêu thích riêng của họ về "giúp đỡ" câu hỏi. Tôi rất vui vì điều này thực sự đã giúp, mặc dù! – jep

+0

Tôi nghĩ rằng người dùng không hiểu các sắc thái của việc gắn thẻ delphi.Trong mọi trường hợp, câu hỏi đó dường như không phải là một câu hỏi cụ thể của delphi vì vậy tôi nghĩ rằng các thẻ ban đầu của bạn là tốt. Ở đây, đó là tất cả về Delphi và bạn cần thẻ chung. Câu hỏi này cũng được gắn thẻ, ngoài việc phân loại và so sánh cần được loại bỏ. Họ không phục vụ mục đích hữu ích. –

Trả lời

9

Trình so sánh mặc định cung cấp triển khai cho nhiều loại phổ biến. Cụ thể nó hỗ trợ như sau:

  • loại Integral: Byte, Word, Integer, vv
  • loại được liệt kê.
  • Loại điểm nổi.
  • Chuỗi.
  • Đặt.
  • Phiên bản lớp.
  • Biến thủ tục.
  • Phương pháp.
  • Các biến thể.
  • Mảng tĩnh.
  • Mảng động.
  • Giao diện.
  • Con trỏ.
  • Bản ghi.

Đối với nhiều loại này, việc triển khai mặc định chính xác là những gì bạn mong đợi. Ví dụ: đối với các số nguyên, các loại được liệt kê, các loại dấu phẩy động, việc triển khai sử dụng các toán tử <, >=. Đối với string, các cuộc gọi triển khai mặc định CompareStr.

Đối với các loại khác, triển khai mặc định có thể ít hữu ích hơn. Ví dụ, đối với các bản ghi, so sánh là một so sánh nhị phân bằng cách so sánh. Rất có khả năng bạn muốn cung cấp việc thực hiện của riêng bạn so sánh cho một bản ghi. Một điều cần lưu ý với các bản ghi là trình so sánh mặc định sẽ so sánh bất kỳ phần đệm nào trong bản ghi của bạn và bạn không bao giờ muốn làm điều đó. Vì vậy, nó không bao giờ hữu ích cho một bản ghi liên kết có đệm. Và tôi cũng đặt câu hỏi về tiện ích cho các bản ghi chứa các kiểu tham chiếu.

Đối với mảng động, triển khai mặc định so sánh độ dài trước, sau đó, nếu chiều dài bằng nhau, so sánh nội dung nhị phân của mảng. Vì vậy, điều đó có thể hợp lý đối với mảng các loại giá trị đơn giản. Nhưng đối với mảng động đa chiều hoặc mảng các loại tham chiếu, không quá nhiều.

Đối với trường hợp lớp, phương pháp, biến thủ tục, giao diện, trình so sánh mặc định coi toán hạng là con trỏ (hai con trỏ trong trường hợp phương thức) và thực hiện so sánh địa chỉ.

Khi nào bạn muốn sử dụng trình so sánh mặc định? Vâng, bạn sẽ sử dụng nó bất cứ khi nào nó phù hợp với yêu cầu của bạn để so sánh. Vì vậy, nó chắc chắn có ý nghĩa đối với các loại giá trị đơn giản. Ngoài ra, bạn cần quyết định từng trường hợp cụ thể.

+1

+1. Rất tốt giải thích. Tốt đẹp. –

+0

+1 Thông tin chi tiết. – Caleth

+0

Cảm ơn bạn đã vượt qua và vượt ra ngoài những gì tôi đã hỏi. Yêu cầu làm rõ, mặc dù. Bạn nói rằng đối với chuỗi nó sử dụng các toán tử so sánh thông thường. Điều này khác với việc sử dụng 'TStringComparer.Ordinal' (trả về' TOrdinalStringComparer') như thế nào? Đào qua mã đó cho thấy 'TOrdinalStringComparer' chỉ thực hiện kiểm tra độ dài và so sánh nhị phân. Liệu bộ so sánh mặc định bằng cách nào đó che khuất chính nó thông qua và sử dụng 'TOrdinalStringComparer' trong trường hợp của chuỗi là tốt hay họ chỉ lặp lại so sánh nhị phân? – jep

5

Chức năng bạn đã đăng ở đó không thực sự là chức năng so sánh, mà đúng hơn là một hàm trả về một chức năng so sánh, dựa trên TypeInfo và sizeof T.

Tiếp theo đó sâu hơn, chúng ta thấy trong Generics.Defaults nhiều chức năng có dạng:

function Compare_ tên của loại (Inst: Pointer; const Left, Right: loại ): Integer;

mà tất cả đều có cùng một cơ thể (nhưng lưu ý trái và phải có các loại khác nhau)

begin 
    if Left < Right then 
    Result := -1 
    else if Left > Right then 
    Result := 1 
    else 
    Result := 0; 
end; 

và cuối cùng cho tất cả mọi thứ còn lại

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; 
+0

Đó là cách phức tạp hơn thế. –

+0

Có, tôi chủ yếu xem xét trường hợp không thể xác định loại T. Cập nhật – Caleth

3

David đã làm một công việc tuyệt vời của bản văn mô tả như thế nào Người đối chiếu mặc định làm việc, nhưng đối với một số bạn nó có thể được dễ dàng hơn để làm theo khi bạn nhìn thấy cách mã cơ bản là có cấu trúc (và quyết định xem các trình so sánh mặc định có được áp dụng không).

Tôi sẽ chỉ bao gồm kiểu so sánh Compare_. Kiểu dáng Equals_ hoạt động theo cách tương tự.

gì xảy ra là _LookupVtableInfo chọn một giao diện IComparer cho Compare_ so sánh phong cách (và một phong cách IEqualityComparer cho Equals_).

Bên dưới những giao diện là giao diện không bình thường, nhưng giấy gói giao diện xung quanh các chức năng toàn cầu của mẫu này cho Compare_ phong cách:

function Compare_t<T>(Inst: Pointer; const Left, Right: T): Integer; 

và toàn cầu thủ tục của biểu mẫu này cho Equals_ phong cách:

function Equals_t<T>(Inst: Pointer; const Left, Right: T): Integer; 
function GetHashCode_t<T>(Inst: Pointer; const Left, Right: T): Integer; 

Các kết quả của các hàm phong cách Compare_ rất đơn giản, nhưng hơi khác với -1, 0, +1 mà một số người có thể mong đợi:

< 0 for Left < Right 
= 0 for Left = Right 
> 0 for Left > Right 

Đối với đa số trường hợp, việc thực hiện rất đơn giản:

Tôi đã nhóm lại các chức năng phong cách Compare_ bởi cách họ làm điều này.

  • Loại thông thường (bao gồm cả điều tra viên và Int64).
  • Điểm nổi (Real) (bao gồm cả Comp và Đơn vị tiền tệ).
  • Chuỗi ngắn (từ Turbo Pascal/Delphi 1 ngày).
  • Chuỗi rộng (kiểu dây OLE).
  • Phương pháp.
  • Con trỏ (bao gồm Lớp học, Giao diện, Tham chiếu và Thủ tục của lớp).

(Loại thông thường ngoài phạm vi 1, 2, 4, 8 byte và các loại thực ngoài phạm vi 4, 8, 10 byte nêu lỗi vì chúng là bất hợp pháp).

Nhóm thứ nhất chỉ trừ trái từ bên phải: ký/số nguyên unsigned của 1 hoặc 2 byte chiều dài

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; 

Nhóm thứ hai làm một so sánh:

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; 

Bây giờ chúng ta có được đến thú vị bit: các kết quả không đơn giản.

Sử dụng chuỗi CompareStr. Nếu bạn muốn một cái gì đó khác nhau, bạn có thể sử dụng 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 được sử dụng cho:

  • dữ liệu nhị phân trong đó không rõ, Char/WCHAR, Set, Array, Record. Ngoại lệ nếu dữ liệu nhị phân là 1, 2 hoặc 4 byte kích thước trong x86 và x64 và 8 byte trong x64, nó sẽ được so sánh dưới dạng số nguyên.
  • carrays động (hãy cẩn thận khi chúng là đa chiều!).
  • biến thể như một phương sách cuối cùng (xem thêm bên dưới)

Đối với hồ sơ mà có thể được so sánh, nó làm cho tinh thần để thực hiện hành quá tải, và có Comparer sử dụng những nhà khai thác.

Dữ liệu nhị phân trong tổng số 1, 2, 4 hoặc 8 byte là một ngoại lệ, mà sẽ cho kết quả kỳ lạ trên máy ít về cuối nhỏ (Intel x86 và x64, và Arm bi-endian trong chế độ ít về cuối nhỏ):

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; 

phần còn lại là nhị phân tinh khiết:

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; 

Như thường lệ, Variants đang ở trong một giải đấu của riêng mình. Đầu tiên, VarCompareValue đã được thử. Nếu không thành công thì Compare_UString sẽ được thử. Nếu điều đó không thành công, BinaryCompare sẽ được thử. Nếu điều đó thất bại: may mắn khó khăn.

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; 
Các vấn đề liên quan