使用for循环反向迭代无符号整型数值

51

我想让for循环中的迭代变量以逆序迭代到0,它是一个无符号整数unsigned int,但我想不出类似于i > -1这样的比较方法,如果它是一个有符号整数signed int,你可能会这样做。

for (unsigned int i = 10; i <= 10; --i) { ... }

但是这种方法似乎很不明确,因为它依赖于无符号整数的数值溢出大于10。

也许我只是头脑不清,但还有更好的方法可以做到这一点...

免责声明:这只是一个简单的用例,上限10是微不足道的,它可以是任何值,且i必须是无符号整数。


如果i最初等于0怎么办? - Shamim Hafiz - MSFT
我认为这是正确的方法。当C++引入size_t时,它让我相当烦恼,但我已经习惯了它。 - Viktor Sehr
然后 i = 0; i <= 0; 它按预期迭代一次... - deceleratedcaviar
3
无符号整数i的初始值为10;当i不为0时,执行循环体:每次迭代减小i的值并继续下一次循环。因此,该循环将迭代9到0这10个数字。 - Erik
https://dev59.com/RXI-5IYBdhLWcg3w18d3 - Robᵩ
显示剩余2条评论
14个回答

54
你可以使用
for( unsigned int j = n; j-- > 0; ) { /*...*/ }

它从n-1迭代到0


这种方法有局限性,所以我采纳了Alexandre C.的建议。 - Anupam Srivastava
不错的解决方案,但我必须承认,我觉得这有点难以阅读。 - TobiMcNamobi
@AnupamSrivastava 有哪些限制? - Yankee
2
我现在记不太清了,但是我认为如果你必须减少一个步骤>1,那么遵循这个模式会很困难。 - Anupam Srivastava
1
实际上,当步长不为1时,Alexandre C.的答案也不太适用。 - Osman-pasha
1
@AnupamSrivastava,“正常”的 for (int i = 0; i < n; i += step) 也不是100%可靠的。对于正确的 stepn 值(当 INT_MAX - n < step 时),表达式 i += step 可能会溢出,这是未定义行为。对于 int 来说,它可能不会,但对于较小的类型,你会遇到这个问题。我知道我曾经遇到过。 - Bolpat

28

以下代码实现了您想要的功能:

for (unsigned i = 10; i != static_cast<unsigned>(-1); --i)
{
    // ...
}

这段代码完美地定义了并且实际上是有效的。标准准确地定义了有符号类型的算术运算。确实:

4.7/2(关于转换为无符号类型):

如果目标类型是无符号的,则结果值是与源整数最小的无符号整数同余(模2^n,其中n是用于表示无符号类型的位数)

以及3.9.1/4

声明为无符号的无符号整数应遵守模2^n的算术定律,其中n是该特定大小的整数的值表示中使用的位数


无符号变量不能小于0,我不想每次都收到编译器的警告... - deceleratedcaviar
我通常会使用强制类型转换。或者在这种情况下,我只是使用 int。 - Alexandre C.
1
为什么会被点踩?4.7/2 在这一点上很清楚,代码的行为已经完美定义并且实现了你想要的功能。 - Alexandre C.
1
+1,回答了这个问题;虽然它涵盖了所有的基础知识,但并不完全符合我所期望的答案。强制类型转换似乎有些像作弊。 - deceleratedcaviar
4
如果没有进行强制类型转换,i会被提升为 int 类型并且行为是未定义的。你必须对 -1 进行强制类型转换。所以我的初始帖子是不正确的。 - Alexandre C.
@AlexandreC.,推荐始终将有符号类型转换为无符号类型,而不是反过来,因此不需要进行强制转换。实际上,最好不要使用强制转换,因为如果有人将第一个“unsigned”更改为“unsigned long”,但忘记更改后面的内容,则使用强制转换将进行“signed->unsigned->unsigned long”的提升,在64位上会导致0x00000000ffffffff,这是错误的,但是没有强制转换,提升将是“signed->signed long->unsigned long”,从而得到正确的值。 - Jan Hudec

4
我的通常模式是...
for( unsigned int i_plus_one = n; i_plus_one > 0; --i_plus_one )
{
    const unsigned int i = i_plus_one - 1;
    // ...
}

3
你是否真的从比 std::numeric_limits<int>::max() 更大的数字开始迭代?如果不是,我建议您只使用普通的 int 作为循环变量,并在代码中期望它为无符号时将其 static_castunsigned。这样,您可以使用直观的条件 >= 0> -1,一般来说,我认为这比任何无符号替代方案更易读。 static_cast 只是告诉编译器如何操作变量,没有任何性能影响。

你有证据证明这里没有实际的强制类型转换吗?这是有道理的,但我只是想确保一下。特别是因为我正在尽可能快地输入数字,虽然在这里进行强制类型转换并不是关键,但如果存在其他(非强制类型转换)的方法,我不明白为什么我要这样做。 - deceleratedcaviar

3
避免下溢。
unsigned int i = n;
while (i != 0) {
  --i;
  ...
}

2

我认为有两种选择,一种是使用有符号或无符号数字(可以通过与-1进行比较来隐式完成),另一种是使用循环条件来检查溢出,例如:

for(unsigned i=10;i>i-1;--i){ } // i = 10, 9, ... , 1
for(unsigned i=10;i+1>i;--i){ } // i = 10, 9, ... , 1,0

这个循环将会一直持续,直到 i 溢出(也就是达到了零)。请注意,i 的迭代必须为 1,否则你可能会陷入无限循环。


2
我最初使用了for(unsigned i = 10;i + 1 > 0;--i) { ... },但它似乎有点不清晰... 猜想这不是一个简单的解决方案 :P。 - deceleratedcaviar

1

我会使用两个变量:

unsigned int start = 10;
for (unsigned int j = 0, i = start; j <= start; ++ j, -- i) {
    // ...
}

你也可以使用 while 循环:

unsigned int start = 10;
unsigned int i = start + 1;
while (i --) {
    // ...
}

这正是我想要避免的,但考虑到情况,可能是最“明显”的解决方案。 - deceleratedcaviar
为什么使用两个变量,如果一个完全足够呢?对我来说,没有可读性的优势。第二个循环很好(大多数与我的相同,除了迭代器索引范围超出循环块)。 - Serge Dundich
因为我更喜欢第二个版本(我总是在写第一个版本时犯错误),但有些人发现它更难理解。 - Sylvain Defresne

1
for (unsigned int i = 10; i <= 10; --i)

你说得一点不错。

我不想将你十年前的问题还给你,但是避免下溢会使你最终尝试做的事情变得含糊不清。

对于无符号整数的下溢是一种明确定义的行为,你可以依赖它,并期望其他程序员能够理解。任何解决方法都会使你尝试操作的范围更难以解析,并且很可能对初学者来说同样令人困惑,同时如果他们将其作为“反向循环”的“教训”学习,则会传递一个糟糕的示例。


1

这里有一个简单的技巧,可以避免溢出,如果i每次迭代加1:

for(unsigned int i = n-1; i+1 >= 1; i--) {;}

如果您想使i迭代超过1:
unsigned int d = 2;
for(unsigned int i = n-1; i+d >= d; i-=d) {;}

0
for(unsigned i = x ; i != 0 ; i--){ ...

如果你想在 i == 0 时执行循环体并在此后停止。只需从 i = x+1; 开始。

顺便问一下,为什么 i 必须是无符号的?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接