Delphi通过引用或值/副本传递参数

时间:2012-03-03 04:33:10

标签: delphi pointers pass-by-reference pascal

背景1

var text:String;

text:='hello';

myFunc(text);

上下文2

function myFunc(mytext:String);
var textcopy:String;
begin

    textcopy:=mytext;

end;
从Context1调用Context2上的

myFunc,局部变量mytext指向Context2之外的内存?或者mytext在范围内有自己的内存空间,并使用text的相同内容填充/复制?我可能遗漏了一些非常基本的东西,因为我收到了access violation错误。

有没有办法明确指定一个函数是应该通过引用还是按值接收参数,然后像C一样复制?我不确定我是怎么做的。

3 个答案:

答案 0 :(得分:23)

Delphi字符串的内存管理有点不寻常。在您致电myFunc(text)并指定textcopy := mytext后,所有三个变量(textmytexttextcopy)将指向同一地址,即原始字符串。

但是只要您使用其中一个变量对字符串进行更改,Delphi就会在后台克隆字符串,并将更改应用于副本。另外两个变量仍指向原始变量,因此它们保持不变。因此,在Context 1中不会看到上下文2中所做的任何更改 - 这种“写时复制”机制有效地为您提供了按值传递的语义。所有这些字符串都是引用计数的,并且一旦所有引用都超出范围,它们将自动释放。

然而,有一个例外。如果使用指针而不是字符串操作访问字符串,则可以绕过复制步骤,更改将影响原始字符串。您还将绕过引用计数逻辑,并可能最终得到一个指向已释放的内存块的指针。这可能是您违反访问权限的原因,但我不能说没有更多细节/更多代码。

如果您想要引用传递,请将您的函数声明为myFunc(var mytext: String)。如果你想强制Delphi复制字符串,而不是等到它被修改,你可以使用System.UniqueString

答案 1 :(得分:13)

在Delphi中,string是一种通常类似于值类型的引用类型。它在堆上分配(不像大多数值类型那样堆栈),并具有自动引用计数和写时复制语义。

要了解这意味着什么,请考虑正常值类型,例如:一个整数,当作为参数传递给过程时表现:

var
  gValue: Integer;

procedure PassByValue(aValue: Integer);
begin
  // Here @gValue <> @aValue
  aValue := aValue + 2;
  // Here @gValue <> @aValue
end;

procedure PassByRefrenceInOut(var aValue: Integer);
begin
  // Here @gValue = @aValue
  aValue := aValue + 2;
  // Here @gValue = @aValue
end;

procedure CallProcedures;
begin
  gValue := 0; 
  PassByValue(gValue);
  // gValue is still 0
  PassByReferenceInOut(gValue);
  // gValue is 2
end;

PassByReferenceInOut中的var参数等效于将指针传递给参数的C约定。

相同的语义适用于字符串参数传递,但值的内部表示存在细微差别:

var
  gValue: string;

procedure PassByValue(aValue: string);
begin
  // Here PChar(gValue) = PChar(aValue) <<<<
  aValue := aValue + '2';
  // Here PChar(gValue) <> PChar(aValue)
end;

procedure PassByRefrenceInOut(var aValue: string);
begin
  // Here PChar(gValue) = PChar(aValue)
  aValue := aValue + '2';
  // Here PChar(gValue) = PChar(aValue)
end;

procedure CallProcedures;
begin
  gValue := ''; 
  PassByValue(gValue);
  // gValue is still ''
  PassByReferenceInOut(gValue);
  // gValue is '2'
end;

如果要确保过程在其自己的字符串值副本上运行,请使用UniqueString过程,例如:

procedure PassByValue(aValue: string);
begin
  // Here PChar(gValue) = PChar(aValue)
  UniqueString(aValue);
  // Here PChar(gValue) <> PChar(aValue)
  aValue := aValue + '2';
  // Here PChar(gValue) <> PChar(aValue)
end;

答案 2 :(得分:9)

在Delphi中通过引用传递显式添加var关键字:

procedure myFunc(var mytext:String);

这意味着myFunc可以修改字符串的内容并让调用者看到更改。