几乎无限的“ for”循环


82

我目前正在调试一些代码,并且遇到了这一行:

for (std::size_t j = M; j <= M; --j)

(由正在度假的老板写的。)

在我看来真的很奇怪。

它有什么作用?在我看来,这就像一个无限循环。


31
size_t是无符号的,因此在尝试弃用零并结束循环时,可以保证回绕到最大值。仍然是可怕的代码。
BoBTFish

4
@Bathsheba如果M恰好是的最大值,该size_t怎么办?您仍然认为它很聪明吗?
Thorsten Dittmar

8
@Barmar不会出现太多不是永远不会出现,因此概率>0。这意味着当有多种方法可以简化该解决方案时,它是愚蠢的。
Thorsten Dittmar

38
我不明白投票的结果。这个问题很清楚-我看不到如何改进。对于新用户,这个问题实际上很有趣
Bathsheba

17
#define TRUE FALSE去度假
Leo Heinsaar,2013年

Answers:


68

std::size_t由C ++标准保证是一种unsigned类型。并且,如果您将unsigned类型从0减1 ,则标准将确保这样做的结果是该类型的最大值。

该环绕值始终大于或等于M1,因此循环终止。

因此j <= M,将其应用于unsigned类型时是一种方便的方式,即“将循环运行为零然后停止”。

存在一些替代方法,例如j比您想要的运行多一个,甚至使用slide运算符 for (std::size_t j = M + 1; j --> 0; ){,尽管需要更多的键入操作,但可以说更清晰一些。我猜一个缺点(除了它在第一次检查时产生的令人困惑的效果)是,它不能很好地移植到没有无符号类型的语言,例如Java。

还要注意,老板选择的方案从unsigned集合中“借”了一个可能的值:在这种情况下,M设置为std::numeric_limits<std::size_t>::max()不会具有正确的行为。实际上,在这种情况下,循环是无限的。(这就是您正在观察的内容吗?)您应该在代码中插入对此效果的注释,甚至可能在特定条件下进行声明。


1M不存在为准std::numeric_limits<std::size_t>::max()


7
我责怪天气。
Hatted Rooster

3
如果M是std::numeric_limits<std::size_t>::max()那么M + 1将为零,而for (std::size_t j = M + 1; j --> 0; )循环不会循环可言。
皮特·柯坎

51
不要称其为“滑动运算符”。在C ++中没有这样的运算符,只是一个看起来很巧妙的混淆。

26
@DimitarMirchev:因为它不是运算符。它是两个运算符,间距为奇数。如果您坚持要询问受访者无关的问题,可以将其称为习惯用法(但理想情况下,请问他们如何实现功能而不是询问“聪明”的语法)。

1
假设size_t是64位,则在边缘情况下观察错误行为将花费数百年的时间。(除非优化程序可以摆脱循环。)
Carsten S'S

27

你的老板可能是试图做的是从倒数M到零包容性,表演上的每个数字有所行动。

不幸的是,在某些情况下确实会给您无限循环,其中一个M就是size_t您可以拥有的最大值。而且,尽管很好地定义了将无符号值从零开始递减时将执行的操作,但我坚持认为代码本身就是草率思维的示例,尤其是因为存在一个完美可行的解决方案,而没有老板尝试的缺点。

这个更安全的变体(我认为,在仍保持严格的范围限制的同时,更具可读性)将是:

{
    std::size_t j = M;
    do {
        doSomethingWith(j);
    } while (j-- != 0);
}

通过示例,请参见以下代码:

#include <iostream>
#include <cstdint>
#include <climits>
int main (void) {
    uint32_t quant = 0;
    unsigned short us = USHRT_MAX;
    std::cout << "Starting at " << us;
    do {
        quant++;
    } while (us-- != 0);
    std::cout << ", we would loop " << quant << " times.\n";
    return 0;
}

与an基本上具有相同的作用unsigned short,您可以看到它处理了每个单个值:

Starting at 65535, we would loop 65536 times.

do..while用您的老板基本上所做的替换上面代码中的循环将导致无限循环。试试看,看看:

for (unsigned int us2 = us; us2 <= us; --us2) {
    quant++;
}

7
现在,边缘情况为0。为什么不for(size_t j = M; j-- != 0; )呢?
LogicStuff

是的,这确实比极端情况更为频繁numeric_limits<size_t>::max()
Bathsheba

7
@Logic等,在我提供的方法中没有极端情况,我添加了代码来演示这一点。在您提出的解决方案中,初始值为M == 0会导致元素0未被处理,因此确实存在边缘情况。使用我的后检查do..while方法可以完全消除边缘情况。如果使用进行尝试M == 1,则会看到它同时执行10。类似地,以max_size_t(无论发生了什么)启动它,它将成功地从该点开始,然后递减到0(包括0)。
paxdiablo '16

有趣的是,这种情况促使人们使用“非结构化代码”,因为它使用了“ exitif{ j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }。如果C语言没有命名循环,则甚至可能需要breakgoto嵌套的a代替。如果“结构化”的意思是“易于对块进行推理”,则它是结构化的(布局有帮助!),如果它的含义是“对通过先决条件和后置条件进行推理也可以进行形式验证”。尽管在这种情况下j--可行,但是当需要更复杂的转换时,可以证明exitif样式是合理的。
PJTraill '16

@PJTraill我无法说出您所说的是结构化的,而您所说的不是结构化的。关于做时的推理很简单。它不会“使用”某个出口,而是以一种非结构化的方式使用“出口”。
philipxy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.