我必须翻译一些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中有一些我错过的语言功能。
答案 0 :(得分: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;
答案 1 :(得分:7)
我发布这个作为答案,因为评论有点过于局限于此。
这个答案试图解释FORTRAN和Delphi中数组和记录的内存布局的差异,并修改answer Todd Grigsby和answer Remy Lebeau(I {}赞成两者。)
FORTRAN和其他一些以计算为中心的语言在column major order中存储嵌套数组。 Delphi和许多其他语言使用row major order。
从内存的角度来看,记录只不过是一个字段数组:
对于计算密集型操作,当您的算法支持列时,存储嵌套数组列主要顺序是有意义的。行主要订单相同。所以在循环中,你需要match the order of your indexes with the order of your storage。
在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中,这是不可能的,所以你必须
Todd Grigsby和Remy Lebeau使用直接Delphi代码或Delphi记录RTTI显示了他们答案的前三个步骤。
第4步以类似的方式工作。
这两种解决方案都使用Delphi 2009中引入的泛型 Until Delphi 2010, RTTI on records was very minimal),所以你得到了正确的Delphi版本的答案。
注意(再次):在将算法从FORTRAN转换为Delphi时,请确保在列中/行主要更改时观察数组中的循环和其他索引。
答案 2 :(得分: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;