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

vol*_*vox 16 language-agnostic for-loop loop-counter

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

for( int y = 4; y < 12; y++ ) {
    // blah
    if( var < othervar ) {
        y++;
    }
    // blah
}
Run Code Online (Sandbox Code Playgroud)

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

Gho*_*ica 31

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

你的循环计数器的额外的操作增加了的复杂性,以你的代码,因为你必须牢记这意味着什么,它是如何影响环路的整体行为.正如Arkady所说,它使您的代码更难维护.

简单地说,避免这种模式.当你遵循"清洁代码"原则,特别是单层抽象(SLA)原则时,就没有这样的东西

for(something)
  if (somethingElse)
   y++
Run Code Online (Sandbox Code Playgroud)

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

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

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

 while (y < whatever) {
   ...
   y = determineY(y, who, knows);
 }
Run Code Online (Sandbox Code Playgroud)

那么这个新方法可以成为计算如何更新循环变量的中心位置.

  • 除了这种做法很普遍. (7认同)
  • @mad你能举一个例子来说明编写让读者感到惊讶的代码吗?否则,你的评论似乎只是过度迂腐,声称普遍持有的意见和高度有用的指导方针只是"意见".事实上,一切都是一种观点; 这是GhostCat的,它的名字附在它上面. (5认同)
  • "令你的读者感到惊讶绝不是一个好主意."是一种观点.一个非常常见的,但只是一个意见.遵循清洁代码原则的"事实"总是一个好主意. (4认同)
  • 好吧,有时你**如果满足条件就想跳过迭代.在循环中添加`y ++`可以实现这一点.当然这取决于上下文,可能需要明确评论发生了什么,但我没有看到许多更好的替代方案,无论如何都不需要明确的评论...... (4认同)
  • @CodyGray,这个问题很快就变成了哲学.谁是你的读者以及让他们感到惊讶的是什么?例如,谷歌开发人员很容易根据他们的指导方针(至少是我前段时间看过的那些)给开发人员带来惊喜,他们声称C++中的模板存在争议,如果你发现自己使用模板模板参数,那么你正在做一些事情.错误.我不喜欢在软件开发中降低最低标准的想法. (2认同)

Ser*_*eyA 27

我不同意上面广为人知的答案.在循环体内操纵循环控制变量没有任何问题.例如,以下是清理地图的经典示例:

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

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

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

  • @RobK嗯,我相信这个问题很明显,循环的意图是**不是**来执行一个动作"k次",因为否则你为什么要从'y = 4`开始?要与读者联系?循环的目的是迭代一系列(大部分)连续数字,并为每个值做一些事情.有时你必须跳过一个值(并添加一个`y ++`会完美地做到这一点). (2认同)
  • @Chimera那是不可能的.这个想法是即使例如`$`是一个特殊的符号,`\ $`也不是特别的.`process_symbol`一次只能得到一个字符,所以看不到`$`前面有```````. (2认同)
  • @ LS97,"它"的可见性将是一个原因. (2认同)

小智 10

请改用while循环.

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

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

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

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


Tem*_*Rex 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 聪明的头脑认为相似:)但是`end`迭代器没有失效,所以你不需要在每次迭代时调用它. (2认同)