使用无符号循环变量进行反向迭代

时间:2010-09-02 01:37:14

标签: c++ c

我一直在讨论与同事一起使用size_t的问题。出现的一个问题是循环,它将循环变量递减直到达到零。

请考虑以下代码:

for (size_t i = n-1; i >= 0; --i) { ... }

由于无符号整数环绕,这会导致无限循环。在这种情况下你做什么?编写上面的代码并没有意识到你犯了一个错误似乎很容易。

我们团队的两个建议是使用以下样式之一:

for (size_t i = n-1; i != -1 ; --i) { ... }

for (size_t i = n; i-- > 0 ; ) { ... }

但我确实想知道还有其他选择......

11 个答案:

答案 0 :(得分:81)

就我个人而言:

for (size_t i = n; i --> 0 ;)

它有a)没有搞笑-1,b)条件检查是助记符,c)它以合适的笑脸结束。

答案 1 :(得分:53)

保证无符号整数很好地包裹。他们只是实现了算术模2 N 。所以这个易于阅读的习语是这样的:

for (size_t i = n-1; i < n ; --i) { ... }

这会将变量设置为您想要的初始值,显示迭代的意义(向下)并精确地给出您要处理的值的条件。

答案 2 :(得分:8)

  1. 用算法替换循环。
  2. 使用反向迭代器而不是整数。
  3. 从n倒数到1,但在循环内使用i-1代替i

答案 3 :(得分:5)

您使用的是标准库容器吗?如果是这样,我喜欢reverse_iterator

   vector<int> ivect;

   // push, push, push...

   vector<int>::reverse_iterator riter;
   for(riter=riter.rbegin(); riter!=ivect.rend(); ++riter)
   {
       //...
   }

对于原始数组,您可以使用std::reverse_iterator键来指示迭代器:

int i[] = {1, 2, 3, 4};

typedef std::reverse_iterator<const int*> irevit;

irevit iter(i+4);
irevit end(i);
for(; iter != end; ++iter) {
    cout << *iter;
}

// Prints 4321

非连续对象迭代可以通过将对象指针存储在容器或数组中来完成:

struct Foo {
    Foo(int i) I(i) { }
    int I;
}

vector<Foo*> foos;
for(int i = 0; i < 10; ++i)
    foos.push_back(new Foo(i));

typedef vector<Foo*>::const_reverse_iterator frevit;

frevit iter(foos.rbegin());
for(; iter != foos.rend(); ++iter) {
    cout << (*iter)->I;
}

// Prints 9876543210

如果你真的想要使用裸size_t那么为什么在其他答案中使用所有这些隐含的混乱-1欺骗? size_t的最大值明确可用作终止值:

int is[] = {1, 2, 3, 4};
int n = 3;

for (size_t i = n; i != std::numeric_limits<size_t>::max(); --i) {
    cout << is[i] << endl;
}

// prints 4321

答案 4 :(得分:5)

如果你担心意外写这样的循环,一些编译器会警告这些事情。例如,gcc通过-Wtype-limits选项启用了警告(也由-Wextra启用):

x.c:42: warning: comparison of unsigned expression >= 0 is always true

答案 5 :(得分:3)

i != -1依赖于-1默默地投射到size_t,这对我来说似乎很脆弱,因此,在您提出的替代方案中,我肯定会选择递减一个。另一种可能性(特别是如果你在循环体中实际上不需要i但只需要以相反的顺序迭代一个数组)就是将数组包装在std::类容器中并且使用rbeginrend方法在包装器上使用迭代器。例如,Boost.Array将支持后者的选择。

答案 6 :(得分:1)

以下是关于此主题的good discussion指针。

我会尝试:

for( size_t i = n; i != 0; i-- ) {
  // do stuff with array[ i - 1 ]
}

答案 7 :(得分:1)

size_t i = n-1;

do  { 
  ...
} while ( i-- != 0);

如果需要,您可以使用if (n > 0)将其换行。

答案 8 :(得分:1)

另一种方式(没有签名/未签名的比较):

for (size_t i = n-1; i + 1 > 0; i--)

因为

(i + 1 > 0) === (i > -1)

答案 9 :(得分:1)

在C ++ 20中,您可以使用范围和视图,如下所示:

namespace sv = std::views;
    
for (unsigned i : sv::iota(0u, n) | sv::reverse)
    std::cout << i << "\n";  

这里是demo

该代码可读性强,并且完全避免了无符号环绕行为的任何问题,因为i仅具有[0,n)范围内的值。

答案 10 :(得分:0)

我发现另一种简单有效的解决方案(适用于POSIX的系统)是用ssize_t替换size_t:

for (ssize_t i = n-1; i >= 0; --i) { ... }

在非POSIX系统上,ssize_t的类型定义并不难: Alternative to ssize_t on POSIX-unconformant systems