2017-01-26 83 views
3

我有记录类型:如何初始化VAR Record参数

type 
    TIPInfo = record 
    IP, 
    HostName, 
    City, 
    Region, 
    Country, 
    Loc, 
    Org: WideString 
    end; 

函数返回的记录数据:

function GetPublicIPInfo(var IPInfo: TIPInfo): Boolean; 
begin 
    // initialize 
    FillChar(IPInfo, SizeOf(TIPInfo), 0); 

    // populate data 
    IPInfo.IP := GetVallue('ip'); 
    IPInfo.HostName := GetVallue('hostname'); 
    IPInfo.City := GetVallue('city'); 
    // etc... 

    Result := IsOk; 
end; 

来电者:

var 
    IPInfo: TIPInfo; 

if GetPublicIPInfo(IPInfo) then... // use data 

它是正确的通过调用FillChar来初始化var TIPInfo的方法,还是应该将每个字段设置为空字符串?呼叫者应该这样做吗?

另外,在这种情况下使用out参数会更好吗(因为函数没有读取数据)?

+0

我会用out参数时,函数不读取参数。此外,在这种情况下,很明显函数本身应该初始化它而不是调用者。 FillChar没有在每种情况下正确初始化字符串等托管类型(尽管它在您的情况下),所以我只是分别初始化每个字段。 –

+2

在这种情况下,所有记录字段都是'WideString',它由编译器自动初始化,所以函数根本不需要初始化任何东西。这就是说,我同意R.Beiboer,使用'out'而不是'var'是最好的选择。 –

+0

@RemyLebeau成员可以使用该对象以前使用的任意值。所以他们确实需要分配。 –

回答

5

使用只是FillChar在这里是错误的。如果任何WideString成员不为空,那么您将以这种方式泄漏它们。相反,我建议如下:

Finalize(IPInfo); 
FillChar(IPInfo, SizeOf(TIPInfo), 0); 

或者另一种方式是定义默认记录作为一个类型的常数:

const 
    DefaultIPInfo: TIPInfo =(); 

然后你可以使用简单的赋值:

IPInfo := DefaultIPInfo; 

在现代德尔福的版本,你可以使用这个更可读的代码:

IPInfo := Default(TIPInfo); 

欲了解更多关于这个问题,请参阅以下主题:

注意,在你的代码泄漏是很难找到,因为WideString变量作为COM实现BSTR对象,并在COM堆上分配。因此,如果你使用Delphi内存管理器的内存泄漏检测功能,泄漏将不会被检测到,因为它是从不同的堆泄漏出来的。

对于您的情况,由于您的记录是托管类型,并且只包含托管类型,因此您可以使用out参数以取得良好效果。对于托管类型,out参数意味着,编译器将生成的代码,在调用点,默认传递之前初始化记录

考虑下面的程序:

{$APPTYPE CONSOLE} 

type 
    TRec = record 
    Value: WideString; 
    end; 

procedure Foo1(var rec: TRec); 
begin 
end; 

procedure Foo2(out rec: TRec); 
begin 
end; 

procedure Main; 
var 
    rec: TRec; 
begin 
    rec.Value := 'Foo'; 
    Foo1(rec); 
    Writeln(rec.Value); 
    Foo2(rec); 
    Writeln(rec.Value); 
end; 

begin 
    Main; 
end. 

输出是:

 
Foo 

如果您的记录包含托管和非托管类型的组合,那么情况就不太好。

{$APPTYPE CONSOLE} 

type 
    TRec = record 
    Value1: WideString; 
    Value2: Integer; 
    end; 

procedure Foo1(var rec: TRec); 
begin 
end; 

procedure Foo2(out rec: TRec); 
begin 
end; 

procedure Main; 
var 
    rec: TRec; 
begin 
    rec.Value1 := 'Foo'; 
    rec.Value2 := 42; 
    Foo1(rec); 
    Writeln(rec.Value1); 
    Writeln(rec.Value2); 
    Foo2(rec); 
    Writeln(rec.Value1); 
    Writeln(rec.Value2); 
end; 

begin 
    Main; 
end. 

输出是:

 
Foo 
42 

42 

只有管理成员是默认初始化参数out。所以最好的选择是默认初始化变量,即使它作为out参数传递。

更多out参数可以在这里找到:What's the difference between "var" and "out" parameters?

+0

所以你建议**总是**调用'Finalize(IPInfo); FillChar(IPInfo,SizeOf(TIPInfo),0);'? – zig

+0

我的意见是,你应该编写代码,以便它对未来的变化很有用。这意味着默认初始化记录始终,即使它恰好只包含托管类型。如果你在问题中记录,那么你可以使用'out'参数,并依靠编译器在调用站点默认初始化。但是,在未来的某一天,您将向记录中添加一个非托管成员,并且几英里之外的某些功能将会中断。我的意见是'out'几乎是无用的。 –

+1

我再次问:我总是可以调用'Finalize(IPInfo); FillChar(IPInfo,SizeOf(TIPInfo),0);'作为一般**解决方案?有没有一个通用的解决方案? – zig