Przejdź przez wektor C ++, używając pętli „for”


140

Jestem nowy w języku C ++. Zacząłem używać wektorów i zauważyłem, że w całym kodzie, który widzę, aby iterować przez wektor za pomocą indeksów, pierwszy parametr forpętli jest zawsze oparty na wektorze. W Javie mógłbym zrobić coś takiego z ArrayList:

for(int i=0; i < vector.size(); i++){
   vector[i].doSomething();
}

Czy jest jakiś powód, dla którego nie widzę tego w C ++? Czy to zła praktyka?


1
Pętla for nie jest funkcją, więc nie ma parametrów (ani argumentów, które są przekazywane). Czy masz na myśli coś takiego std::vector<int>::size_type i = 0;, czy może std::vector<int>::iterator it = vector.begin();?
chris

Dokładnie, wszystkie przykłady, które widzę, są tak napisane.
Flynn

4
W Javie wolałbym pętlę for-each lub używać iteratorów. Prawie taki sam jak C ++, chociaż nieco inna składnia.
Jesse Good


10
Większość odpowiedzi tutaj błędnie zakłada, że ​​Q brzmistd::vector : Jaka jest najlepsza / najkrótsza droga iteracji ? , pytanie, które jest tutaj, brzmi: Czy jest jakiś powód, dla którego nie widzę tego w C ++? Czy to zła praktyka? aka Dlaczego zawsze widzę kod w C ++, który używa iteratorów podczas iteracji std::vector?
Alok Zapisz

Odpowiedzi:


92

Czy jest jakiś powód, dla którego nie widzę tego w C ++? Czy to zła praktyka?

Nie. Nie jest to zła praktyka, ale następujące podejście zapewnia Twojemu kodowi pewną elastyczność .

Zwykle kod przed C ++ 11 do iteracji po elementach kontenera używa iteratorów, na przykład:

std::vector<int>::iterator it = vector.begin();

Dzieje się tak, ponieważ sprawia, że ​​kod jest bardziej elastyczny.

Wszystkie standardowe kontenery bibliotek obsługują i zapewniają iteratory. Jeśli w późniejszym etapie rozwoju będziesz musiał przełączyć się na inny kontener, to ten kod nie musi być zmieniany.

Uwaga: Pisanie kodu, który działa z każdym możliwym kontenerem biblioteki standardowej, nie jest tak łatwe, jak mogłoby się wydawać.


25
Czy ktoś mógłby mi wyjaśnić, dlaczego w tym konkretnym przypadku / fragmencie kodu radzisz iteratorom zamiast indeksowania? O czym jest ta „elastyczność”, o której mówisz? Osobiście nie lubię iteratorów, powiększają kod - po prostu więcej znaków do wpisania dla tego samego efektu. Zwłaszcza jeśli nie możesz użyć auto.
Violet Giraffe

8
@VioletGiraffe: Podczas korzystania z iteratorów trudno jest pomylić się z niektórymi przypadkami, takimi jak puste zakresy, a kod jest bardziej rozwlekły. Oczywiście jest to kwestia lub percepcja i wybór, więc można o tym dyskutować bez końca.
Alok Zapisz

9
Dlaczego pokazujesz tylko, jak zadeklarować iterator, ale nie pokazujesz, jak go użyć do wykonania pętli ...?
underscore_d

116

Powód, dla którego nie widzisz takiej praktyki, jest dość subiektywny i nie może mieć jednoznacznej odpowiedzi, ponieważ widziałem wiele kodów, które używają wspomnianego sposobu, a nie iteratorkodu stylu.

Oto przyczyny, dla których ludzie nie zastanawiają się nad vector.size()sposobem zapętlenia:

  1. Paranoicznie dzwonić za size()każdym razem w stanie pętli. Jednak albo nie jest to problem, albo można go w trywialny sposób naprawić
  2. Wolę std::for_each()od forsamej pętli
  3. Później zmienia pojemnik z std::vectordo drugiego (np map, list) będzie także żądać zmiany mechanizmu pętli, ponieważ nie każdy pojemnik wsparcie size()styl zapętlenie

C ++ 11 zapewnia dobrą możliwość poruszania się po kontenerach. Nazywa się to „pętlą opartą na zakresie dla” (lub „pętlą rozszerzoną dla” w języku Java).

Za pomocą małego kodu możesz przejść przez pełne (obowiązkowe!) std::vector:

vector<int> vi;
...
for(int i : vi) 
  cout << "i = " << i << endl;

12
Wystarczy zwrócić uwagę na małą wadę zakresu opartego na pętli : nie można go używać z #pragma omp parallel for.
liborm

2
Podoba mi się wersja kompaktowa, ponieważ jest mniej kodu do odczytania. Po dostosowaniu mentalnym znacznie łatwiej jest zrozumieć, a błędy bardziej się wyróżniają. Sprawia to również, że jest to znacznie bardziej oczywiste, gdy ma miejsce niestandardowa iteracja, ponieważ istnieje znacznie większy fragment kodu.
Code Abominator

87

Najczystszym sposobem iteracji po wektorze są iteratory:

for (auto it = begin (vector); it != end (vector); ++it) {
    it->doSomething ();
}

lub (odpowiednik powyższego)

for (auto & element : vector) {
    element.doSomething ();
}

Przed C ++ 0x musisz zamienić auto na typ iteratora i używać funkcji składowych zamiast funkcji globalnych begin i end.

To prawdopodobnie to, co widziałeś. W porównaniu z podejściem, o którym wspomniałeś, zaletą jest to, że nie zależy ci w dużym stopniu od rodzaju vector. Jeśli zmienisz vectorklasę na inną „typ kolekcji”, Twój kod prawdopodobnie nadal będzie działał. Możesz jednak zrobić coś podobnego również w Javie. Nie ma dużej różnicy koncepcyjnej; C ++ używa jednak szablonów do zaimplementowania tego (w porównaniu do typów ogólnych w Javie); stąd podejście będzie działać dla wszystkich typów, dla których zdefiniowano begini endfunkcje, nawet dla typów niebędących klasami, takich jak tablice statyczne. Zobacz tutaj: Jak działa na podstawie zakresu dla zwykłych tablic?


5
auto, free begin / end to także C ++ 11. Powinieneś także używać ++ it zamiast tego ++ w wielu przypadkach.
ForEveR

Tak, masz rację. Wdrażanie begini jest endjednak jednowierszowe.
JohnB

@JohnB to więcej niż jedna linijka, ponieważ działa również dla tablic o stałym rozmiarze. autoz drugiej strony byłoby dość trudne.
juanchopanza

Jeśli potrzebujesz go tylko do wektora, jest to jednolinijkowy.
JohnB

Mimo to, pierwszy przykład jest mylący, ponieważ nie może działać w C ++ 03, podczas gdy twoje sformułowanie sugeruje, że tak.
juanchopanza

35

Właściwy sposób to:

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    it->doSomething();
 }

Gdzie T jest typem klasy wewnątrz wektora. Na przykład, jeśli klasa była CActivity, po prostu napisz CActivity zamiast T.

Ten typ metody będzie działał na każdym STL (nie tylko wektorach, co jest trochę lepsze).

Jeśli nadal chcesz używać indeksów, sposób jest następujący:

for(std::vector<T>::size_type i = 0; i != v.size(); i++) {
    v[i].doSomething();
}

nie std::vector<T>::size_typezawsze size_t? To jest typ, którego zawsze do tego używam.
Violet Giraffe

1
@VioletGiraffe Jestem prawie pewien, że masz rację (tak naprawdę nie sprawdziłem), ale lepszą praktyką jest użycie std :: vector <T> :: size_type.
DiGMi

8

Istnieje kilka ważnych powodów, dla których warto używać iteratorów, z których niektóre zostały wymienione tutaj:

Późniejsza zmiana kontenerów nie powoduje unieważnienia kodu.

tj. jeśli przechodzisz od std :: vector do std :: list lub std :: set, nie możesz użyć indeksów numerycznych, aby uzyskać zawartą w niej wartość. Używanie iteratora jest nadal ważne.

Przechwytywanie nieprawidłowej iteracji w czasie wykonywania

Jeśli zmodyfikujesz kontener w środku pętli, następnym razem, gdy użyjesz iteratora, zgłosi on nieprawidłowy wyjątek iteratora.


1
czy możesz wskazać jakiś artykuł / post, który wyjaśnia powyższe punkty za pomocą przykładowego kodu? byłoby wspaniale! lub gdybyś mógł dodać :)
Anu

5

Zaskoczyło mnie, że nikt nie wspomniał, że iteracja przez tablicę z indeksem całkowitoliczbowym ułatwia pisanie błędnego kodu poprzez indeksowanie tablicy z niewłaściwym indeksem. Na przykład, jeśli masz zagnieżdżone pętle używające ii jjako indeksów, możesz nieprawidłowo indeksować tablicę jzamiast, ia tym samym wprowadzić błąd do programu.

Z drugiej strony inne wymienione tutaj formy, a mianowicie forpętla oparta na zakresie i iteratory, są znacznie mniej podatne na błędy. Semantyka języka i mechanizm sprawdzania typu kompilatora zapobiegają przypadkowemu dostępowi do tablicy przy użyciu niewłaściwego indeksu.


4

W przypadku STL programiści używają iteratorsdo przechodzenia przez kontenery, ponieważ iterator jest abstrakcyjną koncepcją zaimplementowaną we wszystkich standardowych kontenerach. Na przykład w std::listogóle nie ma operator [].


3

Korzystanie z operatora auto naprawdę ułatwia korzystanie, ponieważ nie trzeba się martwić o typ danych i rozmiar wektora ani żadnej innej struktury danych

Iterowanie wektora przy użyciu pętli auto i for

vector<int> vec = {1,2,3,4,5}

for(auto itr : vec)
    cout << itr << " ";

Wynik:

1 2 3 4 5

Możesz również użyć tej metody do iteracji zestawów i list. Korzystanie z opcji auto automatycznie wykrywa typ danych używany w szablonie i umożliwia jego użycie. Tak więc, nawet gdybyśmy mieli składnię vectorof stringlub chartę samą, będzie działać dobrze


1

Prawidłowy sposób iteracji pętli i wydrukowania jej wartości jest następujący:

#include<vector>

//declare the vector of type int
vector<int> v;

//insert the 5 element in the vector
for ( unsigned int i = 0; i < 5; i++){
    v.push_back(i);
}

//print those element
for (auto it = 0; it < v.end(); i++){
    std::cout << *it << std::endl;
}

1

Oto prostszy sposób iteracji i drukowania wartości w wektorze.

for(int x: A) // for integer x in vector A
    cout<< x <<" "; 

0
 //different declaration type
    vector<int>v;  
    vector<int>v2(5,30); //size is 5 and fill up with 30
    vector<int>v3={10,20,30};
    
    //From C++11 and onwards
    for(auto itr:v2)
        cout<<"\n"<<itr;
     
     //(pre c++11)   
    for(auto itr=v3.begin(); itr !=v3.end(); itr++)
        cout<<"\n"<<*itr;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.