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

Dan*_*ull 55 c c++

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

请考虑以下代码:

for (size_t i = n-1; i >= 0; --i) { ... }
Run Code Online (Sandbox Code Playgroud)

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

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

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

for (size_t i = n; i-- > 0 ; ) { ... }
Run Code Online (Sandbox Code Playgroud)

但我不知道还有其他选择......

vis*_*tor 84

就个人而言,我喜欢:

for (size_t i = n; i --> 0 ;)
Run Code Online (Sandbox Code Playgroud)

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

  • 一个缺点是我从n-1(非n)开始,这在视觉上是不明显的 (18认同)
  • 伙计,那个岩石! (9认同)
  • 正如@phuclv 指出的那样,C++ 的新手可能会对上述符号感到非常困惑。我更愿意将其视为:`for (size_t i = n; i-- > 0; )`。 (6认同)
  • [" - >"运算符的名称是什么?](http://stackoverflow.com/q/1642028/995714) (4认同)
  • 那不是运算符,它的一元后缀 - 后跟二进制 > 即递减这个东西存储结果并将原始值与 > 右侧的任何值进行比较 (2认同)

Jen*_*edt 55

无符号整数保证很好地包裹.他们只是执行算术模2 ñ.所以这个易于阅读的习语是这样的:

for (size_t i = n-1; i < n ; --i) { ... }
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为这是最好的答案 (4认同)
  • TIL表示无符号反向迭代并不是那么糟糕. (3认同)
  • 尽管此方法有效,但实际上看起来像是个错误,如果有人将类型更改为签名,它将失败。我建议使用此方法的任何人在注释中添加大量警告,以表示谢意任何继承该代码的开发人员。 (2认同)

Jer*_*fin 9

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


jos*_*rry 5

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

   vector<int> ivect;

   // push, push, push...

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

对于原始数组,您只需使用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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

如果你真的想要裸体使用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
Run Code Online (Sandbox Code Playgroud)


caf*_*caf 5

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

x.c:42: warning: comparison of unsigned expression >= 0 is always true
Run Code Online (Sandbox Code Playgroud)


cig*_*ien 5

从 C++20 开始,您可以使用范围和视图,如下所示:

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

这是一个演示

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