【问题标题】:DELPHI class with dynamic array has problem with SetLength()具有动态数组的 DELPHI 类与 SetLength() 存在问题
【发布时间】:2011-07-26 16:08:06
【问题描述】:

我需要创建一个包含 记录对象数组,但尝试使用 SetLength 会引发访问冲突错误。

考虑以下带有水果的树对象示例。

type
TFruit = record
  color: string;
  weight: double;
end;

type
  TObjectTree = class

  Public
  Fruits: array of TFruit;

  constructor Create;
  procedure AddFruit;
end;

在实现中,当尝试调整 Fruit 对象数组的大小或初始化为 nil 时会产生问题。

constructor TObjectTree.Create;
begin
    inherited Create;
    Fruits:=nil;              //Raises an error
end;


procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
begin
    SetLength(Fruits, Length(Fruits)+1);  //Raises an error (when I comment Fruits:=nil; in the constructor)

    Fruits[Length(Fruits)].color:=FruitColor;      
    Fruits[Length(Fruits)].weight:=FruitWeight;
end;

如何在类中使用动态数组?

【问题讨论】:

    标签: arrays delphi class dynamic


    【解决方案1】:

    替换

    Fruits[Length(Fruits)].color:=FruitColor;      
    Fruits[Length(Fruits)].weight:=FruitWeight;
    

    Fruits[High(Fruits)].color:=FruitColor;
    Fruits[High(Fruits)].weight:=FruitWeight;
    

    然后就可以了。

    【讨论】:

    • 解释是动态数组是基于 0 的,并且运行到 Length - 1。
    • 对,这也是例子中的一个bug。
    【解决方案2】:

    告诉我你忽略了创建TObjectTree 的实例。你已经声明了一个TObjectTree 变量,但是你要么没有调用TObjectTree.Create,要么你直接在你声明的变量上调用了它,而不是为该变量分配一个新值:

    var
      Tree: TObjectTree;
    begin
      // This is wrong.
      Tree.Create;
    
      // This is right.
      Tree := TObjectTree.Create;
    

    如果没有正确实例化TObjectTree,就没有有效的内存来支持您尝试使用的Fruits 字段,因此为其赋值会出错。

    【讨论】:

    • +1 这比我的内存损坏解释更有可能解释正在发生的事情
    • 完全正确。这导致了内存问题。现在可以实例化一个 TreeObject 并将 Fruits 数组设置为 nil(以便稍后通过调用要实现的类方法来清除添加的水果)。
    • 在构造函数中将 Fruits 设置为 nil 是没有意义的。已经为零了。
    【解决方案3】:

    作为对 iamjoosy 和 Rob Kennedy 答案的补充,我将这样编写代码:

    procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
    var
      NewCount: Integer;
    begin
      NewCount := Length(Fruits)+1;
      SetLength(Fruits, NewCount);
      Fruits[NewCount-1].color := FruitColor;      
      Fruits[NewCount-1].weight := FruitWeight;
    end;
    

    在我看来,更清楚的是,只调用一次Length()

    您不需要在构造函数中分配Fruits := nil,因为这是自动发生的。实例化对象时,所有字段都初始化为零。也就是说,Fruits := nil 不应引发错误。如果是这样,则可能是由于越界数组访问导致内存损坏。

    还有一点需要说明的是,启用范围检查会导致一个可以解释问题的信息错误。这比依赖访问冲突更有帮助。我不建议高度检查范围。

    最后,SetLength(..., Length(...)+1) 模式通常会导致非常低效的内存使用,并可能导致大型列表的性能问题。如果您有 Delphi 2009+,我建议您改用 TList<TFruit>

    【讨论】:

    • 我通常对这样的代码使用一个函数,返回一个整数,它是新创建项目的索引,然后使用result := length(Fruits); SetLength(Fruits,result+1); Fruits[result].color := FruitColor; 这对我来说确实比那些 +1/-1 更有意义,并且有时可以很方便地为调用者检索新创建的索引,而不会降低速度。只是我的 2 美分。
    • @Arnaud 同意,虽然我只是使用 TList<TFruit>,这当然可以做到这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-06
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    相关资源
    最近更新 更多