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

我一直在和同事讨论 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 ; ) { ... }

但我很好奇还有什么别的选择。

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

您使用的是标准库容器吗? 如果是的话,我喜欢 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

i != -1依赖于 -1被静默地转换为 size_t,这对我来说似乎很脆弱,所以,在你提出的替代方案中,我肯定会选择减量后的方案。另一种可能性(尤指。如果你实际上不需要在循环体中使用 i,只是需要对一个数组进行反向迭代) ,那么就是把这个数组包装在一个类似于 std::的容器中,并在包装器上使用一个迭代器,使用 rbeginrend方法。例如,推进,数组将支持后一种选择。

下面是一个指向这个主题的 讨论得不错的指针。

我会试试:

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


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

如果需要,可以用 if (n > 0)包装。

如果您担心会不小心写出这样的循环,一些编译器会提醒您注意这些事情。例如,gcc 有一个由 -Wtype-limits选项启用的警告(也由 -Wextra启用) :

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

无符号整数保证可以很好地包装。他们只是实现了算术模2N。因此,一个容易理解的习语是这样的:

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

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

就我个人而言,我开始喜欢:

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

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

还有一种方法(没有签名/未签名的比较) :

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

从那以后

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

我发现的另一个简单而有效的解决方案(适用于符合 POSIX 的系统)是将 size _ t 替换为 ssize _ t:

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

在非 POSIX 系统上,ssize _ t 并不难于 typedef: POSIX 不一致系统上 ssize _ t 的替代方案

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

namespace sv = std::views;
    

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

这是 小样

该代码非常易读,并且完全避免了无符号环绕行为的任何问题,因为 i的值只在 [0,n)范围内。