我应该期望看到 `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 ?
答案很明确:
是
当您与其他人一起编写代码时 - 无论您是否进行审查、修复错误、添加新功能 - 您都应该做好最坏的打算。
在语言中有效的一切都是可以预期的。
不要对代码是否符合任何良好做法做出任何假设。
我正在阅读其他人的代码,他们分别在循环内递增 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 ?
答案很明确:
是
当您与其他人一起编写代码时 - 无论您是否进行审查、修复错误、添加新功能 - 您都应该做好最坏的打算。
在语言中有效的一切都是可以预期的。
不要对代码是否符合任何良好做法做出任何假设。