直观的答案是永远不会输入循环。在我能提出的所有测试中似乎都是这种情况。我仍然很焦虑,并且在进入循环之前总是测试它。
这有必要吗?
答案 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循环在概念上翻译如下:
检查步骤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}
在许多情况下,通过确保
loopIndex
,initialValue
和finalValue
使用相同的类型来解决问题。因为这意味着不会成为隐式类型转换,并且循环将可靠地迭代,因为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种可能的状态和代码路径。
如果需要进行延迟初始化,请尽量确保存在可选的代码表面尽可能小。