对可变数组元素(可变参数)的更改在过程外部不可见
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'
。但是 a
和 b
应该还是完整的,没有改变,因为 d
只包含副本。
仅当您传递 array of PString
时,它才会直接影响 a
和 b
。但是这样的话还是没必要把开数组做成引用参数。我试过这个:
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
字符串 .
一样
我正在尝试使用可变数组实现采用可变数量参数的过程。 问题是对传递变量的更改在过程外部不可见。
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'
。但是 a
和 b
应该还是完整的,没有改变,因为 d
只包含副本。
仅当您传递 array of PString
时,它才会直接影响 a
和 b
。但是这样的话还是没必要把开数组做成引用参数。我试过这个:
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
字符串 .