【问题标题】:Why do these two Delphi code blocks behave different?为什么这两个 Delphi 代码块的行为不同?
【发布时间】:2019-02-23 23:10:07
【问题描述】:

第一个代码块中注释的两个部分看似相同,但产生不同的结果(由于其他地方的问题)。我不明白它们有何不同。我唯一改变的是注释掉第一部分(for循环)或第二部分(赋值行)并得到不同的结果。

var
  Amount: Integer;
  I: Integer;

begin
Amount := 3;

// This produces undesired results (**the for loop**)
for I := 0 to Amount-1 do
  CharDataBool[I] := CharToArray(CharDataText[I]);

// This works as expected (**the assignment lines**)
CharDataBool[0] := CharToArray(CharDataText[0]);
CharDataBool[1] := CharToArray(CharDataText[1]);
CharDataBool[2] := CharToArray(CharDataText[2]);

下面的代码有问题,在某种程度上是问题的根源,但我的问题是关于上面的代码。仅当 CharToArray 函数未分配 false 时才会出现上述问题,如下所示:

function CharToArray(Source: TCharDetails): TCharArray;
var
  X, Y: Integer;
begin
  SetLength(Result, Source.Width, 10);

  for Y := 0 to 9 do
    for X := 0 to Source.Width-1 do
      if Source.S[Y*Source.Width+X] = 'x' then
        Result[X,Y] := true
      else Result[X,Y] := false;     // Adding this solves the problem

end;

没有进入“留下未知的值真的很糟糕”,我只是想了解为什么问题在第一部分代码中出现(for循环)而不是(分配行)?三个赋值行与for循环有何不同?

下面可以复制到默认 VCL 项目中的 Unit1 上

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TCharArray = array of array of boolean;

  TCharDetails = record
    S: String;
    Width: Integer;
  end;

var
  CharDataText: array of TCharDetails;
  CharDataBool: array of TCharArray;

var
  Form1: TForm1;

procedure Init;



implementation

{$R *.dfm}

function CharToArray(Source: TCharDetails): TCharArray;
var
  X, Y: Integer;
begin
  SetLength(Result, Source.Width, 10);

  for Y := 0 to 9 do
    for X := 0 to Source.Width-1 do
      if Source.S[Y*Source.Width+X] = 'x' then
        Result[X,Y] := true;

end;

procedure Init;
var
  Amount: Integer;
  I: Integer;

  X,Y: Integer;
  S1, S2: String;

begin
  Amount := 2;

  SetLength(CharDataText, Amount);
  SetLength(CharDataBool, Amount);

  ChardataText[0].Width := 10;
  ChardataText[0].S :=
//   1234567890
    '          ' +    // 0
    '  x       ' +    // 1
    ' xx       ' +    // 2
    '  x       ' +    // 3
    '  x       ' +    // 4
    '  x       ' +    // 5
    '  x       ' +    // 6
    '  x       ' +    // 7
    '  x       ' +    // 8
    ' xxx      ';

  ChardataText[1].Width := 10;
  ChardataText[1].S :=
//   1234567890
    'x         ' +    // 0
    '   xxxx   ' +    // 1
    '  x    x  ' +    // 2
    '       x  ' +    // 3
    '      x   ' +    // 4
    '     x    ' +    // 5
    '    x     ' +    // 6
    '   x      ' +    // 7
    '  x       ' +    // 8
    '  xxxxxx x';


  for I := 0 to Amount-1 do
    CharDataBool[I] := CharToArray(CharDataText[I]);   
  S1 := '';
  for Y := 0 to 9 do
    for X := 0 to 9 do
      S1 := S1 + CharDataBool[1,X,Y].ToString;

  CharDataBool[0] := CharToArray(CharDataText[0]);
  CharDataBool[1] := CharToArray(CharDataText[1]);    
  S2 := '';
  for Y := 0 to 9 do
    for X := 0 to 9 do
      S2 := S2 + CharDataBool[1,X,Y].ToString;

  // S1 != S2 ??
  ShowMessage(S1 + #13 + S2);      


end;

initialization

  Init;

end.

【问题讨论】:

  • 你可以这样做:Result[X, Y] := Source.S[Y * Source + X] = 'x';。这向您展示了为什么这或多或少等同于第一部分代码中的分配。
  • 当结果未定义时,它是未定义的。这导致函数的结果未定义。我的意思是当结果应该为假时,结果可能是理想的或不理想的,这取决于地址当前持有的内容,它不是确定性的。例如,只需更改调用顺序,您可能会得到不同的 udefined 结果..
  • @Sertac:结果被 SetLength 清零,所以不是未定义的。循环前 Result 的所有值都是 false。
  • 我猜在循环中,一个临时的 Result 变量被重用了。如果您将引用类型分配给某事物,并重新使用它,则内容可能会发生变化。动态数组没有写时复制,因此循环的最后一次迭代会覆盖先前迭代的结果,并且 CharDataBool 的每个元素都引用 same dynarray。这可能也是您必须将 items 设置为 false 的原因:SetLenght 还重用数组。这是不正确的,我无法调试它(没有 [MVCE]),但我想它就像我刚才描述的那样。请同时说明 Delphi 版本。
  • 在函数中首先调用SetLength(Result,0,0);。见Do I need to setLength a dynamic array on initialization?动态数组在循环中复用,必须在函数内部清除。

标签: delphi syntax


【解决方案1】:

函数返回值是托管类型。 Result 不能保证在进入函数时被重新初始化。

在循环示例中,编译器优化了迭代之间的隐式本地 Result 重新初始化。这意味着数组的先前内容仍然存在,必须清除。

作为一般规则,总是在进入时初始化=清除托管函数结果。

在这种情况下,首先在函数中调用SetLength(Result,0,0);


有关示例,请参阅 Do I need to setLength a dynamic array on initialization?

【讨论】:

  • 哦,但是在进入的时候就初始化了,不然就是无效的数组。它只是不是零初始化的。在第二次迭代中,它仍然包含第一个 ASCII 图像的数据,并且 SetLength(Result, Source.Width, 10) 保留数据。这意味着在循环中,数组被重用,但在循环外,它不是。
  • FWIW, Result[X, Y] := Source.S[Y * Source.Width + X + 1] = 'x'; 也应该可以工作,因为这样会完全覆盖之前的内容。
  • result := nil 对我来说似乎更短。
猜你喜欢
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-04
  • 1970-01-01
  • 2015-08-31
  • 2012-09-21
  • 2015-10-27
相关资源
最近更新 更多