16

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

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

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

4

7 回答 7

32

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

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

简单地说,避免这种模式。当你遵循“干净的代码”原则,尤其是单层抽象 (SLA)原则时,就不会有这样的事情了

for(something)
  if (somethingElse)
   y++

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

但除此之外,可能会出现像您的示例那样的“某些东西”;但是对于那些情况-为什么不使用while循环呢?

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

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

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

于 2016-12-29T14:00:13.407 回答
27

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

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

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

for (size_t i = 0; i < s_len; ++i) {
    if (s[i] == '\\') {
       ++i;
       continue;
    }
    process_symbol(s[i]);
}
于 2016-12-29T15:15:26.730 回答
10

请改用 while 循环。

虽然您可以使用 for 循环执行此操作,但您不应该这样做。请记住,程序与任何其他交流方式一样,并且必须考虑到您的听众。对于一个程序,受众包括编译器和下一个对代码进行维护的人(可能你在大约 6 个月后)。

对于编译器来说,代码是非常直接的——设置一个索引变量,运行循环体,执行增量,然后检查条件以查看是否再次循环。编译器不在乎您是否使用循环索引。

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

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

于 2016-12-29T16:12:19.553 回答
7

如果您在循环内递增,请确保对其进行注释。问答中给出了一个规范示例(基于 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()和循环内的增量都令人惊讶,因此需要记录它们。注意:这里的循环提升毕竟是可能的,所以为了代码清晰可能应该这样做。

于 2016-12-29T15:15:18.133 回答
3

对于它的价值,以下是 C++ 核心指南关于该主题的内容:

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

ES.86:避免修改原始 for 循环体内的循环控制变量</p>

原因前面的循环控制应该能够正确推理循环内发生的事情。在迭代表达式和循环体内修改循环计数器是一个常年出现意外和错误的来源。

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

于 2016-12-29T17:38:23.973 回答
3

所以在一些混乱之后,即关闭,重新打开,问题主体更新,标题更新,我认为问题终于清楚了。而且也不再基于意见。

据我了解,问题是:

当我查看其他人编写的代码时,我是否应该期望在循环体中看到“循环条件变量”被更改?

这个问题的答案很明确:

是的

当你使用其他代码时——无论你是否进行审查、修复错误、添加新功能——你都会预料到最坏的情况。

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

不要对代码是否符合任何良好实践做出任何假设。

于 2016-12-29T17:44:12.637 回答
2

写成一个while循环真的更好

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

您有时可以从主体中分离出循环逻辑

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

当且仅当您很少“跳过”一个项目时,我才允许使用 for() 方法,例如在带引号的字符串中转义。

于 2016-12-29T16:04:12.707 回答