我应该期望看到 `for` 循环中的计数器在它的体内发生变化吗?

Should I expect to see the counter in `for` loop changed inside its body?

我正在阅读其他人的代码,他们分别在循环内递增 for 循环计数器,并包括通常的事后思考。例如:

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

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

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

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

简单地说,避免这种模式。当你遵循"clean code"原则,尤其是single layer of abstraction (SLA)原则时,就没有

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

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

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

换句话说:让您的示例变得复杂和混乱的是代码的两个 不同 部分改变了您的循环计数器。所以另一种方法可能是这样的:

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

这个新方法可以成为确定如何更新循环变量的中心位置。

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

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() 迭代器的非常量性质和循环内的增量都令人惊讶,因此需要记录它们。注意:这里的循环提升毕竟是可能的,所以为了代码清晰起见,可能应该这样做。

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

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]);
}

写成while循环确实更好

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

您有时可以将循环逻辑与正文分开

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

当且仅当您很少 "skip" 一个项目时,我才会允许使用 for() 方法, 就像在带引号的字符串中转义。

改为使用 while 循环。

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

对于编译器来说,代码是很字面的——设置一个索引变量,运行循环体,执行递增,然后检查条件看是否再次循环。编译器不关心你是否使用循环索引。

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

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

关于它的价值,以下是 C++ 核心指南对这个主题的看法:

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

ES.86: Avoid modifying loop control variables inside the body of raw for-loops

Reason The loop control up front should enable correct reasoning about what is happening inside the loop. Modifying loop counters in both the iteration-expression and inside the body of the loop is a perennial source of surprises and bugs.

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

所以经过一些混乱,即关闭、重新打开、问题 body 更新、标题更新,我认为问题终于清楚了。也不再基于意见。

据我了解,问题是:

When I look at code written by others, should I be expecting to see "loop condition variable" being changed in the loop body ?

答案很明确:

当您与其他人一起编写代码时 - 无论您是否进行审查、修复错误、添加新功能 - 您都应该做好最坏的打算。

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

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