20

在复习的时候,有时会遇到这样的循环:

i = begin
while ( i != end ) {    
   // ... do stuff
   if ( i == end-1 (the one-but-last element) ) {
      ... do other stuff
   }
   increment i
}

然后我问一个问题:你会写这个吗?

i = begin
mid = ( end - begin ) / 2 // (the middle element)
while ( i != end ) {    
   // ... do stuff
   if ( i > mid ) {
      ... do other stuff
   }
   increment i
}

在我看来,这超出了编写循环的意图:你循环是因为每个元素都有一些共同的事情要做。使用此构造,您可以对某些元素执行不同的操作。因此,我得出结论,您需要为这些元素创建一个单独的循环:

i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {    
   // ... do stuff
   increment i
}

while ( i != end ) {
   // ... do stuff
   // ... do other stuff
   increment i
}

现在我什至在 SO 上看到了一个关于如何以if一种好的方式编写 - 子句的问题......我很伤心:这里有些不对劲。

我错了吗?如果是这样,那么在编码时将循环体与特殊情况混在一起有什么好处?

4

13 回答 13

23

我认为这个问题不应该由一个原则来回答(例如“在一个循环中,平等对待每个元素”)。相反,您可以查看两个因素来评估实现是好还是坏:

  1. 运行时有效性 - 编译后的代码运行速度快,还是以不同的方式运行会更快?
  2. 代码可维护性——(对于其他开发人员)是否容易理解这里发生的事情?

如果通过在一个循环中完成所有事情来更快并且代码更具可读性,那么就这样做。如果速度较慢且可读性较差,请以另一种方式进行。

如果它更快且可读性较差,或者速度较慢但可读性更高,请找出哪些因素在您的特定情况下更重要,然后决定如何循环(或不循环)。

于 2008-10-01T08:58:37.553 回答
11

我知道当人们试图将数组的元素加入逗号分隔的字符串时,我已经看到了这一点:

for(i=0;i<elements.size;i++) {
   if (i>0) {
     string += ','
   }
   string += elements[i]
}

你要么在那里有那个 if 子句,要么你必须在最后再次复制字符串 += 行。

在这种情况下,明显的解决方案是

string = elements.join(',')

但是 join 方法在内部执行相同的循环。并不总是有一种方法可以做你想做的事。

于 2008-10-01T08:11:28.120 回答
7

@xtofl,

我同意你的担心。

百万次我遇到了类似的问题。

开发人员都为第一个或最后一个元素添加了特殊处理。

在大多数情况下,值得从startIdx + 1endIdx - 1元素循环,甚至将一个长循环分成多个较短的循环。

在极少数情况下,不可能拆分循环。

在我看来,不常见的事情应该尽可能在循环之外处理。

于 2008-10-01T08:15:34.037 回答
6

我开始意识到,当我将特殊情况放入 for 循环中时,我通常太聪明了,对自己不利。

于 2008-10-01T08:12:32.063 回答
6

在您发布的最后一个片段中,您正在重复 // .... do stuff 的代码。

当您对一组不同的索引进行完全不同的操作时,保持 2 个循环是有意义的。

i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {    
   // ... do stuff
   increment i
}

while ( i != end ) {
   // ... do other stuff
   increment i
}

情况并非如此,您仍然希望保留一个循环。然而事实仍然是你仍然保存 ( end - begin ) / 2 比较次数。因此,归结为您是希望代码看起来整洁还是希望节省一些 CPU 周期。电话是你的。

于 2008-10-01T09:03:53.797 回答
5

我认为你已经完全确定了。大多数人都陷入了在循环中包含条件分支的陷阱,而他们可以在外部执行它们:这只是更快

例如:

if(items == null)
    return null;

StringBuilder result = new StringBuilder();
if(items.Length != 0)
{
    result.Append(items[0]); // Special case outside loop.
    for(int i = 1; i < items.Length; i++) // Note: we start at element one.
    {
        result.Append(";");
        result.Append(items[i]);
    }
}
return result.ToString();

而你描述的中间情况简直令人讨厌。想象一下,如果该代码增长并且需要重构为不同的方法。

除非您正在解析 XML <grin> 循环,否则应尽可能保持简单和简洁。

于 2008-10-01T10:01:08.277 回答
3

我认为你对循环意味着平等地处理所有元素是正确的。不幸的是,有时会有一些特殊情况,这些应该通过 if 语句在循环构造中处理。

如果有很多特殊情况,您可能应该考虑想出一些方法来处理不同结构中的两组不同元素。

于 2008-10-01T08:12:40.783 回答
3

我更喜欢简单地从循环中排除元素并在循环外进行单独处理

例如:让我们考虑 EOF 的情况

i = begin
while ( i != end -1 ) {    
   // ... do stuff for element from begn to second last element
   increment i
}

if(given_array(end -1) != ''){
   // do stuff for the EOF element in the array
}
于 2013-04-06T05:55:55.957 回答
2

当然,可以拉出的循环中的特殊外壳是愚蠢的。我也不会复制 do_stuff ;我要么把它放在一个函数或一个宏中,所以我不会复制粘贴代码。

于 2008-10-01T08:08:38.407 回答
2

哪一个表现更好?

如果项目的数量非常大,那么我总是会循环一次,特别是如果您要对每个项目执行一些操作。评估条件的成本可能低于循环两次。

哎呀,当然你不是循环两次......在这种情况下,两个循环是更可取的。但是,我认为首要考虑因素应该是性能。如果您可以通过对循环边界的简单操作(一次)来划分工作,则无需在循环中产生条件(N 次)。

于 2008-10-01T08:10:40.523 回答
2

我讨厌看到的另一件事是for-case 模式

for (i=0; i<5; i++)
{
  switch(i)
  {
    case 0:
      // something
      break;
    case 1:
      // something else
      break;
    // etc...
  }
}

我已经在真实代码中看到了这一点。

于 2008-10-01T08:14:43.883 回答
1

如果只执行一次,则应在循环外执行特殊情况。

但是,由于作用域,可能有一个索引或一些其他变量更容易保留在循环中。将数据结构上的所有操作放在循环控制结构中可能还有一个上下文原因,尽管我认为这本身就是一个弱论点。

于 2008-10-01T09:06:22.027 回答
1

它只是根据需要和方便使用它。因此,没有提到平等对待元素,而且将语言提供的功能组合在一起当然没有害处。

于 2008-10-01T09:10:25.387 回答