在反向迭代向量中循环

43

我需要从向量末尾向开头迭代。正确的方法是:

for(std::vector<SomeT>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit)
{
    //do Something
}

//do Something需要知道实际索引时,需要使用rit进行一些计算来获取它,例如index = v.size() - 1 - (rit - v.rbegin)

如果无论如何都需要索引,我强烈建议使用该索引进行迭代。

for(int i = v.size() - 1; i >= 0; --i)
{
    //do something with v[i] and i; 
}

这会产生一个警告,指出 i 是有符号的,而 v.size() 是无符号的。 更改为

for(unsigned i = v.size() - 1; i >= 0; --i) 在功能上是错误的,因为这实质上是一个无限循环 :)

有什么美观的方式可以做到我想要做的事情,同时

  • 没有警告
  • 不涉及转换
  • 不过度冗长

循环条件为 i != std::numeric_limits<unsigned>::max() ... 或者如果您认为太冗长,可以使用 UINT_MAX。 - smerlin
到目前为止,我认为进行强制类型转换看起来是最干净的解决方案 :-) - David Gelhar
只需在i上设置一个上限,即v.size()。 - Nim
@Steve:假设该元素的值必须乘以它的索引或类似操作 :) - Armen Tsirunyan
4
for (size_t i = v.size(); i --> 0; ) 可以翻译为:从 v 的大小开始循环,每次递减1直到0为止,循环过程中使用变量 i - UncleBens
显示剩余3条评论
11个回答

65

正如您所指出的,如果在无符号情况下使用条件i >= 0,那么条件永远为真。可以在检查循环条件后减去1,而不是在初始化i和每次迭代后再次减去1:

for (unsigned i = v.size(); i-- > 0; )

我喜欢这种风格,原因如下:

  • 尽管循环中的 i 会在末尾回绕到 UINT_MAX,但它并不依赖于这种行为,即使类型是有符号的,它也能够正常工作。依赖于无符号整数的回绕感觉有点像黑客。
  • 它仅调用一次 size()
  • 它不使用 >=。每当我在 for 循环中看到这个运算符时,我都必须重新阅读它,以确保没有出现 off-by-one 错误。
  • 如果您更改条件语句中的间距,则可以使用“goes to”运算符

3
应该是 size_t 而不是 unsigned 吧? - dynamic
4
如果你确实预期有更多的项目,超出了一个“unsigned”所能容纳的范围,那么使用std::vector<SomeT>::size_type是正式正确的类型,或者现在可以直接使用auto。但是如果你需要的仅仅是一个更好的解决方案,建议使用后者。@Yes123 - Rob Kennedy

17

您的 reverse_iterator 循环也可以像其他答案中所述一样使用索引。这样,您可以在 // do the work 部分根据需要使用迭代器或索引,而几乎不增加额外的成本。

size_t index = v.size() - 1;
for(std::vector<SomeT>::reverse_iterator rit = v.rbegin(); 
    rit != v.rend(); ++rit, --index)
{
  // do the work
}

虽然我很好奇你需要索引做什么。访问v[index]与访问*rit是相同的。


这实际上是对于任何非随机访问的基础类型来说唯一正确的答案。假设 std::vector<SomeT> 在某个地方被 typedef 掉了,我更愿意使用正确的迭代器,以防以后决定使用 list、multiset、map 等等。 - Ben Jackson
1
使用auto来减少冗长性。 - Kabira K

7
为了视觉上更加美观!;)
for(unsigned i = v.size() - 1; v.size() > i; --i)

@Armen,嗯,但是无符号(默认为int)可能vectorsize_type更小,至于为什么你可以在向量中有超过40亿个条目,那是另一个问题... :)..好吧,好吧...我投降了... - Nim
如果 v.size() == UINT_MAX 怎么办?我知道这在实践中永远不会发生,但这是一个理论上的缺陷。 - Jean-Bernard Jansen
@JB:vector::size_type 不是无符号整数也是一个理论上的缺陷。但是只要没有实际的机会出现反弹,我真的不在乎理论上的缺陷。-Armen Tsirunyan 7秒前编辑 - Armen Tsirunyan
我不知道是不是我没有有效地使用包装,但我通常会用 for (i = v.size(); i > 0; --i),然后使用 i-1 作为实际索引。这可能只是个人偏好。 - Matthieu M.

4

在C++20中,可以使用范围(#include <ranges>

//DATA
std::vector<int> vecOfInts = { 2,4,6,8 };

//REVERSE VECTOR 
for (int i : vecOfInts | std::views::reverse)
{
     std::cout << i << "  ";
} 

或者如果需要保存在不同的变量中。

//SAVE IN ANOTHER VARIABLE
auto reverseVecOfInts = std::views::reverse(vecOfInts);

//ITERATION
for (int i : reverseVecOfInts)
{
    std::cout << i << "  ";
}

1
虽然这种方法看起来很好,但它会影响性能 - reverse 将执行 _恰好 (last - first) / 2 次交换_,在某些情况下可能会产生明显的开销。 - AntonK

2
我更倾向于使用反向迭代器的变量,因为它仍然容易理解并可避免与索引相关的错误。
有时您可以简单地使用BOOST_REVERSE_FOREACH,这将使您的代码如下所示:
reverse_foreach (int value, vector) {
   do_something_with_the_value;
}

实际上,您总是可以使用 foreach 语句进行这些类型的循环,但这样它们就变得有点不明显了:

size_t i = 0;

foreach (int value, vector) {
   do_something;
   ++i;
}

0

尝试使用 do while:

std::vector<Type> v;
// Some code 
if(v.size() > 0)
{
    unsigned int i = v.size() - 1;
    do
    {
        // Your stuff
    }
    while(i-- > 0);
}

好的,没问题;)。我测试了do while循环,它可以工作。我不是说这是最好的解决方案,但它是可用的之一。 - Jean-Bernard Jansen

0
如果你的向量不是很大,你可以将大小转换为整数。
for (int i = int(v.size()) - 1; i >= 0; --i) {
    //do something with v[i] and i; 
}

0

嗨,我认为更好的方法是像你在第一个样本中使用迭代器,如果你需要获取迭代器索引,可以使用std::distance来计算,如果我理解你的问题


0

循环条件 i != std::numeric_limits<unsigned>::max() ... 或者使用 UINT_MAX 如果你认为它太啰嗦了。 另一种方式:
for(unsigned j=0, end=v.size(), i=end-1; j<end; --i, ++j)
或者
for(unsigned end=v.size(), i=end-1; (end-i)<end; --i)


-3

我认为:

for(unsigned i = v.size() - 1; i >= 0; --i)

如果你检查过了,那就没问题了。

!v.empty()

早些时候。


@HardCoder1986:再想一想 :) - Armen Tsirunyan
代码无法正常工作,因为存在下溢错误。最好不要一开始就减去 1,而是使用 greater 进行比较,这样写 for(unsigned i = v.size(); i > 0; --i) { ... },但在循环中访问当前元素时必须使用 v[i-1] 来按索引访问。 - AntonK

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