如果x>是使用“for i:= x to y”明确定义的行为Y'

时间:2016-12-09 15:53:54

标签: delphi for-loop

直观的答案是永远不会输入循环。在我能提出的所有测试中似乎都是这种情况。我仍然很焦虑,并且在进入循环之前总是测试它。

这有必要吗?

2 个答案:

答案 0 :(得分:7)

不,没有必要。

The documentation明确指出:

  

for counter:= initialValue to finalValue 执行声明

     

或:

     

for counter:= initialValue downto finalValue 执行声明

     

...

     

如果 initialValue 等于 finalValue 语句只执行一次。如果 initialValue for ... to 语句中大于 finalValue ,或小于 finalValue < strong> for ... downto 语句,然后语句永远不会被执行。

不需要焦虑。

如果我们想进一步研究会发生什么,请举几个例子。先考虑一下:

program Project1;
{$APPTYPE CONSOLE}
var
  i, j, k : integer;
begin
  j := 2;
  k := 1;
  for i := j to k do WriteLn(i);
end.

这会产生编译器提示:

  

[dcc32提示] Project1.dpr(6):H2135 FOR或WHILE循环执行零次 - 删除

因此,编译器将简单地抛弃一个循环,其中常量不会产生循环迭代。即使关闭了优化,它也会这样做 - 根本不会为循环生成任何代码。

现在让我们更聪明一点:

Project1.dpr.8: for i := j to k do WriteLn(i);
004060E8 A1A4AB4000       mov eax,[$0040aba4]   {$0040aba4 -> j = 2}
004060ED 8B15A8AB4000     mov edx,[$0040aba8]   {$0040aba8 -> k = 1}
004060F3 2BD0             sub edx,eax           {edx = k - j = -1} 
004060F5 7C2E             jl $00406125          {was k-j < 0? if yes, jmp to end.}
004060F7 42               inc edx               {set up loop}
004060F8 8955EC           mov [ebp-$14],edx
004060FB A3A0AB4000       mov [$0040aba0],eax
00406100 A118784000       mov eax,[$00407818]       {actual looped section}
00406105 8B15A0AB4000     mov edx,[$0040aba0]
0040610B E8E8D6FFFF       call @Write0Long
00406110 E8C3D9FFFF       call @WriteLn
00406115 E8EECCFFFF       call @_IOTest
0040611A FF05A0AB4000     inc dword ptr [$0040aba0] {update loop var}
00406120 FF4DEC           dec dword ptr [ebp-$14]
00406123 75DB             jnz $00406100             {loop ^ if not complete}
Project1.dpr.9: end.
00406125 E88EE1FFFF       call @Halt0

这实际上编译了循环。输出如下:

sample_list = ['abc', 'def', 'ghi', 'hello']
for ch in sample_list:
    if ch == 'hello':
        print ch

因此,循环首先要做的是检查它是否需要执行。如果初始值大于最终值(对于 for..to 循环),则它会完全跳过它。它甚至不会浪费周期来初始化循环计数器。

答案 1 :(得分:0)

在某些边缘情况下,您可能会惊讶地发现代码 意外地 进入循环。还有一些情况,你可能想要预先检查是否要调用循环。但在我了解这些细节之前,我想尝试一下 预先检查您的循环if条件的重要性。

每一行代码,无论多么容易理解引起注意。阅读更多,更多确认是正确的。因此,如果它不重要,或者它在技术上是多余的:最好省略。

for循环在概念上翻译如下:

  1. 将循环索引初始化为起始值。
  2. 如果迭代约束有效(例如,在正向循环的情况下Index&lt; = EndValue):
    • 执行迭代(循环块/语句中的代码)
    • 执行循环控制操作(增量循环索引)
    • 重复2次
  3. 否则在循环后继续第一条指令。
  4. 检查步骤2的方式,在循环完全冗余之前产生额外的if条件。

    因此,如果您(或其他开发人员)稍后维护具有冗余if条件的代码,他们会留下疑惑:

    • 这条线是否正确?
    • 似乎多余;是否有一个特殊条件,它试图处理?
    • 如果它目前没有用处,或许是为了防止在不同条件下调用循环?

    在简单的情况下,冗余的代码行会造成一些混乱。在更复杂的情况下,它们可能导致开发出不相关代码的全新部分;试图满足传统冗余代码隐含的不相关场景。

      

    推荐:尽可能地删除冗余代码。 包括冗余预检查&#34;如果循环执行完毕&#34;。
      标记冗余代码的最重要的好处是:正确 引起注意特殊情况,只要需要特殊处理 < /强>

    有两个潜在的陷阱,第一个是更危险的陷阱,因为它处理隐式类型转换。因此,检测可能并不容易。以下代码在rextester上使用fpc进行了测试,但我在过去的Delphi 2007/2009上验证了同样的问题。

    //fpc 2.6.2
    
    program UnexpectedForIteration;
    {$MODE DELPHI}
    { Ensure range-checking is off. If it's on, a run-time error
      prevents potentially bad side-effects of invalid iterations.}
    {$R-,H+,W+}
    
    var
      IntStart, IntEnd, IntIndex: Integer;
      UIntStart, UIntEnd, UIntIndex: Cardinal;
      IterCount: Integer;
    begin
      Writeln('Case 1');
      IntStart := High(Integer) - 1;
      IntEnd := -IntStart;
      UIntStart := Cardinal(IntStart);
      UIntEnd := Cardinal(IntEnd);
      {This gives a clue why the problem occurs.}
      Writeln('From: ', IntStart, ' To: ', IntEnd);
      Writeln('From: ', UIntStart, ' To: ', UIntEnd, ' (unsigned)');
    
      Writeln('Loop 1');
      IterCount := 0;
      for IntIndex := IntStart to IntEnd do Inc(IterCount);
      Writeln(IterCount);
    
      Writeln('Loop 2');
      IterCount := 0;
      { The loop index variable is a different type to the initial &
        final values. So implicit conversion takes place and:
        IntEnd **as** unsigned is unexpectedly bigger than IntStart }
      for UIntIndex := IntStart to IntEnd do Inc(IterCount);
      Writeln(IterCount, ' {Houston we have a problem}');
    
      Writeln();
      Writeln('Case 2');
      UIntStart := High(Cardinal) - 2;
      UIntEnd := 2;
      IntStart := Integer(UIntStart);
      IntEnd := Integer(UIntEnd);
      {This gives a clue why the problem occurs.}
      Writeln('From: ', UIntStart, ' To: ', UIntEnd);
      Writeln('From: ', IntStart, ' To: ', IntEnd, ' (signed)');
    
      Writeln('Loop 3');
      IterCount := 0;
      for UIntIndex := UIntStart to UIntEnd do Inc(IterCount);
      Writeln(IterCount);
    
      Writeln('Loop 4');
      IterCount := 0;
      { The loop index variable is a different type to the initial &
        final values. So implicit conversion takes place and:
        UIntStart **as** signed is unexpectedly less than UIntEnd }
      for IntIndex := UIntStart to UIntEnd do Inc(IterCount);
      Writeln(IterCount, ' {Houston we have a problem}');
    end.
    

    输出如下:

    Case 1
    From: 2147483646 To: -2147483646
    From: 2147483646 To: 2147483650 (unsigned)
    Loop 1
    0
    Loop 2
    5 {Houston we have a problem}
    
    Case 2
    From: 4294967293 To: 2
    From: -3 To: 2 (signed)
    Loop 3
    0
    Loop 4
    6 {Houston we have a problem}
    
      

    在许多情况下,通过确保loopIndexinitialValuefinalValue使用相同的类型来解决问题。因为这意味着不会成为隐式类型转换,并且循环将可靠地迭代,因为 initialValue finalValue 会建议。
      如果编译器在 for 循环中为隐式类型转换发出适当的警告会更容易。不幸的是,fpc没有;我不记得Delphi 2007/2009是否这样做;并且不知道最近的版本是否有。

         

    然而,首选的方法是支持容器迭代语法(推动对枚举器进行正确的迭代的责任)。例如:for <element> in <container> do ...;。如果枚举器的方法正确实现,这不应该迭代空容器。

         

    我唯一一次说预检是值得考虑的:

         
        
    • for in由于某种原因不可行时
    •   
    • 并且循环索引需要从零开始
    •   
    • 并支持大型无符号整数(高(整​​数)&lt; index&lt;高(红衣主教))
    •   
    • 因为这为可靠的哨兵留下的空间少于所有可能的初始值。
    •   
    • 即使在这种情况下,也请考虑使用Int64循环索引而不是if (initialValue <= finalValue) then for ...
    •   

    第二个陷阱涉及我在任何情况下都认为是设计缺陷。因此,可以通过非常了解这种设计考虑来完全避免这个问题。它在代码中演示如下:

    if Assigned(AnObject) then
      for LIndex := 0 to AnObject.Count - 1 do ...;
    

    在这种情况下,由于可疑设计,if条件实际上可能是必要的。当然,如果尚未创建AnObject,则您不希望访问其Count属性/方法。但设计中可疑的方面是你不确定AnObject是否存在。是的,您可能采用了惰性初始化模式。但它没有改变这样一个事实:在上面的代码中,没有办法区分:&#34;零迭代&#34;因为AnObject不存在或因为AnObject.Count = 0

    我想指出,当代码有许多冗余if Assigned(AnObject) then(或类似)行时,会导致我在第1部分中描述的问题之一。本地代码可以满足2种可能性。通过扩展,客户端代码也迎合了两种可能性。通过归纳,这个问题最终会在整个代码库中泄漏。

      

    解决方案首先要限制AnObject存在不确定的情况。

         
        
    • 确保创建Count = 0的空对象更容易(通常只会影响代码中的少量位置)。
    •   
    • 处理对象可能尚不存在的大量场所的连锁反应的工作要多得多;产生2种可能的状态和代码路径。
    •   
         

    如果需要进行延迟初始化,请尽量确保存在可选的代码表面尽可能小。