끝에서 처음으로 C ++ 벡터 반복


101

끝에서 시작까지 벡터를 반복 할 수 있습니까?

for (vector<my_class>::iterator i = my_vector.end();
        i != my_vector.begin(); /* ?! */ ) {
}

또는 다음과 같은 경우에만 가능합니다.

for (int i = my_vector.size() - 1; i >= 0; --i) {
}

2
C ++ 11에서는 역방향 어댑터와 함께 범위 기반 for 루프를 사용할 수 있습니다. 여기를 참조하십시오
MM

1
이론적으로 32 비트 시스템에서 두 번째 솔루션의 경우 벡터 크기가 2,147,483,647 + 1보다 크면 오버플로 (vector :: size ()는 부호 없음)되지만 현재는 해당 제한에 도달하지 않을 가능성이 있습니다 (또한 32 비트 머신의 현재 벡터 제한은 1,073,741,823입니다).
Stefan Rogin 2014

@StefanRogin 오버플로 문제는 for 루프에서 "int i"대신 누군가가 size_t (또는 auto)를 사용하여 컴파일러 경고를 피하기 위해 (int에 대한 size () 할당으로 인해) 사용할 때 현실이됩니다. 이것과 단일 요소 벡터의 경우 두 번째 반복이 auto i를 오버플로하고 루프가 오버플로 "i"로 실행되어 모든 종류의 충돌이 발생합니다.
n-mam

답변:


162

가장 좋은 방법은 다음과 같습니다.

for (vector<my_class>::reverse_iterator i = my_vector.rbegin(); 
        i != my_vector.rend(); ++i ) { 
} 

rbegin()/ rend()특별히 그 목적을 위해 설계되었습니다. (예, a를 증가 reverse_interator시키면 뒤로 이동합니다.)

이제 이론적으로는 방법 ( begin()/ end()& 사용 --i)이 작동 할 것입니다. std::vector반복기는 양방향이지만 end()마지막 요소는 아닙니다. 마지막 요소를 넘어서는 요소이므로 먼저 감소해야합니다. 도달하면 완료 begin()되지만 여전히 처리를해야합니다.

vector<my_class>::iterator i = my_vector.end();
while (i != my_vector.begin())
{
     --i;
    /*do stuff */

} 

업데이트 : 나는 for()루프를 루프로 다시 쓰는 데 너무 공격적이었습니다 while(). (중요한 부분은이 --i시작 부분에 있다는 것 입니다.)


방금 --i컨테이너가 비어 있으면 큰 문제가 발생할 것임을 깨달았 습니다. do - while루프에 들어가기 전에 확인하는 것이 좋습니다 (my_vector.begin() != my_vector.end()).
a1ex07

1
do-while루프 대신 루프 를 사용하는 이유는 무엇 while입니까? 그러면 빈 벡터에 대한 특별한 검사가 필요하지 않습니다.
jamesdlin

auto가독성을 높이기 위해 답변을 업데이트 할 수 있습니까?
LNJ

60

C ++ 11이있는 경우 auto.

for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it)
{
}

30

폐쇄-개방 범위를 통한 역 반복을위한 잘 확립 된 "패턴"은 다음과 같습니다.

// Iterate over [begin, end) range in reverse
for (iterator = end; iterator-- != begin; ) {
  // Process `*iterator`
}

또는 원하는 경우

// Iterate over [begin, end) range in reverse
for (iterator = end; iterator != begin; ) {
  --iterator;
  // Process `*iterator`
}

이 패턴은 예를 들어 부호없는 인덱스를 사용하여 배열을 역 인덱싱하는 데 유용합니다.

int array[N];
...
// Iterate over [0, N) range in reverse
for (unsigned i = N; i-- != 0; ) {
  array[i]; // <- process it
}

(이 패턴에 익숙하지 않은 사람들 은 서명되지 않은 유형이 역 인덱싱에 "사용할 수 없다"고 잘못 믿기 때문에 특히 배열 인덱싱에 부호있는 정수 유형을 사용해야한다고 주장합니다.)

"슬라이딩 포인터"기술을 사용하여 배열을 반복하는 데 사용할 수 있습니다.

// Iterate over [array, array + N) range in reverse
for (int *p = array + N; p-- != array; ) {
  *p; // <- process it
}

또는 일반 (역방향 아님) 반복기를 사용하여 벡터에 대한 역 반복에 사용할 수 있습니다.

for (vector<my_class>::iterator i = my_vector.end(); i-- != my_vector.begin(); ) {
  *i; // <- process it
}

cppreference.com은 내가 생각하는, 그래서 루프에서 시작해야한다, 끝 () "정의되지 않은 동작의 결과"에있는 요소에 액세스 말한다--end()
토마스 슈미트

1
@ThomasSchmid 이러한 루프는에서 액세스를 시도하지 않습니다 end(). 에서 시작하는 것처럼 보이지만 end()항상 첫 번째 액세스 전에 반복자를 감소시켜야합니다.
AnT

이것은 rbegin / rend보다 훨씬 좋습니다. 런타임에 다른 방식으로 반복 할 수 있기 때문입니다 (템플릿 없음) auto a = vector<int>{0,1,2}; bool reversed = 0; auto it = (!reversed?a.begin():a.end()); auto end = (reversed?a.begin():a.end()); while(it != end) { if(reversed)--it; cout << *it << endl; if(!reversed)++it; }
colin

1
@colin Egads! 그 못생긴!. reversed 번 테스트 하고 있는데 두 번은 루프 안에 있습니다. 물론 부울 테스트는 매우 빠르지 만 그래도 작업 할 필요가없는 이유는 무엇입니까? 특히, 유일한 목적은 코드를 읽을 수 없게 만드는 것 같습니다. 두 개의 개별 루프를 사용하는 것은 어떻습니까? if (reversed) for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it) {doStuff(*it);} else for (auto it = my_vector.begin(); it != my_vector.end(); ++it) {doStuff(*it);}
James Curran 19

사실 당신은 내 요점을 놓쳤습니다. 당신은 그것을 두 개로 나누는 것이 절대적으로 옳지 ifdoStuff(). 두 개의 ifs로 첫 번째 것에서 반대 방향으로 반복하여 여전히 할 수 있습니다.
colin

17

C ++ 20부터 a std::ranges::reverse_view및 범위 기반 for 루프를 사용할 수 있습니다 .

#include<ranges>
#include<vector>
#include<iostream>

using namespace std::ranges;

std::vector<int> const vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

for(auto& i :  views::reverse(vec)) {
    std::cout << i << ",";
}

또는

for(auto& i :  vec | views::reverse)

불행히도 작성 시점 (2020 년 1 월)에는 주요 컴파일러가 범위 라이브러리를 구현하지 않지만 Eric Niebler의 ranges-v3에 의지 할 수 있습니다 .

#include <iostream>
#include <vector>
#include "range/v3/all.hpp"

int main() {

    using namespace ranges;

    std::vector<int> const vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    for(auto& i :  views::reverse(vec)) {
        std::cout << i << ",";
    }

    return 0;
}

9

사용자 rend() / rbegin()반복자 :

for (vector<myclass>::reverse_iterator it = myvector.rbegin(); it != myvector.rend(); it++)


6
template<class It>
std::reverse_iterator<It> reversed( It it ) {
  return std::reverse_iterator<It>(std::forward<It>(it));
}

그때:

for( auto rit = reversed(data.end()); rit != reversed(data.begin()); ++rit ) {
  std::cout << *rit;

또는 C ++ 14에서는 다음을 수행하십시오.

for( auto rit = std::rbegin(data); rit != std::rend(data); ++rit ) {
  std::cout << *rit;

C ++ 03 / 11에서 대부분의 표준 컨테이너에는 .rbegin().rend()메서드도 있습니다.

마지막 backwards으로 다음과 같이 범위 어댑터 를 작성할 수 있습니다 .

namespace adl_aux {
  using std::begin; using std::end;
  template<class C>
  decltype( begin( std::declval<C>() ) ) adl_begin( C&& c ) {
    return begin(std::forward<C>(c));
  }
  template<class C>
  decltype( end( std::declval<C>() ) ) adl_end( C&& c ) {
    return end(std::forward<C>(c));
  }
}

template<class It>
struct simple_range {
  It b_, e_;
  simple_range():b_(),e_(){}
  It begin() const { return b_; }
  It end() const { return e_; }
  simple_range( It b, It e ):b_(b), e_(e) {}

  template<class OtherRange>
  simple_range( OtherRange&& o ):
    simple_range(adl_aux::adl_begin(o), adl_aux::adl_end(o))
  {}

  // explicit defaults:
  simple_range( simple_range const& o ) = default;
  simple_range( simple_range && o ) = default;
  simple_range& operator=( simple_range const& o ) = default;
  simple_range& operator=( simple_range && o ) = default;
};
template<class C>
simple_range< decltype( reversed( adl_aux::adl_begin( std::declval<C&>() ) ) ) >
backwards( C&& c ) {
  return { reversed( adl_aux::adl_end(c) ), reversed( adl_aux::adl_begin(c) ) };
}

이제 다음과 같이 할 수 있습니다.

for (auto&& x : backwards(ctnr))
  std::cout << x;

꽤 예쁘다고 생각합니다.



1

나는 Yakk-Adam Nevraumont의 대답 끝에있는 역방향 반복기를 좋아하지만 내가 필요한 것에 대해 복잡해 보였으므로 다음과 같이 썼습니다.

template <class T>
class backwards {
    T& _obj;
public:
    backwards(T &obj) : _obj(obj) {}
    auto begin() {return _obj.rbegin();}
    auto end() {return _obj.rend();}
};

나는 다음과 같은 정상적인 반복자를 취할 수 있습니다.

for (auto &elem : vec) {
    // ... my useful code
}

반대로 반복하려면 다음과 같이 변경하십시오.

for (auto &elem : backwards(vec)) {
    // ... my useful code
}

1

다음은 각 구문을 사용할 수 있고 C ++ 14 표준 라이브러리에만 의존하는 매우 간단한 구현입니다.

namespace Details {

    // simple storage of a begin and end iterator
    template<class T>
    struct iterator_range
    {
        T beginning, ending;
        iterator_range(T beginning, T ending) : beginning(beginning), ending(ending) {}

        T begin() const { return beginning; }
        T end() const { return ending; }
    };

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// usage:
//  for (auto e : backwards(collection))
template<class T>
auto backwards(T & collection)
{
    using namespace std;
    return Details::iterator_range(rbegin(collection), rend(collection));
}

이것은 rbegin () 및 rend ()를 제공하는 것뿐만 아니라 정적 배열에서도 작동합니다.

std::vector<int> collection{ 5, 9, 15, 22 };
for (auto e : backwards(collection))
    ;

long values[] = { 3, 6, 9, 12 };
for (auto e : backwards(values))
    ;

1

Boost Library를 사용할 수 있다면 다음 을 포함 하여 범위 어댑터 를 제공하는 Boost.Range 가 있습니다 .reverse

#include <boost/range/adaptor/reversed.hpp>

그런 다음 C ++ 11의 range- forloop 와 함께 다음과 같이 작성할 수 있습니다.

for (auto& elem: boost::adaptors::reverse(my_vector)) {
   // ...
}

이 코드는 반복자 쌍을 사용하는 것보다 짧기 때문에주의해야 할 세부 사항이 적기 때문에 더 읽기 쉽고 오류 발생 가능성이 적습니다.


1
실제로 boost::adaptors::reverse매우 유용합니다!
Kai Petzke

-1

이 코드 사용

//print the vector element in reverse order by normal iterator.
cout <<"print the vector element in reverse order by normal iterator." <<endl;
vector<string>::iterator iter=vec.end();
--iter;
while (iter != vec.begin())
{
    cout << *iter  << " "; 
    --iter;
}

1
이 코드 vec는 빈 벡터를 참조 하면 끔찍하게 실패합니다 !
Kai Petzke

-2

외계인과 같은 새로운 C ++ 구문을 도입하고 싶지 않고 단순히 기존 기본 요소를 구축하고 싶기 때문에 아래 스 니펫이 작동하는 것 같습니다.

#include <vector>
#include <iostream>

int main (int argc,char *argv[])
{
    std::vector<int> arr{1,2,3,4,5};
    std::vector<int>::iterator it;

    // iterate forward
    for (it = arr.begin(); it != arr.end(); it++) {
        std::cout << *it << " ";
    }

    std::cout << "\n************\n";
 
    if (arr.size() > 0) {
        // iterate backward, simple Joe version
        it = arr.end() - 1;
        while (it != arr.begin()) {
            std::cout << *it << " ";
            it--;
        }
        std::cout << *it << " ";
    } 

    // iterate backwards, the C++ way
    std::vector<int>::reverse_iterator rit;
    for (rit = arr.rbegin(); rit != arr.rend(); rit++) {
        std::cout << *rit << " ";
    }

    return 0;
}

이 코드 arr는 빈 벡터를 참조 하면 끔찍하게 실패합니다 !
Kai Petzke
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.