【问题标题】:Are there good practices if any avoiding out of bounds index error when looping TStringList items?如果在循环 TStringList 项时避免出现越界索引错误,是否有好的做法?
【发布时间】:2023-07-20 17:34:01
【问题描述】:

:)

首先,我的代码

procedure TForm1.Button3Click(Sender: TObject);
var tempId,i:integer;
begin
tempId:=strtoint(edit5.Text);
plik:=TStringList.Create;
plik.LoadFromFile('.\klienci\'+linia_klient[id+1]+'.txt');
if (plik.Count=1) then
  begin
  label6.Caption:='then';
    if (tempId=StrToInt(plik[0])) then
      begin
      Label6.Caption:='Zwrócono';
      plik.Delete(0);
    end
  end
else
for i:=0 to plik.Count-2 do
  begin
    if (tempId=StrToInt(plik[i])) then
    begin
      Label6.Caption:='Zwrócono';
      plik.Delete(i);
    end;
  end;
plik.SaveToFile('.\klienci\'+linia_klient[id+1]+'.txt');
plik.Free;
end;
  • for i:=0 to plik.Count-2 do 我可以删除任何元素但不能 最后。
  • for i:=0 to plik.Count-1 do 我可以删除任何元素 但从头到尾。因为否则列表索引超出范围

怎么了?如何安全地从 TStringList 中搜索和删除元素?

【问题讨论】:

  • 事实上这是重复的,有很多这样的问题。

标签: delphi pascal tstringlist


【解决方案1】:

当从你想使用downto循环的列表中删除intems时,即

for i := plik.Count-1 downto 0 do
  begin
    if (tempId=StrToInt(plik[i])) then
    begin
      Label6.Caption:='Zwrócono';
      plik.Delete(i);
    end;
  end;

这可确保如果您删除项目,循环索引在您从列表末尾向列表开头移动时保持有效。

【讨论】:

    【解决方案2】:

    这是一个经典问题。 for 循环在循环开始时对循环边界进行一次评估,所以你跑到最后,这解释了你的索引越界错误。

    但即使for 循环每次都像while 那样评估循环边界,但这也无济于事。当您删除一个元素时,会将Count 减1,并将剩余元素在列表中下移一个。因此,您更改了所有仍需处理的元素的索引。

    标准技巧是循环列表:

    for i := List.Count-1 downto 0 do
      if DeleteThisItem(i) then
        List.Delete(i);
    

    当你这样写时,对Delete 的调用会影响已经处理过的元素的索引。

    【讨论】:

    • 但 ain 是第一个,但感谢他的回答 :)
    • @Dudi 根据您的判断,您应该接受最佳答案,而不是第一个。我不是说你没有这样做,只有你可以决定。但请记住,产生更好的答案需要更多时间。如果有人提出了一个精心制作的综合答案,比你现在的答案要好得多,你应该接受那个答案。例如,P.A. 的回答比其他人更全面。
    【解决方案3】:
    For I := stringlist.count-1 downto 0 do
    

    现在您可以删除所有项目而不会出现任何错误

    【讨论】:

    • 该死的,我一直在寻找那个解决方案:)...等等。我什至不知道你可以在 delphi 中减少 var。
    • 别着急,你会学到很多关于Delphi的东西。继续学习
    【解决方案4】:

    在像for i:=1 to count 这样的升序循环中,您无法删除正在迭代的列表中的项目。

    根据您想要实现的整体逻辑,有几种解决方案。

    1. 您可以将 for 循环更改为重新评估 countwhile 循环,并且不要在删除迭代时增加索引

    2. 你可以反转循环,有点for i:=count downto 1

    3. 代替delete,您可以创建一个临时列表,只复制您想要保留的项目,然后重新复制回来。

    【讨论】:

    • 您为什么要在我更正答案中的索引错误的地方恢复我的编辑?
    • 我关于如何构建循环的示例是正确的,我没有展示如何使用索引items[i-1]。事实上,我总是更喜欢从 1 开始计数的帕斯卡利什,而不是基于 0 的愚蠢计数。
    • 索引是从零开始的,没有讨论。计数不是。
    • 这肯定会导致笨拙的代码。有时你索引 i-1 有时不索引。例如,当您执行 IndexOf 并存储结果时,您是否将其加 1?对我来说,这听起来像是一种痛苦的秘诀。
    • 应该推广好的做法!
    【解决方案5】:

    正如其他人所说,使用downto 循环通常是最佳选择。当然,它确实改变了循环的语义,因此它向后运行而不是向前运行。如果要继续向前循环,则必须改用while 循环,例如:

    I := 0;
    while I < plik.Count do 
    begin 
      if (tempId = StrToInt(plik[I])) then 
      begin 
        ...
        plik.Delete(I); 
      end else
        Inc(I); 
    end; 
    

    或者:

    var
      CurIdx, Cnt: Integer;
    
    CurIdx := 0;
    Cnt := plik.Count;
    for I := 0 to Cnt-1 do 
    begin 
      if (tempId = StrToInt(plik[CurIdx])) then 
      begin 
        ...
        plik.Delete(CurIdx); 
      end else
        Inc(CurIdx); 
    end; 
    

    【讨论】: