我是否应该看到`for`循环中的计数器在其体内发生了变化?

时间:2016-12-29 13:58:00

标签: for-loop language-agnostic loop-counter

我正在阅读别人的代码,他们分别在循环内增加for循环计数器,以及包括通常的事后想法。例如:

for( int y = 4; y < 12; y++ ) {
    // blah
    if( var < othervar ) {
        y++;
    }
    // blah
}

基于其他人编写和阅读的大部分代码,我应该期待看到这个吗?

7 个答案:

答案 0 :(得分:31)

for 循环中操作循环计数器的做法并不十分普遍。阅读该代码的人会感到惊讶。并且令人惊讶您的读者很少是一个好主意。

对循环计数器的额外操作会给代码增加的复杂性,因为您必须牢记它的含义以及它如何影响循环的整体行为。正如Arkady所说,它使您的代码更难维护。

简单地说,避免使用此模式。当你遵循“清洁代码”原则,特别是single layer of abstraction (SLA)原则时,就没有

这样的东西
for(something)
  if (somethingElse)
   y++

遵循该原则要求您将if块移动到其自己的方法中,使得在该方法中操纵某些外部计数器变得尴尬。

但除此之外,可能会出现像你的例子那样“某事”的情况;但是对于那些情况 - 为什么不在循环中使用呢?

换句话说:使您的示例变得复杂和混乱的事实是,代码的两个不同部分会改变您的循环计数器。所以另一种方法可能如下:

 while (y < whatever) {
   ...
   y = determineY(y, who, knows);
 }

然后,新方法可以成为计算如何更新循环变量的中心位置。

答案 1 :(得分:27)

我不同意上述广受好评的答案。在循环体内操纵循环控制变量没有任何问题。例如,这是清理地图的经典示例:

for (auto it = map.begin(), e = map.end(); it != e; ) {
    if (it->second == 10)
        it = map.erase(it);
    else
        ++it;
}

因为我已经正确地指出了迭代器与数字控制变量不同的事实,所以让我们考虑一个解析字符串的例子。让我们假设字符串由一系列字符组成,其中字符前缀为&#39; \&#39;被认为是特殊的,需要跳过:

for (size_t i = 0; i < s_len; ++i) {
    if (s[i] == '\\') {
       ++i;
       continue;
    }
    process_symbol(s[i]);
}

答案 2 :(得分:10)

改为使用while循环。

虽然可以使用for循环执行此操作,但 不应该。请记住,程序就像任何其他通信一样,必须考虑到您的受众。对于一个程序,观众包括编译器和下一个对代码进行维护的人(可能在大约6个月内)。

对编译器来说,代码非常直观 - 设置索引变量,运行循环体,执行增量,然后检查条件以查看是否再次循环。编译器不关心你是否使用循环索引。

然而,对于某个人来说,for循环具有特定的隐含含义:运行此循环固定次数。如果你使用循环索引,那么这就违反了这个含义。从某种意义上讲它是不诚实的,这很重要,因为下一个阅读代码的人要么花费额外的精力来理解循环,要么就是不能这样做,因此无法理解。

如果你想使用循环索引,请使用while循环。特别是在C / C ++ /相关语言中,for循环和while循环一样强大,所以你永远不会失去任何力量或表现力。任何for循环都可以转换为while循环,反之亦然。但是,下一个阅读它的人将不会依赖于你没有使用循环索引的暗示。使它成为while循环而不是for循环是一种警告,这种循环可能更复杂,在你的情况下,它实际上更复杂。

答案 3 :(得分:7)

如果在循环内增加,请务必对其进行注释。 Q&amp; A How to remove from a map while iterating it?(逐字代码副本)中提供了一个规范示例(基于Scott Meyers Effective C ++项目)

for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
  if (must_delete)
  {
    m.erase(it++);    // or "it = m.erase(it)" since C++11
  }
  else
  {
    ++it;
  }
}

这里,end()迭代器的非常数性质和循环内部的增量都是令人惊讶的,因此需要记录它们。注意:此处的循环提升毕竟是可能的,因此可能应该为代码清晰完成。

答案 4 :(得分:3)

对于它的价值,以下是C ++核心指南对此主题的评论:

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-loop-counter

  

ES.86:避免修改raw体内的循环控制变量   for-loops

     

原因预先的循环控制应该启用正确   推理循环内发生的事情。修改循环   迭代表达式和体内的计数器   循环是一个常年的惊喜和错误来源。

另请注意,在这里讨论std::map的情况的其他答案中,控制变量的增量仍然只在每次迭代中完成一次,在您的示例中,每次迭代可以执行多次

答案 5 :(得分:3)

所以在一些混乱之后,即关闭,重新开启,问题正文更新,标题更新,我认为问题终于明确了。并且不再基于意见。

据我所知,问题是:

  

当我查看其他人编写的代码时,我是否应该期待看到&#34;循环条件变量&#34;在循环体中被改变了吗?

答案很清楚:

当您使用其他代码时 - 无论您是否进行审核,修复错误,添加新功能 - 您都应该期待最坏的情况。

在语言中有效的一切都是可以预期的。

不要对任何良好做法的代码做出任何假设。

答案 6 :(得分:2)

写作while循环真的更好

y = 4;
while(y < 12)
{
   /* body */
   if(condition)
     y++;
   y++;
}

您有时可以将循环逻辑从正文中分离出来

 while(y < 12)
 {
    /* body */
    y += condition ? 2 : 1;
 }

当且仅当你很少&#34;跳过&#34;我会允许for()方法。一个物品, 比如带引号的字符串中的转义。