有没有办法使用基于范围的for循环迭代最多N个元素?

Dec*_*ber 40 c++ boost stl c++11 c++14

我想知道是否有一种很好的方法可以使用基于for循环的范围和/或标准库中的算法迭代容器中的大多数N个元素(这就是重点,我知道我可以使用"旧的" "带有条件的for循环".

基本上,我正在寻找与此Python代码相对应的内容:

for i in arr[:N]:
    print(i)
Run Code Online (Sandbox Code Playgroud)

Pio*_*ycz 36

因为我个人会使用这个这个答案(两者都是+1),只是为了增加你的知识 - 你可以使用增强适配器.对于您的情况 - 切片似乎是最合适的:

#include <boost/range/adaptor/sliced.hpp>
#include <vector>
#include <iostream>

int main(int argc, const char* argv[])
{
    std::vector<int> input={1,2,3,4,5,6,7,8,9};
    const int N = 4;
    using boost::adaptors::sliced;
    for (auto&& e: input | sliced(0, N))
        std::cout << e << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

一个重要的注意事项:N要求sliced不大于distance(range)- 所以更安全(和更慢)的版本如下:

    for (auto&& e: input | sliced(0, std::min(N, input.size())))
Run Code Online (Sandbox Code Playgroud)

所以 - 再次 - 我会使用更简单,旧的C/C++方法(这是你想避免的问题;)


Bau*_*gen 13

这是最便宜的保存解决方案,适用于我可以提出的所有前向迭代器:

auto begin = std::begin(range);
auto end = std::end(range);
if (std::distance(begin, end) > N)
    end = std::next(begin,N);
Run Code Online (Sandbox Code Playgroud)

这可能会在几乎两次的范围内运行,但我认为没有其他方法可以获得范围的长度.

  • 我建议`std :: advance(begin,N)`而不是`std :: next`.如果可用,前者可以利用`RandomAccessInterator`,后者则不会. (2认同)
  • @BaummitAugen看起来我撒谎,从标准的`§24.4.4.6`为`std :: next()`*"效果:相当于advance(x,n);返回x;"*我不确定它是****要求**利用RandomAccessIterator,但如果他们没有,那将是一种耻辱. (2认同)

Pet*_*etr 8

您可以使用good old break来在需要时手动断开循环.它甚至可以使用基于范围的循环.

#include <vector>
#include <iostream>

int main() {
    std::vector<int> a{2, 3, 4, 5, 6};
    int cnt = 0;
    int n = 3;
    for (int x: a) {
       if (cnt++ >= n) break;
       std::cout << x << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • -1:问题明确表明他已经知道如何用他自己的for循环来做这件事.我意识到他也要求提供远程想法,但你的建议实际上并没有添加任何特定于ranged-for的东西.他想调整标准算法,比如`std :: for_each`.这可能涉及到使用迭代器. (2认同)

Mar*_* A. 7

C++非常棒,因为您可以编写自己的可怕解决方案并将其隐藏在抽象层下

#include <vector>
#include <iostream>

//~-~-~-~-~-~-~- abstraction begins here ~-~-~-~-~-//
struct range {
 range(std::vector<int>& cnt) : m_container(cnt),
   m_end(cnt.end()) {}
 range& till(int N) {
     if (N >= m_container.size())
         m_end = m_container.end();
     else
        m_end = m_container.begin() + N;
     return *this;
 }
 std::vector<int>& m_container;
 std::vector<int>::iterator m_end;
 std::vector<int>::iterator begin() {
    return m_container.begin();
 }
 std::vector<int>::iterator end() {
    return m_end;
 }
};
//~-~-~-~-~-~-~- abstraction ends here ~-~-~-~-~-//

int main() {
    std::vector<int> a{11, 22, 33, 44, 55};
    int n = 4;

    range subRange(a);        
    for ( int i : subRange.till(n) ) {
       std::cout << i << std::endl; // prints 11, then 22, then 33, then 44
    }
}
Run Code Online (Sandbox Code Playgroud)

Live Example

上面的代码显然缺少一些错误检查和其他调整,但我想清楚地表达这个想法.

这是有效的,因为基于范围的for循环产生类似于以下的代码

{
  auto && __range = range_expression ; 
  for (auto __begin = begin_expr,
       __end = end_expr; 
       __begin != __end; ++__begin) { 
    range_declaration = *__begin; 
    loop_statement 
  } 
} 
Run Code Online (Sandbox Code Playgroud)

(CFR).begin_exprend_expr

  • 你的代码是非法的,`range(a)`是临时的,`till()`返回对它的引用,并且该引用绑定在基于范围的for循环中(`auto && __range = range_expression`).然后在执行循环之前删除表达式中的中间临时值 - 最后得到一个悬空引用. (4认同)

No-*_*are 5

如果你的容器没有(或可能没有)RandomAccessIterator,那么还有一种方法可以为这只猫设置皮肤:

int cnt = 0;
for(auto it=container.begin(); it != container.end() && cnt < N ; ++it,++cnt) {
  //
}
Run Code Online (Sandbox Code Playgroud)

至少对我来说,这是非常可读的:-).无论容器类型如何,它都具有O(N)复杂度.

  • -1:问题明确表明他已经知道如何用他自己的for循环来做这件事.他想调整标准算法,比如`std :: for_each`.这可能涉及到使用迭代器. (7认同)

Yak*_*ont 5

这是一个索引迭代器.通常是样板,留下来,因为我很懒.

template<class T>
struct indexT
 //: std::iterator< /* ... */ > // or do your own typedefs, or don't bother
{
  T t = {};
  indexT()=default;
  indexT(T tin):t(tin){}
  indexT& operator++(){ ++t; return *this; }
  indexT operator++(int){ auto tmp = *this; ++t; return tmp; }
  T operator*()const{return t;}
  bool operator==( indexT const& o )const{ return t==o.t; }
  bool operator!=( indexT const& o )const{ return t!=o.t; }
  // etc if you want full functionality.
  // The above is enough for a `for(:)` range-loop
};
Run Code Online (Sandbox Code Playgroud)

它包装了一个标量类型T,然后*返回一个副本.它也适用于迭代器,有趣,这在这里很有用,因为它允许我们从指针有效地继承:

template<class ItA, class ItB>
struct indexing_iterator:indexT<ItA> {
  ItB b;
  // TODO: add the typedefs required for an iterator here
  // that are going to be different than indexT<ItA>, like value_type
  // and reference etc.  (for simple use, not needed)
  indexing_iterator(ItA a, ItB bin):ItA(a), b(bin) {}
  indexT<ItA>& a() { return *this; }
  indexT<ItA> const& a() const { return *this; }
  decltype(auto) operator*() {
    return b[**a()];
  }
  decltype(auto) operator->() {
    return std::addressof(b[**a()]);
  }
};
Run Code Online (Sandbox Code Playgroud)

索引迭代器包装了两个迭代器,第二个迭代器必须是随机访问.它使用第一个迭代器来获取索引,它用于从第二个查找值.

接下来,我们有一个范围类型.SFINAE改进版可以在许多地方找到.它使循环中的一系列迭代器迭代for(:)变得容易:

template<class Iterator>
struct range {
  Iterator b = {};
  Iterator e = {};
  Iterator begin() { return b; }
  Iterator end() { return e; }
  range(Iterator s, Iterator f):b(s),e(f) {}
  range(Iterator s, size_t n):b(s), e(s+n) {}
  range()=default;
  decltype(auto) operator[](size_t N) { return b[N]; }
  decltype(auto) operator[] (size_t N) const { return b[N]; }\
  decltype(auto) front() { return *b; }
  decltype(auto) back() { return *std::prev(e); }
  bool empty() const { return begin()==end(); }
  size_t size() const { return end()-begin(); }
};
Run Code Online (Sandbox Code Playgroud)

以下是帮助您轻松处理范围的帮助indexT:

template<class T>
using indexT_range = range<indexT<T>>;
using index = indexT<size_t>;
using index_range = range<index>;

template<class C>
size_t size(C&&c){return c.size();}
template<class T, std::size_t N>
size_t size(T(&)[N]){return N;}

index_range indexes( size_t start, size_t finish ) {
  return {index{start},index{finish}};
}
template<class C>
index_range indexes( C&& c ) {
  return make_indexes( 0, size(c) );
}
index_range intersect( index_range lhs, index_range rhs ) {
  if (lhs.b.t > rhs.e.t || rhs.b.t > lhs.b.t) return {};
  return {index{(std::max)(lhs.b.t, rhs.b.t)}, index{(std::min)(lhs.e.t, rhs.e.t)}};
}
Run Code Online (Sandbox Code Playgroud)

好的,几乎就在那里.

index_filter_it 获取一系列索引和随机访问迭代器,并将一系列索引迭代器放入随机访问迭代器的数据中:

template<class R, class It>
auto index_filter_it( R&& r, It it ) {
  using std::begin; using std::end;
  using ItA = decltype( begin(r) );
  using R = range<indexing_iterator<ItA, It>>;
  return R{{begin(r),it}, {end(r),it}};
}
Run Code Online (Sandbox Code Playgroud)

index_filter获取index_range一个随机访问容器,与其索引相交,然后调用index_filter_it:

template<class C>
auto index_filter( index_range r, C& c ) {
  r = intersect( r, indexes(c) );
  using std::begin;
  return index_filter_it( r, begin(c) );
}
Run Code Online (Sandbox Code Playgroud)

现在我们有:

for (auto&& i : index_filter( indexes(0,6), arr )) {
}
Run Code Online (Sandbox Code Playgroud)

和中提琴,我们有一个大型的乐器.

实例

Fancier过滤器是可能的.

size_t filter[] = {1,3,0,18,22,2,4};
using std::begin;
for (auto&& i : index_filter_it( filter, begin(arr) ) )
Run Code Online (Sandbox Code Playgroud)

将访问1,3,0,18,22,2,4 in arr.但是,除非进行arr.begin()[]边界检查,否则它不会进行边界检查.

上面的代码中可能存在错误,您应该只使用它boost.

如果您实现-[]打开indexT,您甚至可以菊花链式连接这些范围.


hon*_*onk 3

C++20开始,您可以将Ranges 库std::views::take中的范围适配器添加到基于范围的 for 循环中。通过这种方式,您可以实现与PiotrNycz 的答案中的解决方案类似的解决方案,但不使用 Boost:

int main() {
    std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9};
    const int N = 4;

    for (int i : v | std::views::take(N))
        std::cout << i << std::endl;
        
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案的好处是它N可能大于向量的大小。这意味着,对于上面的示例,可以安全使用N = 13;然后将打印完整的向量。

Wandbox 上的代码