C ++ vektörünü sondan başa yineleme


101

Bir vektörü baştan sona yinelemek mümkün mü?

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

Yoksa bu sadece böyle bir şeyle mümkün mü?

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

2
C ++ 11'de, ters adaptörle menzil tabanlı for-
MM

1
teorik olarak, 32 bitlik bir makinede, ikinci çözüm için, vektör boyutu 2.147.483.647 + 1'den büyükse taşacaktır (vector :: size () işaretsizdir), ancak şu anda şansınız bu limite asla ulaşamayacağınızdır (ayrıca 32 bit makinelerde geçerli vektör limiti 1.073.741.823'tür).
Stefan Rogin

@StefanRogin taşma sorunu, for döngüsünde "int i" yerine birisi derleyici uyarılarından kaçınmak için (size () int'e atanmasından dolayı) size_t (veya belki de auto) kullandığında gerçek olur. Bununla ve tek bir eleman vektörü için, ikinci yineleme otomatik i taşar ve döngü, taşan "i" ile yürütülür ve her tür çökmeye neden olur.
n-mam

Yanıtlar:


163

En iyi yol şudur:

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

rbegin()/ rend()özellikle bu amaç için tasarlandı. (Ve evet, a'yı artırmak reverse_interatoronu geriye doğru hareket ettirir.)

Şimdi, teoride, yönteminiz ( begin()/ end()& kullanarak --i) işe std::vectoryarar, yineleyicinin çift yönlü olması, ancak unutmayın, end()son öğe değildir - bu, son öğenin ötesindedir, bu nedenle önce azaltmanız gerekir ve siz ulaştığınızda yapılır begin()- ancak yine de işlemlerinizi yapmanız gerekir.

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

} 

GÜNCELLEME: Görünüşe göre for()döngüyü bir while()döngüye yeniden yazarken çok agresif davrandım. (Önemli --iolan, başlangıçta olmasıdır.)


--iKonteynır boşsa bunun büyük bir soruna neden olacağını yeni fark ettim ... Döngüye girmeden önce do - whilekontrol etmek mantıklı (my_vector.begin() != my_vector.end()).
a1ex07

1
Neden do-whilesadece bir whiledöngü yerine bir döngü kullanıyorsunuz? O zaman boş vektörler için özel bir kontrole ihtiyacınız olmaz.
jamesdlin

autoDaha iyi okunabilirlik için kullanmak üzere yanıtı güncelleyebilir misiniz ?
LNJ

60

C ++ 11'iniz varsa auto.

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

30

Kapalı-açık aralıklarla ters yineleme için iyi bilinen "model" aşağıdaki gibidir

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

veya tercih ederseniz,

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

Bu şablon, örneğin işaretsiz bir dizin kullanarak bir diziyi tersine endekslemek için kullanışlıdır.

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

(Bu kalıba aşina olmayan kişiler genellikle işaretli özellikle işaretsiz türlerin bir şekilde ters dizinleme için "kullanılamaz" olduklarına yanlış bir şekilde inanmaları nedeniyle, özellikle dizi dizinleme için tam sayı türleri ederler)

Bir "kayan işaretçi" tekniği kullanılarak bir dizi üzerinde yineleme yapmak için kullanılabilir

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

veya sıradan (ters değil) bir yineleyici kullanarak bir vektör üzerinde ters yineleme için kullanılabilir

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

cppreference.com diyor ki, sonunda () öğeye erişmenin "tanımsız davranışla sonuçlandığını", bu yüzden döngülerin başlaması gerektiğini düşünüyorum--end()
Thomas Schmid

1
@ThomasSchmid Bu döngüler asla adresinden erişmeye çalışmaz end(). Başlıyor gibi görünseler bile end(), her zaman ilk erişimden önce yineleyiciyi azaltmaya özen gösterirler.
AnT

Bu, rbegin / rend'den çok daha güzel çünkü çalışma zamanında diğer şekilde döngü yapabilirsiniz (şablon yok) 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! O çirkin!. reversed Dört kez test ediyorsunuz - ikisi bir döngü içinde. Elbette, bir boole testi çok hızlıdır, ancak yine de neden çalışmak zorunda değilsiniz? Özellikle, tek amaç kodu okunamaz hale getirmek gibi göründüğü için. iki ayrı döngü kullanmaya ne dersiniz? 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

Aslında amacımı kaçırdın. Onu ikiye bölmekte kesinlikle haklısınız, ifancak şablondaki şablondan kurtulmak istedim doStuff(). Yine de, sahip olduğunuz iki ifs ile, birincisinde diğer yolu döndürerek yapılabilir .
colin

17

C ++ 20'den başlayarak, a std::ranges::reverse_viewve aralık tabanlı bir for-döngüsü kullanabilirsiniz:

#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 << ",";
}

Ya da

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

Ne yazık ki, yazı yazarken (Ocak 2020) hiçbir büyük derleyici aralık kitaplığını uygulamaz, ancak Eric Niebler'in aralıkları-v3'e başvurabilirsiniz :

#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

Kullanıcı rend() / rbegin()yineleyiciler:

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));
}

Sonra:

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

Alternatif olarak C ++ 14'te şunları yapın:

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

C ++ 03 / 11'de çoğu standart konteynerin bir .rbegin()ve .rend()yöntemi de vardır.

Son olarak, aralık adaptörünü backwardsşu şekilde yazabilirsiniz :

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) ) };
}

ve şimdi bunu yapabilirsiniz:

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

bence oldukça güzel.



1

Yakk'in sonundaki geriye dönük yineleyiciyi seviyorum - Adam Nevraumont'un cevabı, ama ihtiyacım olan şey için karmaşık görünüyordu, ben de şunu yazdım:

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

Bunun gibi normal bir yineleyici alabilirim:

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

ve tersten yinelemek için bunu buna değiştirin:

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

1

Her yapının kullanımına izin veren ve yalnızca C ++ 14 std kitaplığına dayanan süper basit bir uygulama:

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));
}

Bu, bir rbegin () ve rend () sağlayan şeylerin yanı sıra statik dizilerle de çalışır.

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 Kitaplığını kullanabiliyorsanız, aşağıdakileri dahil ederek aralık adaptörünü sağlayan Boost.Range vardır :reverse

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

Ardından, bir C ++ 11'in aralık fordöngüsü ile birlikte aşağıdakileri yazabilirsiniz:

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

Bu kod yineleyici çiftini kullanan koddan daha kısa olduğundan, dikkat edilmesi gereken daha az ayrıntı olduğundan daha okunabilir ve hatalara daha az eğilimli olabilir.


1
Gerçekten boost::adaptors::reverseçok faydalıdır!
Kai Petzke

-1

bu kodu kullan

//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
Bu kod, vecboş bir vektöre atıfta bulunursa, korkunç şekilde başarısız olur !
Kai Petzke

-2

Uzaylı benzeri yeni C ++ sözdizimi tanıtmak istemediğimden ve sadece mevcut ilkelleri geliştirmek istediğimden, aşağıdaki parçacıklar işe yarıyor gibi görünüyor:

#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;
}

Bu kod, arrboş bir vektöre atıfta bulunursa, korkunç şekilde başarısız olur !
Kai Petzke
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.