Delphi - 指向 TRecord 的常量数组?

问题描述

如何让变量指向记录数组?

注意:我想将 TRecord 的预定义数组作为常量... 但是在代码中,我需要变量 'W' 来记录要使用的记录数组。

请注意,我不希望使用 TRecord 的构造函数代码中(动态地)创建 TRecord 数组,而是希望拥有静态数组(因为数据不会改变)。

如何让变量“W”“记录”TRecord 的哪个数组?

请看下面的代码 - 更容易理解我的意思。

procedure TForm1.Button1Click(Sender: TObject);
type
  TTestRec = record
    X: string;
    Y: Integer;
  end;
  TMyArr = TArray<TTestRec>;
const
  ARRAY_A : TArray<string> = ['A1','A2','A3','A4'];
  ARRAY_B : TArray<string> = ['B1','B2','B3'];

  ARRAY_C : array[1..2] of TTestRec = (
    (X: 'testC1'; Y:1),(X: 'testC2'; Y:2)
    );
  ARRAY_D : array[1..3] of TTestRec = (
    (X: 'testD1'; Y:3),(X: 'testD2'; Y:4)
    (X: 'testD3'; Y:9)
    );
var
  Z : TArray<string>;
  W : array of TTestRec;
begin
  Z := ARRAY_A;  // this works
  Z := ARRAY_B;  // this works

  W := ARRAY_C;  // this does not work
  W := ARRAY_D;  // this does not work
end;

解决方法

ARRAY_AARRAY_B 分配给 Z 有效,因为您将 TArray<string> 常量分配给 TArray<string> 变量。它们都是相同的类型,因此可以相互兼容。

ARRAY_CARRAY_D 分配给 W 不起作用,因为您将静态数组常量分配给动态数组变量。它们是彼此不兼容的不同类型,如 Delphi 的文档中所述:

Type Compatibility and Identity (Delphi)

类型兼容性

每种类型都与自身兼容。如果两个不同的类型至少满足以下条件之一,则它们兼容

  • 它们都是真实的类型。
  • 它们都是整数类型。
  • 一种类型是另一种类型的子范围。
  • 两种类型都是同一类型的子范围。
  • 两者都是具有兼容基类型的集合类型。
  • 两者都是具有相同字符数的压缩字符串类型。
  • 一种是字符串类型,另一种是字符串、压缩字符串或 Char 类型。
  • 一种类型是 Variant,另一种是整数、实数、字符串、字符或布尔类型。
  • 两者都是类、类引用或接口类型,一种类型派生自另一种类型。
  • 一种类型是 PAnsiCharPWideChar,另一种是 array[0..n] of PAnsiCharPWideChar 形式的从零开始的字符数组。
  • 一种类型是 Pointer(无类型指针),另一种是任何指针类型。
  • 两种类型都是指向同一类型的(类型化)指针,并且 {$T+} 编译器指令有效。
  • 两者都是过程类型,具有相同的结果类型、相同的参数数量以及相应位置的参数之间的类型标识。

作业兼容性

分配兼容性不是对称关系。如果表达式的值落在 T1 的范围内并且至少满足以下条件之一,则可以将类型为 T2 的表达式分配给类型为 T1 的变量:

  • T1 和 T2 属于同一类型,并且不是包含任何级别的文件类型的文件类型或结构化类型。
  • T1 和 T2 是兼容的序数类型。
  • T1 和 T2 都是实数类型。
  • T1 是实数类型,T2 是整数类型。
  • T1 为 PAnsiCharPWideCharPChar 或任何字符串类型,且表达式为字符串常量。
  • T1 和 T2 都是字符串类型。
  • T1 是字符串类型,T2 是 Char 或压缩字符串类型。
  • T1 是一个长字符串,T2 是 PAnsiCharPWideCharPChar
  • T1 和 T2 是兼容的压缩字符串类型。
  • T1 和 T2 是兼容的集合类型。
  • T1 和 T2 是兼容的指针类型。
  • T1 和 T2 都是类、类引用或接口类型,T2 是从 T1 派生的。
  • T1 是接口类型,T2 是实现 T1 的类类型。
  • T1 是 PAnsiCharPWideChar,T2 是 array[0..n] of Char 形式(当 T1 为 PAnsiChar)或 WideChar 形式的从零开始的字符数组(当 T1 为 PWideChar 时)。
  • T1 和 T2 是兼容的程序类型。 (在某些赋值语句中,函数或过程标识符被视为过程类型的表达式。请参阅本章前面的“语句和表达式中的过程类型”。)
  • T1 是 Variant,T2 是整数、实数、字符串、字符、布尔值、接口类型或 OleVariant 类型。
  • T1 是 OleVariant,T2 是整数、实数、字符串、字符、布尔值、接口或 Variant 类型。
  • T1 是整数、实数、字符串、字符或布尔类型,T2 是 VariantOleVariant
  • T1 是 IUnknownIDispatch 接口类型,T2 是 VariantOleVariant。 (如果 T1 是 varEmpty,则变体的类型代码必须是 varUnknownvarDispatchIUnknown,如果 T1 是 varEmpty,则必须是 varDispatchIDispatch {1}}。)

ARRAY_AARRAY_B 分配给 Z 满足“分配兼容性”要求。将 ARRAY_CARRAY_D 分配给 W 不会。

要解决静态数组的问题,您必须改用指针(动态数组已经是指针),例如:

procedure TForm1.Button1Click(Sender: TObject);
type
  TTestRec = record
    X: string;
    Y: Integer;
  end;
  PTestRec = ^TTestRec;
const
  ARRAY_A : TArray<string> = ['A1','A2','A3','A4'];
  ARRAY_B : TArray<string> = ['B1','B2','B3'];

  ARRAY_C : array[1..2] of TTestRec = (
    (X: 'testC1'; Y:1),(X: 'testC2'; Y:2)
    );
  ARRAY_D : array[1..3] of TTestRec = (
    (X: 'testD1'; Y:3),(X: 'testD2'; Y:4)
    (X: 'testD3'; Y:9)
    );
var
  Z : TArray<string>;
  W : PTestRec;
begin
  Z := ARRAY_A;
  Z := ARRAY_B;

  W := @ARRAY_C[1];
  W := @ARRAY_D[1];
end;

但是请注意,无法从 W 本身确定它是指向 array[1..2] of TTestRec 还是 array[1..3] of TTestRec,这是两种完全不同的类型。因此,如果您需要使用 W 来迭代数组,则必须单独跟踪可接受的边界,例如:

{$POINTERMATH ON}

procedure TForm1.Button1Click(Sender: TObject);
type
  TTestRec = record
    X: string;
    Y: Integer;
  end;
  PTestRec = ^TTestRec;
const
  ARRAY_A : TArray<string> = ['A1',(X: 'testD2'; Y:4)
    (X: 'testD3'; Y:9)
    );
var
  Z : TArray<string>;
  W : PTestRec;
  W_Len,I: Integer;
begin
  Z := ARRAY_A;
  for I := 0 to High(Z) do begin
    // use Z[I] as needed ...
  end;

  Z := ARRAY_B;
  for I := 0 to High(Z) do begin
    // use Z[I] as needed ...
  end;

  W := @ARRAY_C[1];
  W_Len := Length(ARRAY_C);
  for I := 0 to Pred(W_Len) do begin
    // use W[I] as needed ...
  end;

  W := @ARRAY_D[1];
  W_Len := Length(ARRAY_D);
  for I := 0 to Pred(W_Len) do begin
    // use W[I] as needed ...
  end;
end;
,

Remy 已经解释了您的问题,但我想提出一个替代答案。

您不能将静态数组分配给动态数组,但您可以将动态数组隐藏在记录中并使该记录执行您要执行的操作。

每个静态数组都不同,因此您需要单独处理每个数组,但由于您想使用常量数组,我猜不同长度的数量是有限的。

这是对处理我所谈论内容的代码的重写。例行测试在外观上与您的示例非常相似。

unit UnitTest;

interface

implementation

type
  TTestRec = record
    X: string;
    Y: Integer;
  end;

  TDArray = array of TTestRec;
  TSArray2 = array[1..2] of TTestRec;
  TSArray3 = array[1..3] of TTestRec;

  TMyArr = record
    Items : array of TTestRec;
  private
    function GetItem(const i: integer): TTestRec;
    procedure SetItem(const i: integer; const Value: TTestRec);
    function GetCount: integer;
  public
    class operator implicit (a : TDArray ) : TMyArr;
    class operator implicit (a : TMyArr ) : TDArray;
    class operator implicit (a : TSArray2  ) : TMyArr;
    class operator implicit (a : TSArray3  ) : TMyArr;

    property Item[ const i : integer ] : TTestRec
             read GetItem
             write SetItem; default;
    property Count : integer
             read GetCount;
  end;

const
  ARRAY_A : TArray<string> = ['A1','B3'];

  ARRAY_C : TSArray2 = (
    (X: 'testC1'; Y:1),(X: 'testC2'; Y:2)
    );
  ARRAY_D : TSArray3 = (
    (X: 'testD1'; Y:3),(X: 'testD2'; Y:4),(X: 'testD3'; Y:9)
    );
var
  Z : TArray<string>;
  W : TMyArr;

procedure Test;
var
  iTest : TTestRec;
  i : integer;
begin
  Z := ARRAY_A;  // this works
  Z := ARRAY_B;  // this works

  ARRAY_A[ 1 ] := 'Fred';
  W := ARRAY_C;  // this does not work
  iTest := W[1];
  W := ARRAY_D;  // this does not work
  iTest := W[ 1 ];
  // iteration
  for i := 0 to W.Count - 1 do
  begin
    iTest := W[i]; //etc
  end;
end;


{ TMyArr }

class operator TMyArr.implicit(a: TMyArr): TDArray;
var
  i: Integer;
begin
  SetLength( Result,Length( a.Items ) );
  for i := 0 to Length( a.Items ) - 1 do
  begin
    Result[ i ] := a.Items[ i ];
  end;
end;

class operator TMyArr.implicit(a: TDArray): TMyArr;
var
  i: Integer;
begin
  SetLength( Result.Items,Length( a ));
  for i := 0 to Length( a ) - 1 do
  begin
    Result.Items[ i ] := a[i + 1]; //not zero based!
  end;
end;


class operator TMyArr.implicit(a: TSArray2): TMyArr;
var
  i: Integer;
begin
  SetLength( Result.Items,Length(a));
  for i := 0 to Length(a) - 1 do
  begin
    Result.Items[i] := a[i + 1];
  end;
end;

function TMyArr.GetCount: integer;
begin
  Result := Length( Items );
end;

function TMyArr.GetItem(const i: integer): TTestRec;
begin
  Result := Items[ i ];
end;

class operator TMyArr.implicit(a: TSArray3): TMyArr;
var
  i: Integer;
begin
  SetLength( Result.Items,Length(a));
  for i := 0 to Length(a) - 1 do
  begin
    Result.Items[i] := a[i];
  end;
end;

procedure TMyArr.SetItem(const i: integer; const Value: TTestRec);
begin
  Items[ i ] := Value;
end;

end.

编辑

已经展示了如何迭代。我已经添加了一个属性调用计数和显示在测试例程如何遍历