如何在基于索引的范围for循环中跳过元素?

14

有没有一种方法可以在C++11的基于范围的for循环中访问迭代器(我想这里没有循环索引?)

通常我们需要对容器的第一个元素执行特殊操作,并遍历其余元素。因此,我正在寻找类似于此伪代码中的c++11_get_index_of语句:

for (auto& elem: container) 
{
  if (c++11_get_index_of(elem) == 0)
     continue;

  // do something with remaining elements
}

我希望在这种情况下避免返回到旧式手动迭代器处理代码。

2
使用提供所需功能的结构。也就是说,如果普通的 for 循环可以轻松解决您的问题,请使用它。不要强迫自己做不必要复杂的事情。 - Nawaz
1
使用首选范围的STL算法。如果没有算法适用,请只使用“for”循环。 - Karoly Horvath
2
@Jay:除非你想每次都祈祷容器不包含相等(==)的元素,否则最好不要这样做。 - Karoly Horvath
@Jay:正确的做法是使用迭代器,而不是索引(它们不是通用的)或C++算法(我不认为它们中的任何一个适用于此)。请参考Daniel的答案。 - user541686
@Jay 根据容器和跳过条件的不同,这可能会变得不必要地昂贵。如果容器中有重复元素,它也可能失败。 - Joseph Mansfield
显示剩余4条评论
8个回答

33

通常我们需要对容器的第一个元素执行特殊操作,然后迭代其余元素。

我很惊讶到目前为止还没有人提出这个解决方案:

  auto it = std::begin(container);

  // do your special stuff here with the first element

  ++it;

  for (auto end=std::end(container); it!=end; ++it) {

      // Note that there is no branch inside the loop!

      // iterate over the rest of the container
  }

它有一个巨大的优势,就是将分支移出循环。这使得循环更加简单,也许编译器可以更好地对其进行优化。

如果您坚持使用基于范围的for循环,可能最简单的方法是这样做(还有其他更丑陋的方法):

std::size_t index = 0;

for (auto& elem : container) {

  // skip the first element
  if (index++ == 0) {
     continue;
  }

  // iterate over the rest of the container
}

然而,如果你只需要跳过第一个元素,我建议将分支移出循环。


3
绝对更喜欢在循环外分支。 - Jay
这种方法的一个缺点是,当你从可能返回空列表的函数中获取容器时,会增加冗余性,因为你最终会对容器大小进行两次检查。 - John Neuhaus

6
Boost提供了一种简洁的方法来实现这一点:
std::vector<int> xs{ 1, 2, 3, 4, 5 };
for (const auto &x : boost::make_iterator_range(xs.begin() + 1, xs.end())) {
  std::cout << x << " ";
}
// Prints: 2 3 4 5

boost/range/iterator_range.hpp头文件中可以找到make_iterator_range


3
使用一个简单的for循环和迭代器如何?
for(auto it = container.begin(); it != container.end(); it++)
{
    if(it == container.begin())
    {
        //do stuff for first
    }
    else
    {
        //do default stuff
    }
}

这不是基于范围的,但它具有功能性。 如果你仍然想使用范围循环:

int counter = 0;
for(auto &data: container)
{
    if(counter == 0)
    {
        //do stuff for first
    }
    else
    {
        //do default stuff
    }
    counter++;
}

这些应该是预增量。 - underscore_d

2
不,你无法在基于范围的for循环中获取迭代器(当然,如果不查找容器中的元素)。标准将迭代器定义为__begin,但这仅用于说明。如果需要迭代器,则应使用普通的for循环。基于范围的for循环存在的原因是针对那些不需要自己处理迭代的情况。
使用autostd::beginstd::end,您的for循环仍应非常简单:
for (auto it = std::begin(container); it != std::end(container); it++)

在这里使用后置递增会引入无谓的开销,特别是对于迭代器是类类型而不是指针的容器。我希望每个人都能摆脱默认使用后置递增/递减的习惯。 - underscore_d
@underscore_d 这就是为什么它叫做 C++,对吧 :P 不过编译器不能注意到这个值没有被使用吗? - GeeTransit
@GeeTransit 是的,但它并不一定需要这样做,特别是对于可能具有副作用且不分析为无操作的复杂迭代器。而且,是的,它被称为C++有点不幸,但我怀疑“++C”不会有同样的效果,所以我们就这样了。;-) - underscore_d

2
迭代元素时,应始终首选使用算法,仅在没有任何算法适用时才使用普通的for循环。
选择正确的算法取决于您想要对元素执行什么操作...但您并没有告诉我们。
如果您想跳过第一个元素,请参考以下示例:
if (!container.empty()) {
   for_each(++container.begin(), container.end(), [](int val) { cout << val; });
}

我理解你的意思,虽然通常我们在处理业务对象时会有很多事情要做,所以即使是lambda表达式也可能变得很大并且难以控制。但是当我们需要跳过其中一个元素的处理时,使用for_each仍然存在问题,不是吗? - Jay
如果跳过是基于索引的,那么你可能做错了什么。重新构造你的数据。 - Karoly Horvath

0
当我需要在随机访问容器上执行类似操作时,我的习惯是迭代索引。
for( std::size_t i : indexes( container ) ) {
  if (i==0) continue;
  auto&& e = container[i];
  // code
}

唯一棘手的部分是编写索引,它返回了Boost所称的计数迭代器的范围。从迭代器创建基本可迭代范围很容易:可以使用Boost的范围概念,也可以自己编写。

任意迭代器类型的基本范围为:

template<typename Iterator>
struct Range {
  Iterator b; Iterator e;
  Range( Iterator b_, Iterator e_ ):b(b_), e(e_) {};
  Iterator begin() const { return b; }
  Iterator end() const { return e; }
};

你可以把它打扮得漂漂亮亮的,但那就是核心。


0

如果没有迭代器、指针或内部索引,就无法知道元素在容器中的位置。以下是一种简单的方法:

int index= 0;
for (auto& elem: container) 
{
  if (index++ == something)
     continue;

  // do something with remaining elements
}

如果你想跳过第一个元素,另一种方法是使用std::deque并弹出第一个元素。然后你可以像往常一样使用容器进行范围for循环。

0

我会尽量避免使用迭代器,因为基于范围的 for 循环的理念就是摆脱它们。截至C++20,要跳过你的容器中的第一个元素,我会采取以下其中一种方法。为了完整起见,我也包括如何单独处理第一个元素:

  1. 处理循环外的第一个元素

    您可以使用container.front()来访问第一个元素,该函数适用于所有序列容器。但是,您必须确保容器不为空,以避免出现分段错误。然后,要在循环中跳过第一个(或更多)元素,您可以使用Ranges库中的范围适配器std::views::drop。所有这些内容如下所示:

    std::vector<int> container { 1, 2, 3 };
    
    if(!container.empty()) {
        // 处理第一个元素
        std::cout << "First element: " << container.front() << std::endl;
    }
    
    for (auto& elem : container | std::views::drop(1)) {
        // 处理剩余元素
        std::cout << "Remaining element: " << elem << std::endl;
    }
    

    除了container.front()之外,您还可以使用另一个基于范围的for循环,结合范围适配器std::views::take(1)。使用take()drop()的优点是,即使它们的参数超过了container中元素的数量,它们也可以安全地工作。

  2. 处理循环内的第一个元素

    您可以在基于范围的for循环中使用初始化语句来定义一个布尔标志(甚至是计数器)。这样,该标志仅在循环范围内可见。您可以在循环内部如下使用该标志:

    std::vector<int> container { 1, 2, 3 };
    
    for(bool isFirst(true); auto& elem : container) {
        if(isFirst) {
            // 处理第一个元素
            std::cout << "First element: " << elem << std::endl;
            isFirst = false;
            continue;
        }
    
        // 处理剩余元素
        std::cout << "Remaining element: " << elem << std::endl;
    }
    

两种方法的输出如下:

第一个元素:1
其余元素:2
其余元素:3

Wandbox上的代码


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