Weź następujące dwa wiersze kodu:
for (int i = 0; i < some_vector.size(); i++)
{
//do stuff
}
I to:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
some_iterator++)
{
//do stuff
}
Powiedziano mi, że preferowany jest drugi sposób. Dlaczego to dokładnie jest?
some_iterator++
na++some_iterator
. Po wykonaniu przyrostu tworzy niepotrzebny tymczasowy iterator.end()
klauzulę deklaracji.vector::end
prawdopodobnie ma gorsze problemy do zmartwienia niż to, czy jest on wyciągnięty z pętli, czy nie. Ja osobiście wolę jasność - jeślifind
byłabym wezwaniem do warunku wypowiedzenia, martwiłbym się.it != vec.end()
ait != end
jest pomiędzy(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)
i(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)
. Nie muszę liczyć postaci. Zdecydowanie wolą jedni od drugich, ale niezgodność innych ludzi z twoimi preferencjami nie jest „niechlujstwem”, jest to preferencja dla prostszego kodu z mniejszą liczbą zmiennych, a zatem mniej do przemyślenia podczas czytania.Odpowiedzi:
Pierwsza forma jest skuteczna tylko wtedy, gdy vector.size () jest szybką operacją. Odnosi się to do wektorów, ale nie do list, na przykład. Co również planujesz zrobić w ciele pętli? Jeśli planujesz uzyskać dostęp do elementów jak w
wtedy przyjmujesz założenie, że kontener
operator[](std::size_t)
zdefiniował. To samo dotyczy wektora, ale nie dotyczy innych pojemników.Zastosowanie iteratorów przybliża Cię do niezależności kontenera . Nie przyjmujesz założeń dotyczących możliwości losowego dostępu lub szybkiej
size()
operacji, tylko to, że kontener ma możliwości iteratora.Możesz dodatkowo ulepszyć swój kod, używając standardowych algorytmów. W zależności od tego, co próbujesz osiągnąć, możesz zdecydować się na użycie
std::for_each()
,std::transform()
i tak dalej. Używając standardowego algorytmu zamiast wyraźnej pętli, unikniesz ponownego wynalezienia koła. Twój kod prawdopodobnie będzie bardziej wydajny (jeśli wybrany zostanie odpowiedni algorytm), poprawny i będzie można go ponownie wykorzystać.źródło
size_t
śledził zmienną członkasize()
.size()
funkcja członka będzie musiała mieć stałą złożoność czasową dla wszystkich kontenerów, które ją obsługują, w tymstd::list
.Jest to część nowoczesnego procesu indoktrynacji w C ++. Iteratory to jedyny sposób na iterację większości pojemników, więc używasz go nawet z wektorami, aby uzyskać właściwy sposób myślenia. Poważnie, to jedyny powód, dla którego to robię - nie sądzę, żebym kiedykolwiek zastępował wektor innym rodzajem pojemnika.
Wow, po trzech tygodniach wciąż się to ocenia. Myślę, że nie opłaca się być zuchwałym.
Myślę, że indeks tablicy jest bardziej czytelny. Odpowiada składni używanej w innych językach i składni używanej w przypadku starych tablic C. Jest również mniej gadatliwy. Wydajność powinna być myta, jeśli twój kompilator jest dobry i nie ma prawie żadnych przypadków, w których ma to znaczenie.
Mimo to wciąż często używam iteratorów z wektorami. Uważam, że iterator jest ważną koncepcją, dlatego promuję go, kiedy tylko mogę.
źródło
ponieważ nie wiążesz swojego kodu z konkretną implementacją listy some_vector. jeśli używasz indeksów tablicowych, musi to być jakaś forma tablicowa; jeśli używasz iteratorów, możesz użyć tego kodu w dowolnej implementacji listy.
źródło
Wyobraź sobie, że some_vector jest zaimplementowany z połączoną listą. Następnie żądanie elementu na i-tym miejscu wymaga wykonania operacji i, aby przejść przez listę węzłów. Teraz, jeśli używasz iteratora, ogólnie mówiąc, dołoży wszelkich starań, aby być tak wydajnym, jak to możliwe (w przypadku listy połączonej utrzyma wskaźnik do bieżącego węzła i będzie go przesuwał w każdej iteracji, wymagając tylko pojedyncza operacja).
Zapewnia to dwie rzeczy:
źródło
Będę tutaj orędownikiem diabłów i nie polecam iteratorów. Głównym powodem jest to, że cały kod źródłowy, nad którym pracowałem, od tworzenia aplikacji komputerowych po tworzenie gier, nie musiałem używać iteratorów. Przez cały czas nie były one wymagane, a po drugie ukryte założenia, bałagan w kodzie i koszmary debugowania, które otrzymujesz z iteratorami, sprawiają, że są doskonałym przykładem tego, że nie można ich używać w aplikacjach wymagających szybkości.
Nawet z punktu widzenia konserwacji są bałaganem. To nie z ich powodu, ale z powodu aliasingu, który ma miejsce za sceną. Skąd mam wiedzieć, że nie zaimplementowałeś własnej listy wirtualnych wektorów lub tablic, która robi coś zupełnie innego niż standardy. Czy wiem, jaki typ jest obecnie w trakcie działania? Czy przeciążyłeś operatora? Nie miałem czasu na sprawdzenie całego kodu źródłowego. Do diabła, czy w ogóle wiem, jakiej wersji STL używasz?
Kolejnym problemem związanym z iteratorami jest nieszczelna abstrakcja, chociaż istnieje wiele stron internetowych, które szczegółowo o tym dyskutują.
Przepraszam, nie widziałem i nadal nie widziałem żadnego punktu w iteratorach. Jeśli odciągają od ciebie listę lub wektor, to w rzeczywistości powinieneś już wiedzieć, z jakim wektorem lub listą masz do czynienia, jeśli tego nie zrobisz, to po prostu przygotujesz się na wspaniałe sesje debugowania w przyszłości.
źródło
Możesz użyć iteratora, jeśli zamierzasz dodawać / usuwać elementy do wektora podczas iteracji.
Jeśli korzystasz z indeksów, będziesz musiał przetasować elementy w górę / w dół w tablicy, aby obsłużyć wstawienia i usunięcia.
źródło
std::list
porównaniu do astd::vector
, jeśli zaleca się stosowanie listy połączonej zamiaststd::vector
. Patrz strona 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Z mojego doświadczenia wynika, żestd::vector
jest szybszy niżstd::list
nawet jeśli szukam w nim wszystkich i usuwam elementy z dowolnych pozycji.for (node = list->head; node != NULL; node = node->next)
krótsza niż pierwsze dwa wiersze kodu razem (deklaracja i głowa pętli). Powtarzam więc - nie ma zasadniczej różnicy w zwięzłości między używaniem iteratorów a ich niestosowaniem - nadal spełniasz trzy częścifor
instrukcji, nawet jeśli używaszwhile
: deklaruj, iteruj, sprawdzaj zakończenie.Rozdzielenie obaw
Bardzo miło jest oddzielić kod iteracyjny od „podstawowej” kwestii pętli. To prawie decyzja projektowa.
Rzeczywiście, iteracja według indeksu wiąże cię z implementacją kontenera. Pytanie o kontener do początku i końca iteratora umożliwia włączenie kodu pętli do użycia z innymi typami kontenerów.
Ponadto, w pewnym
std::for_each
sensie, powiesz kolekcji, co robić, zamiast zadawać jej coś o jej wnętrznościachStandard 0x wprowadzi zamknięcia, które znacznie ułatwią korzystanie z tego podejścia - spójrz na ekspresyjną moc np. Ruby
[1..6].each { |i| print i; }
...Występ
Ale być może o wiele pilniejszym problemem jest to, że zastosowanie tego
for_each
podejścia daje możliwość równoległego wykonania iteracji - bloki wątków Intel mogą rozdzielić blok kodu na liczbę procesorów w systemie!Uwaga: po odkryciu
algorithms
biblioteki, a zwłaszczaforeach
, przeszedłem dwa lub trzy miesiące pisania śmiesznie małych struktur operatora pomocnika, które doprowadzą innych programistów do szaleństwa. Po tym czasie wróciłem do pragmatycznego podejścia - ciała o małej pętli nie zasługują naforeach
nic więcej :)Odwołaniem do iteratorów jest przeczytana książka „Extended STL” .
GoF ma mały akapit na końcu wzoru Iterator, który mówi o tej marce iteracji; nazywa się to „wewnętrznym iteratorem”. Spójrz też tutaj .
źródło
Ponieważ jest bardziej obiektowy. jeśli iterujesz z indeksem, zakładasz:
a) że te obiekty są uporządkowane
b) że te obiekty można uzyskać za pomocą indeksu
c) że przyrost indeksu uderzy w każdą pozycję
d) że ten indeks zaczyna się od zera
Za pomocą iteratora mówisz „daj mi wszystko, abym mógł z tym pracować”, nie wiedząc, na czym polega implementacja. (W Javie istnieją kolekcje, do których nie można uzyskać dostępu za pośrednictwem indeksu)
Ponadto dzięki iteratorowi nie musisz się martwić, że wyjdziesz poza granice tablicy.
źródło
Kolejną zaletą iteratorów jest to, że pozwalają lepiej wyrażać (i egzekwować) preferencje const. Ten przykład zapewnia, że nie zmienisz wektora w środku pętli:
źródło
const_iterator
. Jeśli zmienię wektor w pętli, robię to z jakiegoś powodu i przez 99,9% czasu zmiana nie jest przypadkowa, a reszta jest tylko błędem, jak każdy inny błąd w kodzie autora musi to naprawić. Ponieważ w Javie i wielu innych językach nie ma w ogóle obiektu const, ale użytkownicy tych języków nigdy nie mają problemu z brakiem stałej const w tych językach.const_iterator
, to czym, u licha, może być powód?Oprócz wszystkich innych doskonałych odpowiedzi ...
int
może nie być wystarczająco duży dla twojego wektora. Zamiast tego, jeśli chcesz użyć indeksowania, użyjsize_type
dla swojego kontenera:źródło
int i
domyvector.size()
.Powinienem chyba zaznaczyć, że możesz również zadzwonić
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
źródło
Iteratory STL są tam głównie, więc algorytmy STL, takie jak sort, mogą być niezależne od kontenera.
Jeśli chcesz po prostu zapętlić wszystkie wpisy w wektorze, użyj stylu pętli indeksu.
Jest mniej pisania i łatwiejsze do przeanalizowania dla większości ludzi. Byłoby miło, gdyby C ++ miał prostą pętlę foreach bez przesadzania z magią szablonów.
źródło
Nie sądzę, żeby miało to duże znaczenie dla wektora. Wolę sam korzystać z indeksu, ponieważ uważam, że jest on bardziej czytelny i możesz uzyskać dostęp przypadkowy, np. Przeskoczyć do przodu o 6 pozycji lub w razie potrzeby skoczyć do tyłu.
Chciałbym również odnieść się do elementu wewnątrz pętli w ten sposób, aby wokół tego miejsca nie było dużo nawiasów kwadratowych:
Korzystanie z iteratora może być dobre, jeśli uważasz, że w pewnym momencie w przyszłości może być konieczne zastąpienie wektora listą, a także wygląda bardziej stylowo dla maniaków STL, ale nie mogę wymyślić żadnego innego powodu.
źródło
I prefer to use an index myself as I consider it to be more readable
tylko w niektórych sytuacjach; w innych indeksy szybko stają się bardzo nieuporządkowane.and you can do random access
co wcale nie jest unikalną cechą indeksów: patrz en.cppreference.com/w/cpp/concept/RandomAccessIteratorDruga forma reprezentuje to, co robisz dokładniej. W twoim przykładzie tak naprawdę nie zależy ci na wartości i - wszystko, czego chcesz, to następny element w iteratorze.
źródło
Po tym, jak dowiedziałem się nieco więcej na temat tej odpowiedzi, zdaję sobie sprawę, że było to trochę nadmierne uproszczenie. Różnica między tą pętlą:
I ta pętla:
Jest dość minimalny. W rzeczywistości wydaje mi się, że składnia wykonywania pętli w ten sposób:
Iteratory odblokowują niektóre dość potężne funkcje deklaratywne, a w połączeniu z biblioteką algorytmów STL możesz robić całkiem fajne rzeczy, które są poza zakresem administrowania indeksem tablic.
źródło
for (Iter it = {0}; it != end; ++it) {...}
- właśnie pominąłeś deklarację - więc zwięzłość niewiele różni się od twojego drugiego przykładu. Nadal +1.Indeksowanie wymaga dodatkowej
mul
operacji. Na przykładvector<int> v
kompilator konwertujev[i]
na&v + sizeof(int) * i
.źródło
sizeof
i po prostu dodać go raz na iterację, zamiast wykonywać ponownie obliczenia całego przesunięcia za każdym razem.Podczas iteracji nie musisz znać liczby elementów do przetworzenia. Potrzebujesz tego przedmiotu, a iteratory robią takie rzeczy bardzo dobrze.
źródło
Nikt jeszcze nie wspominał, że jedną z zalet indeksów jest to, że nie stają się one nieważne, gdy dodasz do ciągłego pojemnika, takiego jak
std::vector
, dzięki czemu możesz dodawać elementy do kontenera podczas iteracji.Jest to również możliwe w przypadku iteratorów, ale musisz zadzwonić
reserve()
, a zatem musisz wiedzieć, ile elementów dołączysz.źródło
Kilka dobrych punktów już. Mam kilka dodatkowych komentarzy:
Zakładając, że mówimy o standardowej bibliotece C ++, „wektor” oznacza kontener o swobodnym dostępie, który ma gwarancje macierzy C (losowy dostęp, układ pamięci contiguos itp.). Gdybyś powiedział „some_container”, wiele z powyższych odpowiedzi byłoby dokładniejszych (niezależność kontenera itp.).
Aby wyeliminować jakiekolwiek zależności związane z optymalizacją kompilatora, możesz przenieść some_vector.size () z pętli w indeksowanym kodzie, tak jak:
Zawsze iteratory wstępnej inkrementacji i traktuj post-inkrementy jako wyjątkowe przypadki.
Więc zakładając i indeksowalne
std::vector<>
jak kontener, nie ma żadnego powodu, aby preferować jeden nad drugim, przechodząc kolejno przez kontener. Jeśli musisz często odwoływać się do starszych lub nowszych elemenentnych indeksów, wówczas indeksowana wersja jest bardziej odpowiednia.Ogólnie rzecz biorąc, preferowane jest używanie iteratorów, ponieważ algorytmy je wykorzystują, a zachowanie może być kontrolowane (i domyślnie dokumentowane) poprzez zmianę typu iteratora. Lokalizacje tablic mogą być używane zamiast iteratorów, ale różnica składniowa będzie się utrzymywać.
źródło
Nie używam iteratorów z tego samego powodu, dla którego nie lubię instrukcji foreach. Mając wiele wewnętrznych pętli, trudno jest śledzić zmienne globalne / składowe bez konieczności pamiętania wszystkich lokalnych wartości i nazw iteratorów. Za użyteczne uważam użycie dwóch zestawów wskaźników na różne okazje:
Nie chcę nawet skracać rzeczy takich jak „animation_matrices [i]” do jakiegoś losowego „iteratora” o nazwie anim_matrix, ponieważ wtedy nie możesz wyraźnie zobaczyć, z której tablicy pochodzi ta wartość.
źródło
it
,jt
,kt
, itd., A nawet po prostu kontynuować używaniei
,j
,k
, itd. A jeśli trzeba wiedzieć dokładnie, co iterator reprezentuje, to dla mnie coś takiegofor (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()
byłoby bardziej opisowe niż ciągłe indeksowanie jakanimation_matrices[animIndex][boneIndex]
.for
pętlę opartą na zakresie , co sprawia, że iteratorowy sposób robienia tego jest jeszcze bardziej zwięzły.Naprawdę, to wszystko. To nie tak, że średnio zyskasz więcej zwięzłości w każdym kierunku, a jeśli zwięzłość naprawdę jest twoim celem, zawsze możesz polegać na makrach.
źródło
Jeśli masz dostęp do funkcji C ++ 11 , możesz także użyć pętli opartej
for
na zakresie do iteracji po wektorze (lub dowolnym innym kontenerze) w następujący sposób:Zaletą tej pętli jest to, że można uzyskać dostęp do elementów wektora bezpośrednio przez
item
zmienną, bez ryzyka zepsucia indeksu lub popełnienia błędu przy dereferencji iteratora. Ponadto symbol zastępczyauto
zapobiega powtórzeniu rodzaju elementów kontenera, co jeszcze bardziej zbliża Cię do rozwiązania niezależnego od kontenera.Uwagi:
operator[]
istnieje on dla twojego kontenera (i jest dla ciebie wystarczająco szybki), lepiej idź pierwszy.for
Pętla oparta na zakresie nie może być używana do dodawania / usuwania elementów do / z kontenera. Jeśli chcesz to zrobić, lepiej trzymaj się rozwiązania zaproponowanego przez Briana Matthewsa.const
w następujący sposób:for (auto const &item : some_vector) { ... }
.źródło
Jeszcze lepsze niż „mówienie procesorowi, co ma robić” (imperatyw) jest „mówienie bibliotekom, co chcesz” (funkcjonalne).
Więc zamiast używać pętli powinieneś nauczyć się algorytmów obecnych w stl.
źródło
Dla niezależności kontenera
źródło
Zawsze używam indeksu tablic, ponieważ wiele moich aplikacji wymaga czegoś takiego jak „wyświetl miniaturę”. Więc napisałem coś takiego:
źródło
Obie implementacje są poprawne, ale wolałbym pętlę „for”. Ponieważ zdecydowaliśmy się na użycie Vectora, a nie innego kontenera, najlepszym rozwiązaniem byłoby użycie indeksów. Używanie iteratorów z wektorami straciłoby bardzo korzyść z posiadania obiektów w ciągłych blokach pamięci, które ułatwiają ich dostęp.
źródło
Czułem, że żadna z odpowiedzi tutaj nie wyjaśnia, dlaczego lubię iteratory jako ogólną koncepcję nad indeksowaniem do kontenerów. Zauważ, że większość mojego doświadczenia z iteratorami nie pochodzi z C ++, ale z języków programowania wyższego poziomu, takich jak Python.
Interfejs iteratora nakłada mniej wymagań na użytkowników twojej funkcji, co pozwala im robić więcej.
Jeśli wszystko, co potrzebne jest, aby być w stanie przekazać powtórzyć, deweloper nie ogranicza się do korzystania z płytek z węglików pojemniki - mogą używać klasę wykonawczą
operator++(T&)
,operator*(T)
ioperator!=(const &T, const &T)
.Twój algorytm działa w przypadku, gdy go potrzebujesz - iterując po wektorze - ale może być również przydatny w aplikacjach, których niekoniecznie przewidujesz:
Próba zaimplementowania operatora w nawiasach kwadratowych, który robi coś podobnego do tego iteratora, byłaby wymyślona, podczas gdy implementacja iteratora jest stosunkowo prosta. Operator nawiasów kwadratowych ma również wpływ na możliwości twojej klasy - które możesz indeksować w dowolnym dowolnym punkcie - co może być trudne lub nieefektywne.
Iteratory nadają się również do dekoracji . Ludzie mogą pisać iteratory, które biorą iterator w swoim konstruktorze i rozszerzają jego funkcjonalność:
Chociaż te zabawki mogą wydawać się przyziemne, nietrudno wyobrazić sobie używanie iteratorów i dekoratorów iteratorów do robienia potężnych rzeczy za pomocą prostego interfejsu - dekorowanie iteratora wyników bazy danych tylko do przodu za pomocą iteratora, który konstruuje obiekt modelowy z jednego wyniku, na przykład . Wzorce te umożliwiają wydajną pamięć nieskończonej liczby zestawów nieskończonych i, z filtrem takim jak ten, który napisałem powyżej, potencjalnie leniwą ocenę wyników.
Częścią potęgi szablonów C ++ jest interfejs iteratora, gdy zastosowany do takich jak tablice C o stałej długości, rozpada się na prostą i wydajną arytmetykę wskaźników , dzięki czemu jest to naprawdę zero-kosztowa abstrakcja.
źródło