6

我必须翻译一些 Fortran 90 代码并找到一个有趣的语言功能。

例如,他们定义了以下类型和动态数组变量:

TYPE WallInfo
  CHARACTER(len=40) :: Name
  REAL              :: Azimuth
  REAL              :: Tilt
  REAL              :: Area
  REAL              :: Height
END TYPE WallInfo

TYPE(WallInfo), ALLOCATABLE, DIMENSION(:) :: Wall

稍后在代码中,他们调用了一个函数:

CALL HeatFlow(Wall%Area, Wall%Azimuth)

作为一个 Delphi 程序员,这让我有点吃惊,因为 Wall 是一个记录数组!

从例程中的用法可以看出,Fortran 可以将记录数组中的字段投影为自己的数组。

SUBROUTINE HeatFlow( Area, Azimuth )
  REAL, INTENT(IN), DIMENSION(:) :: Area
  REAL, INTENT(IN), DIMENSION(:) :: Azimuth

有谁知道是否有办法用 Delphi 做到这一点(我使用的是 2010 版)?

我可以编写一个函数来将记录值提取为数组,但这有点乏味,因为我必须为每个字段编写一个专用例程(而且有很多)。

我希望 Delphi 2010 中有一些我错过的语言功能。

4

3 回答 3

8

使用扩展 RTTI,可以创建一个通用函数,该函数将数组和字段名称作为输入,并使用数组的 RTTI 仅提取该字段的值并使用它们创建一个具有正确数据类型的新数组。

以下代码在 XE2 中适用于我:

uses
  System.SysUtils, System.Rtti;

type
  FieldArray<TArrElemType, TFieldType> = class
  public
    class function Extract(const Arr: TArray<TArrElemType>; const FieldName: String): TArray<TFieldType>;
  end;

class function FieldArray<TArrElemType, TFieldType>.Extract(const Arr: TArray<TArrElemType>; const FieldName: String): TArray<TFieldType>;
var
  Ctx: TRttiContext;
  LArrElemType: TRttiType;
  LField: TRttiField;
  LFieldType: TRttiType;
  I: Integer;
begin
  Ctx := TRttiContext.Create;
  try
    LArrElemType := Ctx.GetType(TypeInfo(TArrElemType));
    LField := LArrElemType.GetField(FieldName);
    LFieldType := Ctx.GetType(TypeInfo(TFieldType));
    if LField.FieldType <> LFieldType then
      raise Exception.Create('Type mismatch');
    SetLength(Result, Length(Arr));
    for I := 0 to Length(Arr)-1 do
    begin
      Result[I] := LField.GetValue(@Arr[I]).AsType<TFieldType>;
    end;
  finally
    Ctx.Free;
  end;
end;

.

type
  WallInfo = record
    Name: array[0..39] of Char;
    Azimuth: Real;
    Tilt: Real;
    Area: Real;
    Height: Real;
  end;

procedure HeatFlow(const Area: TArray<Real>; const Azimuth: TArray<Real>);
begin
  // Area contains (4, 9) an Azimuth contains (2, 7) as expected ...
end;

var
  Wall: TArray<WallInfo>;
begin
  SetLength(Wall, 2);

  Wall[0].Name := '1';
  Wall[0].Azimuth := 2;
  Wall[0].Tilt := 3;
  Wall[0].Area := 4;
  Wall[0].Height := 5;

  Wall[1].Name := '6';
  Wall[1].Azimuth := 7;
  Wall[1].Tilt := 8;
  Wall[1].Area := 9;
  Wall[1].Height := 10;

  HeatFlow(
    FieldArray<WallInfo, Real>.Extract(Wall, 'Area'),
    FieldArray<WallInfo, Real>.Extract(Wall, 'Azimuth')
    );
end;
于 2012-08-09T00:04:35.860 回答
7

我将其发布为答案,因为评论有点过于有限,无法表达这一点。

这个答案试图解释 FORTRAN 和 Delphi 中数组和记录的内存布局的差异,并修改了Todd Grigsby答案Remy Lebeau的答案(我都赞成)。

FORTRAN 和其他一些以计算为中心的语言按列主要顺序存储嵌套数组。Delphi 和许多其他语言都使用行大命令

从内存的角度来看,一条记录只不过是一个字段数组,它们:

  • 有名字而不是索引
  • 可能有不同的类型

对于计算密集型操作,当您的算法有利于列时,存储嵌套数组列的主要顺序是有意义的。与行主要订单相同。因此,在循环中,您需要将索引的顺序与存储的顺序相匹配

鉴于 FORTRAN 中的此记录和数组定义:

TYPE WallInfo
  CHARACTER(len=40) :: Name
  REAL              :: Azimuth
  REAL              :: Tilt
  REAL              :: Area
  REAL              :: Height
END TYPE WallInfo

TYPE(WallInfo), ALLOCATABLE, DIMENSION(:) :: Wall

以及 Delphi 中的功能等效定义:

type
  WallInfo = record
    Name: array[0..39] of Char;
    Azimuth: Real;
    Tilt: Real;
    Area: Real;
    Height: Real;
  end;

var
  Wall: array of WallInfo;

和一个由 3 个 WallInfo 元素组成的数组,这就是内存布局的外观(它们都是连续的内存区域,我将它们分成几行以保持可读性):

在 FORTRAN 中:

Name[0,0]...Name[0,39], Name[1,0]...Name[1,39], Name[2,0]...Name[2,39],
Azimuth[0], Azimuth[1], Azimuth[2],
Tilt[0], Tilt[1], Tilt[2],
Area[0], Area[1], Area[2],
Height[0], Height[1], Height[2],

在德尔福:

Name[0,0]...Name[0,39], Azimuth[0], Tilt[0], Area[0], Height[0],
Name[1,0]...Name[1,39], Azimuth[1], Tilt[1], Area[1], Height[1],
Name[2,0]...Name[2,39], Azimuth[2], Tilt[2], Area[2], Height[2],

所以这个 FORTRAN 电话:

CALL HeatFlow(Wall%Area, Wall%Azimuth)

只会将指向 Area[0] 和 Azimuth[0] 内存位置的指针以及这些内存区域的长度传递给函数。

在 Delphi 中,这是不可能的,所以你必须

  1. 构建新的区域和方位角阵列
  2. 从名为 Wall 的 WallInfo 记录实例数组中的信息中复制它们
  3. 将它们发送到函数
  4. 如果这些是 var 参数:将两个数组中的更改复制回 Wall

Todd GrigsbyRemy Lebeau使用直接的 Delphi 代码或 Delphi 记录 RTTI 在他们的答案中展示了前三个步骤。
第 4 步的工作方式类似。

两种解决方案都使用 Delphi 2009 中引入的泛型。
在 Delphi 2010 之前,记录上的 RTTI 非常少),因此您得到了两个答案的正确 Delphi 版本。

注意(再次):将您的算法从 FORTRAN 转换为 Delphi 时,请确保注意数组中的循环和其他索引,因为列/行的主要变化。

于 2012-08-09T09:34:06.780 回答
2

要回答您的问题,不,没有语言构造或便捷方法可以将记录数组中的单个列拆分为自己的简单数组。

我会推荐如下内容:

function SplitColumn( RecordArray : Array of {recordtype} ) : Array of {columntype};
var
  column : array of {type};
  x : Integer;
begin
  setlength( result, high( RecordArray ) + 1 );
  for x := 0 to high( RecordArray ) do
    result[ x ] := RecordArray[ x ].{columnname};
end;

那就是如果你想使用动态数组。就个人而言,如果您要移植它,我会使用 List 和 List,如下所示:

type
   TWallList = class( TList<TWallInfo> );
   TDoubleList = class( TList<Double> );

function SplitColumn( WallList : TWallList; AreaList, AzimuthList : TDoubleList ); 
var
  x : Integer;
begin
  for x := 0 to RecList.Count-1 do
  begin
    AreaList.add( RecordArray[ x ].Area );
    Azimuth.add( RecordArray[ x ].Azimuth );
  end;
end;
于 2012-08-08T21:36:32.000 回答