遍历记录字段

Iterate over record fields

如何用for循环遍历G1-G4的记录字段?

  TrFireGroup = record   
    idx: integer; 
    G1: integer;
    G2: integer;
    G3: integer;
    G4: integer;
    FGroup: integer;
    MstIdx: integer; 
    Status: byte;
    procedure Clear;
  end;

这可能不是一个好主意,但它是可能的。

procedure Test();
var
  i: Integer;
  rec: TrFireGroup;
  GPtr: PInteger;
  value: Integer;
begin
  // Initialize rec ...

  // Get pointer to first G? field
  GPtr := @rec.G1;

  // Loop over G fields
  for i := 0 to 3 do
  begin
    // Use value G? field here...
    // For example: value := GPtr^;

    // Increment pointer, thus moving it with four bytes.
    Inc(GPtr);
  end;
end;
  • 首先得到一个指向rec.

  • 中第一个字段的指针(GPtr)
  • 然后,我们通过取消引用指针(GPtr^)来使用该值。

  • 最后,将指针递增到下一个值 (Inc(GPtr))。 请注意,增量是将指针移动到被引用类型的大小。

    并且由于我们正在使用指向整数 (PInteger) 的指针,Inc 将指针移动到 SizeOf(Integer),从而前进到记录中的下一个字段。

同样,这不是好的做法。

在这种情况下,指针运算有点矫枉过正,除非记录中有数百个字段(这种情况非常罕见),但如果确实需要,这是一种实现方法。

仅供参考,Delphi中的指针和指针运算在这里有非常详细和高质量的解释:

如果您需要维护现有的记录接口结构以实现兼容性,您可以使用属性来执行此操作。将值存储在数组中允许您枚举它们,并且您可以使用现有名称定义属性以直接以现有方式访问值。

program Project1;    
{$APPTYPE CONSOLE}   

type
  TGArray = array[0..3] of integer;
  TrFireGroup = record
    private
      FGArr : TGArray;
    public
      idx: integer;
      FGroup: integer;
      MstIdx: integer;
      Status: byte;         
      property G1 : integer read FGArr[0] write FGArr[0];
      property G2 : integer read FGArr[1] write FGArr[1];
      property G3 : integer read FGArr[2] write FGArr[2];
      property G4 : integer read FGArr[3] write FGArr[3];
      property GArray : TGArray read FGArr;
  end;

var
  LFireGroup : TrFireGroup;
  j : integer;
begin
  LFireGroup.G1 := 1;
  LFireGroup.G2 := 3;
  LFireGroup.G3 := 5;
  LFireGroup.G4 := 7;

  for j in LFireGroup.GArray do
    WriteLn(j);    
  ReadLn;
end.

如果您需要记录布局与二进制兼容(无论出于何种原因),您可以放弃一些整洁并重新安排内容

  TrFireGroup = record        
    idx: integer;
    GArray : TGArray;
    FGroup: integer;
    MstIdx: integer;
    Status: byte;
    procedure Clear;         
    property G1 : integer read GArray[0] write GArray[0];
    property G2 : integer read GArray[1] write GArray[1];
    property G3 : integer read GArray[2] write GArray[2];
    property G4 : integer read GArray[3] write GArray[3];          
  end;

GArray 保留为 public 字段还可以让您进行索引写入(而将其作为普通 属性 是不可能的)- 即:

for i := Low(TGArray) to High(TGArray) do
    LFireGroup.GArray[i] := 2*i;

或者,如果您可以放弃 for/in 语义,您可以将数组定义为默认值 属性 并直接引用记录:

  TrFireGroup = record
    private
      FGArr : TGArray;
      procedure SetG(index:integer; Value:integer);
      function GetG(index:integer) : integer;
    public
      property G1 : integer read FGArr[0] write FGArr[0];
      property G2 : integer read FGArr[1] write FGArr[1];
      property G3 : integer read FGArr[2] write FGArr[2];
      property G4 : integer read FGArr[3] write FGArr[3];
      property GArray[index:integer]:integer read GetG write SetG; default;
  end;

procedure TrFireGroup.SetG(index:integer; Value:integer);
begin
  FGArr[index] := Value;
end;

function TrFireGroup.GetG(index: Integer) : integer;
begin
  result := FGArr[index];
end;

这让你可以做到:

for i := Low(TGArray) to High(TGArray) do
  LFireGroup[i] := 2*i;

for i := Low(TGArray) to High(TGArray) do
  WriteLn(LFireGroup[i]);

如果您使用的是最新的 Delphi 版本并且只需要 G1..G4 的当前值(即不写入它们),您也可以使用这种方法:

var
  I: Integer;
  fg: TrFireGroup;
  arr: TArray<Integer>;
begin
  ...
  arr := [fg.G1, fg.G2, fg.G3, fg.G4];
  for I in arr do begin
    Writeln(I);
  end;
  ...
end;

恕我直言,如果您不需要二进制兼容性,那么最简单的选择是使用 variant record:

TrFireGroup = record
  idx: integer;
  FGroup: integer;
  MstIdx: integer;
  Status: byte;
  procedure Clear;
  case Integer of
    0: (G1: integer; G2: integer; G3: integer; G4: integer);
    1: (GArr: array[0..3] of integer);
end;

这相当于旧的 Delphi absolute 关键字。既是数组又是4个G?变量在内存中共享相同的位置。