Delphi:为什么在过程中不能分配TStringList?

6
第一个步骤:
procedure TestOne(List : TStringList);
var
  TempList : TStringList;
begin
  TempList := TStringList.Create;
  TempList.Add('Test');
  List := TempList;
  TempList.Free;
end;

procedure TForm1.Button1Click(Sender : TObject);
var
  aList : TStringList;
begin
  aList := TStringList.Create;
  TestOne(aList);
  Memo1.Lines := aList;
end;

当我点击按钮时,备忘录没有显示任何内容,断点显示该过程没有执行此行:

  List := TempList;

这一行

我修改了该程序:

procedure TestTwo(List : TStringList);
var
  TempList : TStringList;
begin
  TempList := TStringList.Create;
  TempList.Add('Test');
  List.Text := TempList.Text;
  //or
  List.Assign(TempList);
  //List := TempList;
  TempList.Free;
end;

这一次它工作了。

那么为什么不能使用List := TempList;呢?

1个回答

15

当您按值传递变量时,Delphi会在堆栈中复制参数值,并且在方法内部对该参数进行的所有更改都是针对该副本进行的。

这就是自Turbo Pascal时代以来Pascal的工作方式,也许从一开始就是这样。考虑以下内容:

procedure TestInt(Int: Integer);
begin
  Int := 10;
  Writeln(Int); //writes 10
end;

var
  I: Integer;
begin
  I := 5;
  Wirteln(I); //writes 5
  TestInt(I);
  Writeln(I); //also writes 5
end.

现在,当您将对象作为参数传递时,您必须记住对象变量是对该对象的引用(指向堆中实际存储对象的地址的指针)。但是,如果您通过引用传递参数,则仍适用上述规则:在堆栈中制作引用的副本。您在方法/过程内部对该引用所做的任何更改都是在该副本上进行的。 List := TempList; 这一行只是更改引用,使其指向不同的内存位置中的另一个对象。当该过程返回时,该值将被丢失,就像 TestInt 过程返回时整数值被丢失一样。
事实上,这行代码从未执行是优化器在起作用。由于新值从未被使用,优化器会在最终的可执行文件中消除赋值语句,因此该行代码实际上从未执行过。
您可以更改参数声明以通过引用(var 参数)传递它,但这对于使用对象来说并不是一个好主意,因为您必须考虑谁负责释放对象的内存以避免内存泄漏。
您没有说明您要完成什么样的任务,但看起来 Assign 是正确的方法,因为赋值会将字符串从一个列表复制到另一个列表中。您必须直接操作 List 而不是使用 TempList,像这样:
procedure TestOne(List : TStringList);
begin
  List.Clear;
  List.Add('Test');
end;

procedure TForm1.Button1Click(Sender : TObject);
var
  aList : TStringList;
begin
  aList := TStringList.Create;
  TestOne(aList);
  Memo1.Lines := aList;
end;

正如你所看到的,结果是相同的。

编辑

Rob在评论中指出了一些重要的问题,因此我将解释为什么 Button1Click 方法的最后一行是有效的:Memo1.Lines := aList;

这看起来像是一个直接赋值,但你必须知道在那一行中,你正在处理一个Delphi 属性

属性(property)与字段(field)类似,都定义了对象的某个属性。但是,尽管字段只是存储位置,其内容可以被查看和更改,但是属性则将特定操作与读取或修改其数据相关联。属性提供对对象属性的访问控制,并允许计算属性。

属性的声明指定名称和类型,并包括至少一个访问指定符。

看一下 TCustomMemo 的 Lines 属性如何声明:

  TCustomMemo = class(TCustomEdit)
  ..
  ..
    property Lines: TStrings read FLines write SetLines;

这意味着当你给属性分配一个值时,实际上是调用了 SetLines 方法并将 Value 作为参数传递进去,就像这样:
  Memo1.SetLines(AList);

SetLines实现如下:

procedure TCustomMemo.SetLines(Value: TStrings);
begin
  FLines.Assign(Value);
end;

所以,最终你将发出相同的 TStrings.Assign 调用,从源列表复制所有字符串到目标列表。

这是 Delphi 处理对象属性并保持对对象拥有权的一种方式。每个组件创建并拥有它自己的子对象,而你则创建并拥有你自己的对象。

赋值运算符(:=)在属性上的使用是语法糖,允许组件编写者在读取或写入值时引入副作用,并允许你作为程序员像处理标准字段一样思考属性,并享受那些附加效果带来的便利。


1
你可能想要提到为什么Memo1.Lines的赋值可以正常工作,而其他的却不行。这种不一致性可能是导致最初产生这个问题的混淆的原因。 - Rob Kennedy
1
非常好的回答和出色的编辑,附有Memo.Lines的解释。如果SO允许我的话,我会点赞很多次。 - Marjan Venema

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接