对可变数组元素(可变参数)的更改在过程外部不可见

Changes to variable array elements (variable parameter) not visible outside of procedure

我正在尝试使用可变数组实现采用可变数量参数的过程。 问题是对传递变量的更改在过程外部不可见。

procedure arrChange(var arr: array of String);
var
  i: Integer;
begin
  for i := 0 to Length(arr) do
    arr[0] := 'changed';
end;

procedure strChange(var str: String);
begin
  str := 'changed';
end;

procedure strChangeNotVariable(str: String);
begin
  str := 'changed again';
end;

var a, b: String;
begin
  a := 'a';  
  b := 'b';

  print(a); // prints a - expected   
  print(b); // prints b - expected  

  arrChange ([a, b]);

  print(a); // prints a  - why?
  print(b); // prints b  - why? 

  strChange(a);     
  strChange(b);

  print(a); // prints changed - expected 
  print(b); // prints changed - expected

  strChangeNotVariable(a);
  strChangeNotVariable(b); 

  print(a); // prints changed - expected 
  print(b); // prints changed - expected
end.

我能解释一下为什么 arrChange 之外的更改不可见吗and/or 可能的解决方法?

我猜你应该知道开放数组参数:有一种叫做开放数组构造函数的东西。如果您不将真实的现有数组传递给这样一个 开放数组参数 ,而是将一堆值括在方括号中,就像您所做的那样,就会发生这种情况。

对于这样的开放数组构造函数,编译器编写代码在堆栈上创建一个临时数组(它保留堆栈space,然后将值放在那里),然后将指向该临时数组的指针及其大小减一传递给过程。此类临时数组仅在过程运行时有效,即仅在过程内部。它们在程序退出时消失。这就是为什么它们不能作为 var(参考)参数传递的原因。有些版本可能允许这样做,但这只会更改堆栈中的副本,而不是原件。

如果你想让你的把戏奏效,你必须传递一个真实存在的数组。这可能是这样的:

var
  d: array of string;
...
  SetLength(d, 2);
  d[0] := a;
  d[1] := b;
  arrChange(d);

现在,如果您在程序后打印 d[0]d[1],它们应该显示 'changed'。但是 ab 应该还是完整的,没有改变,因为 d 只包含副本。

仅当您传递 array of PString 时,它才会直接影响 ab。但是这样的话还是没必要把开数组做成引用参数。我试过这个:

procedure arrChange(const arr: array of PString);
var
  i: Integer;
begin
  for i := 0 to High(arr) do
    // Not modifying arr[i] itself, only to what it points!
    arr[i]^ := 'changed'; // FWIW, you had arr[0] instead of arr[i] ;-)
end;

arrChange([@a, @b]);
print(a);
print(b);

成功并产生了

changed
changed

procedure print(const s: string);
begin
  Writeln(s);
end;

更多信息:Open array parameters and array of const


FWIW,似乎编译器甚至不更新字符串的引用计数。它将指针的原始副本放在堆栈上,就像 const 字符串 .

一样