我现在正在调试一些代码,而我遇到了这一行:
for (std::size_t j = M; j <= M; --j)
(由我正在度假的老板撰写。)
对我来说,这看起来非常奇怪。
它是干什么的?对我来说,它看起来像一个无限循环。
我现在正在调试一些代码,而我遇到了这一行:
for (std::size_t j = M; j <= M; --j)
(由我正在度假的老板撰写。)
对我来说,这看起来非常奇怪。
它是干什么的?对我来说,它看起来像一个无限循环。
std::size_t
是 C++ 标准保证的一个 unsigned
类型。如果你从 0 减去一个 unsigned
类型,标准保证这样做的结果是该类型的最大值。
这个被包裹的值总是大于等于 M
1,所以循环会终止。
因此,对于 unsigned
类型,j <= M
是一种方便的表示“运行循环到 0 然后停止”的方式。
还有其他选择,比如将 j
运行得更多,甚至使用滑动运算符 for (std::size_t j = M + 1; j --> 0; ){
,虽然这些选择可能更清晰,但需要输入更多内容。不过,我想另外一个缺点(除了在首次检查时产生的困惑效应之外)是它不能很好地移植到没有无符号类型的语言,比如 Java。
还要注意,你的老板选择的方案“借用”了一个可能的值从 unsigned
集合中:在这种情况下,设置为 std::numeric_limits<std::size_t>::max()
的 M
将没有正确的行为。 事实上,在这种情况下,循环是无限的。(你是否观察到了这一点?)你应该在代码中插入一个注释来说明这一点,并可能在特定条件下进行断言。
M
不是 std::numeric_limits<std::size_t>::max()
。std::numeric_limits<std::size_t>::max()
,那么M+1
会变成零,而for (std::size_t j = M + 1; j --> 0; )
循环就不会执行。 - Pete Kirkhamsize_t
是64位,除非优化器可以消除循环,否则需要几百年才能观察到边缘情况下的错误行为。 - Carsten S你的老板大概是想从M倒数到0(包括0),并对每个数字执行一些操作。
不幸的是,存在一种边缘情况,会导致你进入一个无限循环,即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;
}
这个代码基本上使用 unsigned short
做了同样的事情,你可以看到它处理了每一个值:
Starting at 65535, we would loop 65536 times.
将上面代码中的do..while
循环替换为你的老板基本上做的事情会导致无限循环。尝试一下:
for (unsigned int us2 = us; us2 <= us; --us2) {
quant++;
}
0
。为什么不使用 for(size_t j = M; j-- != 0; )
? - LogicStuffnumeric_limits<size_t>::max()
更频繁。 - BathshebaM == 0
,则元素0将不会被处理,因此确实存在边缘情况。使用我的后检查do..while
方法完全消除了边缘情况。如果您尝试将其设置为M == 1
,您会发现它会处理1和0。同样,如果以max_size_t
(无论它是什么)开始,则会成功地从该点开始递减,直到包括零。 - paxdiabloexitif
”,即 { j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }
。如果嵌套的情况变得复杂,甚至可能需要用goto
替换break
,因为C语言没有命名循环。如果“结构化”意味着“容易对块进行推理”,那么它就是结构化的(布局有助于!),并且如果它意味着“容易通过推理关于前置条件和后置条件进行正式验证”。虽然在这种情况下j--
可以工作,但当需要更复杂的转换时,exitif风格可能是合理的。 - PJTraillexitif
关键字:他用荷兰语不满地说:“但这不是结构化的!” 这让我思考人们所谓的“结构化”是什么意思,以及为什么 Dijkstra 等人引入了这个概念。在这种情况下,我建议某些人 - 如我的同事 - 可能会称我的评论中的 C 片段为“非结构化”,尽管它有其优点。我没有将 do…while
与 while…do
进行对比。 - PJTraill
size_t
是无符号的,因此在尝试将零作为循环结束标志时,保证会绕回到其最大值。然而,这段代码仍然很糟糕。 - BoBTFishsize_t
的最大值恰好是M
,你是否仍然认为这很聪明? - Thorsten Dittmar#define TRUE FALSE
and go on holiday.将被翻译为:
将TRUE定义为FALSE,然后去度假。` - Leo Heinsaar