Nehmen Sie die folgenden zwei Codezeilen:
for (int i = 0; i < some_vector.size(); i++)
{
//do stuff
}
Und das:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
some_iterator++)
{
//do stuff
}
Mir wurde gesagt, dass der zweite Weg bevorzugt wird. Warum genau ist das so?
some_iterator++
zu++some_iterator
. Nach dem Inkrementieren wird ein unnötiger temporärer Iterator erstellt.end()
in die Deklarationsklausel einbringen .vector::end
hat wahrscheinlich schlimmere Probleme als die Frage, ob sie aus Schleifen gehisst wird oder nicht. Persönlich bevorzuge ich Klarheit - wenn es ein Anruffind
in der Kündigungsbedingung wäre, würde ich mir allerdings Sorgen machen.it != vec.end()
undit != end
, sondern zwischen(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)
und liegt(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)
. Ich muss die Zeichen nicht zählen. Ziehen Sie auf jeden Fall das eine dem anderen vor, aber die Nichtübereinstimmung anderer mit Ihrer Präferenz ist nicht "schlampig", sondern eine Präferenz für einfacheren Code mit weniger Variablen und daher weniger Überlegungen beim Lesen.Antworten:
Die erste Form ist nur dann effizient, wenn vector.size () eine schnelle Operation ist. Dies gilt beispielsweise für Vektoren, nicht jedoch für Listen. Was planen Sie auch innerhalb des Loop-Körpers? Wenn Sie wie in auf die Elemente zugreifen möchten
Dann gehen Sie davon aus, dass der Container
operator[](std::size_t)
definiert wurde. Dies gilt wiederum für Vektoren, jedoch nicht für andere Container.Die Verwendung von Iteratoren bringt Sie der Containerunabhängigkeit näher . Sie machen keine Annahmen über die Direktzugriffsfähigkeit oder den schnellen
size()
Betrieb, sondern nur, dass der Container über Iteratorfunktionen verfügt.Sie können Ihren Code mithilfe von Standardalgorithmen weiter verbessern. Je nachdem , was Sie erreichen wollen, können Sie Gebrauch machen möchten
std::for_each()
,std::transform()
und so weiter. Wenn Sie einen Standardalgorithmus anstelle einer expliziten Schleife verwenden, vermeiden Sie, das Rad neu zu erfinden. Ihr Code ist wahrscheinlich effizienter (vorausgesetzt, der richtige Algorithmus wird ausgewählt), korrekt und wiederverwendbar.quelle
size_t
Mitgliedsvariable im Augesize()
.size()
für alle Container, die sie unterstützen, eine konstante zeitliche Komplexität aufweisen, einschließlichstd::list
.Es ist Teil des modernen C ++ - Indoktrinationsprozesses. Iteratoren sind die einzige Möglichkeit, die meisten Container zu iterieren. Sie verwenden sie daher auch mit Vektoren, um sich in die richtige Denkweise zu versetzen. Im Ernst, das ist der einzige Grund, warum ich es tue - ich glaube nicht, dass ich jemals einen Vektor durch eine andere Art von Container ersetzt habe.
Wow, das wird nach drei Wochen immer noch abgelehnt. Ich denke, es lohnt sich nicht, ein bisschen frech zu sein.
Ich denke, der Array-Index ist besser lesbar. Es entspricht der in anderen Sprachen verwendeten Syntax und der für altmodische C-Arrays verwendeten Syntax. Es ist auch weniger ausführlich. Effizienz sollte eine Wäsche sein, wenn Ihr Compiler gut ist, und es gibt kaum Fälle, in denen es sowieso darauf ankommt.
Trotzdem benutze ich immer noch häufig Iteratoren mit Vektoren. Ich glaube, der Iterator ist ein wichtiges Konzept, deshalb fördere ich es, wann immer ich kann.
quelle
weil Sie Ihren Code nicht an die bestimmte Implementierung der some_vector-Liste binden. Wenn Sie Array-Indizes verwenden, muss es sich um eine Art Array handeln. Wenn Sie Iteratoren verwenden, können Sie diesen Code für jede Listenimplementierung verwenden.
quelle
Stellen Sie sich vor, some_vector wird mit einer verknüpften Liste implementiert. Um dann ein Element an der i-ten Stelle anzufordern, müssen i-Operationen ausgeführt werden, um die Liste der Knoten zu durchlaufen. Wenn Sie nun im Allgemeinen den Iterator verwenden, bemüht er sich nach besten Kräften, so effizient wie möglich zu sein (im Fall einer verknüpften Liste wird ein Zeiger auf den aktuellen Knoten beibehalten und in jeder Iteration weiterentwickelt, wobei nur ein Iterator erforderlich ist Einzeloperation).
Es bietet also zwei Dinge:
quelle
Ich werde hier der Anwalt der Teufel sein und keine Iteratoren empfehlen. Der Hauptgrund dafür ist, dass der gesamte Quellcode, an dem ich von der Entwicklung von Desktop-Anwendungen bis zur Spieleentwicklung gearbeitet habe, weder Iteratoren verwendet werden musste. Die ganze Zeit, in der sie nicht benötigt wurden, und zweitens machen die versteckten Annahmen und das Durcheinander von Code und das Debuggen von Albträumen, die Sie mit Iteratoren bekommen, sie zu einem Paradebeispiel dafür, sie nicht in Anwendungen zu verwenden, die Geschwindigkeit erfordern.
Selbst vom Standpunkt der Wartung aus sind sie ein Chaos. Es liegt nicht an ihnen, sondern an all dem Aliasing, das hinter den Kulissen stattfindet. Woher weiß ich, dass Sie keine eigene virtuelle Vektor- oder Array-Liste implementiert haben, die etwas völlig anderes als die Standards tut? Weiß ich, welcher Typ derzeit zur Laufzeit ist? Haben Sie einen Operator überladen? Ich hatte keine Zeit, Ihren gesamten Quellcode zu überprüfen. Zur Hölle, weiß ich überhaupt, welche Version der STL Sie verwenden?
Das nächste Problem, das Sie mit Iteratoren haben, ist die undichte Abstraktion, obwohl es zahlreiche Websites gibt, die dies mit ihnen ausführlich besprechen.
Entschuldigung, ich habe und habe noch keinen Punkt in Iteratoren gesehen. Wenn sie die Liste oder den Vektor von Ihnen weg abstrahieren, sollten Sie tatsächlich bereits wissen, mit welchem Vektor oder welcher Liste Sie sich befassen, wenn Sie dies nicht tun, dann werden Sie sich in Zukunft nur auf einige großartige Debugging-Sitzungen einstellen.
quelle
Möglicherweise möchten Sie einen Iterator verwenden, wenn Sie dem Vektor Elemente hinzufügen / entfernen möchten, während Sie darüber iterieren.
Wenn Sie Indizes verwenden würden, müssten Sie Elemente im Array nach oben / unten mischen, um die Einfügungen und Löschungen zu verarbeiten.
quelle
std::list
Vergleich zu a jedoch ziemlich teuerstd::vector
, wenn Sie die Verwendung einer verknüpften Liste anstelle von a empfehlenstd::vector
. Siehe Seite 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Nach meiner Erfahrungstd::vector
ist a schneller als a,std::list
selbst wenn ich alles durchsuche und Elemente an beliebigen Positionen entferne.for (node = list->head; node != NULL; node = node->next)
kürzer als Ihre ersten beiden Codezeilen zusammen (Deklaration und Schleifenkopf). Also sage ich noch einmal - es gibt keinen großen grundsätzlichen Unterschied in der Kürze zwischen der Verwendung von Iteratoren und der Nichtverwendung von Iteratoren - Sie haben immer noch die drei Teile einerfor
Anweisung erfüllt , selbst wenn Siewhile
Folgendes verwenden : Deklarieren, Iterieren, Überprüfen der Beendigung.Trennung von Bedenken
Es ist sehr schön, den Iterationscode vom Kernanliegen der Schleife zu trennen. Es ist fast eine Designentscheidung.
In der Tat bindet Sie das Iterieren nach Index an die Implementierung des Containers. Wenn Sie den Container nach einem Start- und Enditerator fragen, wird der Schleifencode für die Verwendung mit anderen Containertypen aktiviert.
Außerdem sagen
std::for_each
Sie der Sammlung, was zu tun ist, anstatt sie nach ihren Interna zu fragenDer 0x-Standard wird Verschlüsse einführen, die die Verwendung dieses Ansatzes erheblich vereinfachen. Schauen Sie sich die Ausdruckskraft von z. B. Rubys
[1..6].each { |i| print i; }
...Performance
Aber vielleicht ist ein viel übersehenes Problem, dass die Verwendung des
for_each
Ansatzes die Möglichkeit bietet, die Iteration parallelisieren zu lassen - die Intel-Threading-Blöcke können den Codeblock über die Anzahl der Prozessoren im System verteilen!Hinweis: Nachdem ich die
algorithms
Bibliothek entdeckt hatte, und insbesondereforeach
, habe ich zwei oder drei Monate lang lächerlich kleine 'Helfer'-Operator-Strukturen geschrieben, die Ihre Mitentwickler verrückt machen werden. Nach dieser Zeit kehrte ich zu einem pragmatischen Ansatz zurück - kleine Schleifenkörper verdienenforeach
nichts mehr :)Ein Muss für Iteratoren ist das Buch "Extended STL" .
Die GoF haben am Ende des Iterator-Musters einen winzigen kleinen Absatz, der über diese Art der Iteration spricht. Es wird als "interner Iterator" bezeichnet. Schauen Sie auch hier vorbei .
quelle
Weil es objektorientierter ist. Wenn Sie mit einem Index iterieren, gehen Sie davon aus:
a) dass diese Objekte geordnet sind
b) dass diese Objekte durch einen Index erhalten werden können
c) dass das Indexinkrement jedes Element trifft
d) dass dieser Index bei Null beginnt
Mit einem Iterator sagen Sie "Gib mir alles, damit ich damit arbeiten kann", ohne zu wissen, was die zugrunde liegende Implementierung ist. (In Java gibt es Sammlungen, auf die über einen Index nicht zugegriffen werden kann.)
Mit einem Iterator müssen Sie sich auch keine Sorgen mehr machen, dass die Grenzen des Arrays überschritten werden.
quelle
Eine weitere nette Sache bei Iteratoren ist, dass sie es Ihnen besser ermöglichen, Ihre Konstantenpräferenz auszudrücken (und durchzusetzen). Dieses Beispiel stellt sicher, dass Sie den Vektor in der Mitte Ihrer Schleife nicht ändern:
quelle
const_iterator
. Wenn ich den Vektor in der Schleife ändere, mache ich das aus einem Grund, und in 99,9% der Fälle ist das Ändern kein Zufall, und im Übrigen ist es nur ein Fehler wie jede Art von Fehler im Code des Autors muss beheben. Da es in Java und vielen anderen Sprachen überhaupt kein const-Objekt gibt, haben Benutzer dieser Sprachen jedoch nie ein Problem ohne const-Unterstützung in diesen Sprachen.const_iterator
, was um alles in der Welt könnte der Grund sein?Abgesehen von all den anderen hervorragenden Antworten ... ist es
int
möglicherweise nicht groß genug für Ihren Vektor. Wenn Sie stattdessen die Indizierung verwenden möchten, verwenden Sie Folgendessize_type
für Ihren Container:quelle
int i
mit ausgeführt werdenmyvector.size()
.Ich sollte wahrscheinlich darauf hinweisen, dass Sie auch anrufen können
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
quelle
STL-Iteratoren sind meistens vorhanden, damit die STL-Algorithmen wie sort sortnerunabhängig sein können.
Wenn Sie nur alle Einträge in einem Vektor durchlaufen möchten, verwenden Sie einfach den Indexschleifenstil.
Es ist weniger tippend und für die meisten Menschen einfacher zu analysieren. Es wäre schön, wenn C ++ eine einfache foreach-Schleife hätte, ohne mit Template-Magie über Bord zu gehen.
quelle
Ich denke nicht, dass es für einen Vektor einen großen Unterschied macht. Ich bevorzuge es, einen Index selbst zu verwenden, da ich ihn für besser lesbar halte und Sie einen wahlfreien Zugriff ausführen können, z. B. 6 Elemente vorwärts springen oder bei Bedarf rückwärts springen.
Ich möchte auch einen Verweis auf das Element in der Schleife wie folgt machen, damit es nicht viele eckige Klammern um den Ort gibt:
Die Verwendung eines Iterators kann gut sein, wenn Sie glauben, dass Sie den Vektor irgendwann in der Zukunft durch eine Liste ersetzen müssen, und er sieht für STL-Freaks auch eleganter aus, aber ich kann mir keinen anderen Grund vorstellen.
quelle
I prefer to use an index myself as I consider it to be more readable
nur in einigen Situationen; In anderen Fällen werden Indizes schnell sehr unordentlich. Diesand you can do random access
ist überhaupt keine Besonderheit von Indizes: siehe en.cppreference.com/w/cpp/concept/RandomAccessIteratorDas zweite Formular gibt an, was Sie genauer tun. In Ihrem Beispiel interessiert Sie der Wert von i eigentlich nicht - alles, was Sie wollen, ist das nächste Element im Iterator.
quelle
Nachdem ich etwas mehr über das Thema dieser Antwort gelernt habe, stelle ich fest, dass es ein bisschen zu einfach war. Der Unterschied zwischen dieser Schleife:
Und diese Schleife:
Ist ziemlich minimal. Tatsächlich scheint mir die Syntax, Schleifen auf diese Weise zu machen, zu wachsen:
Iteratoren entsperren einige ziemlich leistungsfähige deklarative Funktionen, und in Kombination mit der STL-Algorithmusbibliothek können Sie einige ziemlich coole Dinge tun, die außerhalb des Bereichs der Array-Indexverwaltung liegen.
quelle
for (Iter it = {0}; it != end; ++it) {...}
- Sie haben die Erklärung einfach weggelassen -, also unterscheidet sich die Kürze nicht wesentlich von Ihrem zweiten Beispiel. Trotzdem +1.Die Indizierung erfordert eine zusätzliche
mul
Operation. Zum Beispielvector<int> v
konvertiert der Compilerv[i]
in&v + sizeof(int) * i
.quelle
sizeof
und es nur einmal pro Iteration hinzuzufügen, anstatt jedes Mal die gesamte Offset-Berechnung erneut durchzuführen.Während der Iteration müssen Sie nicht wissen, wie viele Elemente verarbeitet werden sollen. Sie brauchen nur das Element und Iteratoren tun solche Dinge sehr gut.
quelle
Bisher hat noch niemand erwähnt, dass ein Vorteil von Indizes darin besteht, dass sie nicht ungültig werden, wenn Sie an einen zusammenhängenden Container wie anhängen
std::vector
, sodass Sie dem Container während der Iteration Elemente hinzufügen können.Dies ist auch mit Iteratoren möglich, Sie müssen jedoch aufrufen
reserve()
und daher wissen, wie viele Elemente Sie anhängen werden.quelle
Einige gute Punkte bereits. Ich habe ein paar zusätzliche Kommentare:
Unter der Annahme, dass es sich um die C ++ - Standardbibliothek handelt, impliziert "vector" einen Direktzugriffscontainer, der die Garantien eines C-Arrays (Direktzugriff, Speicherlayout für Contiguos usw.) bietet. Wenn Sie 'some_container' gesagt hätten, wären viele der oben genannten Antworten genauer gewesen (Containerunabhängigkeit usw.).
Um Abhängigkeiten von der Compileroptimierung zu beseitigen, können Sie some_vector.size () im indizierten Code wie folgt aus der Schleife verschieben:
Iteratoren immer vorinkrementieren und Postinkremente als Ausnahmefälle behandeln.
Also vorausgesetzt und indexierbar
std::vector<>
wie ein Container , gibt es keinen guten Grund, einen anderen vorzuziehen und den Container nacheinander zu durchlaufen. Wenn Sie häufig auf ältere oder neuere Elementindizes verweisen müssen, ist die indizierte Version angemessener.Im Allgemeinen wird die Verwendung der Iteratoren bevorzugt, da Algorithmen diese verwenden und das Verhalten durch Ändern des Iteratortyps gesteuert (und implizit dokumentiert) werden kann. Array-Positionen können anstelle von Iteratoren verwendet werden, aber der syntaktische Unterschied wird auffallen.
quelle
Ich benutze keine Iteratoren aus dem gleichen Grund, aus dem ich foreach-Anweisungen nicht mag. Bei mehreren inneren Schleifen ist es schwierig genug, globale / Mitgliedsvariablen zu verfolgen, ohne sich auch alle lokalen Werte und Iteratornamen merken zu müssen. Was ich nützlich finde, ist, zwei Sätze von Indizes für verschiedene Anlässe zu verwenden:
Ich möchte Dinge wie "animation_matrices [i]" nicht einmal mit einem zufälligen "anim_matrix" -benannten Iterator abkürzen, weil Sie dann nicht klar sehen können, von welchem Array dieser Wert stammt.
quelle
it
,jt
,kt
etc. oder auch nur weiter zu verwendeni
,j
,k
etc. Und wenn Sie wissen müssen , um genau das, was ein Iterator darstellt, dann wie etwas zu mirfor (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()
wären kräftiger als ständig wie indizieren zu müssenanimation_matrices[animIndex][boneIndex]
.for
Schleife haben, was die iteratorbasierte Methode noch präziser macht.Das ist wirklich alles. Es ist nicht so, dass Sie im Durchschnitt mehr oder mehr an Kürze gewinnen, und wenn Kürze wirklich Ihr Ziel ist, können Sie jederzeit auf Makros zurückgreifen.
quelle
Wenn Sie Zugang zu haben C ++ 11 - Funktionen, dann können Sie auch eine Verwendung bereichsbasierte
for
Schleife zur Iteration über dem Vektor (oder einen anderen Behälter) wie folgt:Der Vorteil dieser Schleife besteht darin, dass Sie direkt über die
item
Variable auf Elemente des Vektors zugreifen können, ohne das Risiko einzugehen, dass ein Index durcheinander gebracht wird oder beim Dereferenzieren eines Iterators ein Fehler gemacht wird. Darüber hinausauto
verhindert der Platzhalter, dass Sie den Typ der Containerelemente wiederholen müssen, wodurch Sie einer containerunabhängigen Lösung noch näher kommen.Anmerkungen:
operator[]
für Ihren Container vorhanden ist (und für Sie schnell genug ist), gehen Sie besser Ihren ersten Weg.for
Schleife kann nicht zum Hinzufügen / Löschen von Elementen zu / aus einem Container verwendet werden. Wenn Sie das tun möchten, halten Sie sich besser an die Lösung von Brian Matthews.const
wie folgt verwenden :for (auto const &item : some_vector) { ... }
.quelle
Noch besser als "der CPU sagen, was zu tun ist" (zwingend erforderlich) ist "den Bibliotheken sagen, was Sie wollen" (funktional).
Anstatt also Schleifen zu verwenden, sollten Sie die in stl vorhandenen Algorithmen lernen.
quelle
Für die Unabhängigkeit der Container
quelle
Ich verwende immer einen Array-Index, da viele meiner Anwendungen so etwas wie "Miniaturbild anzeigen" erfordern. Also habe ich so etwas geschrieben:
quelle
Beide Implementierungen sind korrekt, aber ich würde die 'for'-Schleife bevorzugen. Da wir uns entschieden haben, einen Vektor und keinen anderen Container zu verwenden, ist die Verwendung von Indizes die beste Option. Die Verwendung von Iteratoren mit Vektoren würde den Vorteil verlieren, dass sich die Objekte in fortlaufenden Speicherblöcken befinden, was den Zugriff erleichtert.
quelle
Ich hatte das Gefühl, dass keine der Antworten hier erklärt, warum ich Iteratoren als allgemeines Konzept für die Indizierung in Containern mag. Beachten Sie, dass der größte Teil meiner Erfahrung mit Iteratoren nicht aus C ++ stammt, sondern aus übergeordneten Programmiersprachen wie Python.
Die Iterator-Schnittstelle stellt weniger Anforderungen an die Verbraucher Ihrer Funktion, sodass die Verbraucher mehr damit anfangen können.
Wenn Sie nur in der Lage sein müssen, vorwärts zu iterieren, kann der Entwickler nicht nur indizierbare Container verwenden, sondern auch jede Klasse, die implementiert
operator++(T&)
,operator*(T)
undoperator!=(const &T, const &T)
.Ihr Algorithmus funktioniert für den Fall, dass Sie ihn benötigen - er iteriert über einen Vektor -, kann aber auch für Anwendungen nützlich sein, die Sie nicht unbedingt erwarten:
Der Versuch, einen Operator in eckigen Klammern zu implementieren, der etwas Ähnliches wie dieser Iterator ausführt, wäre erfunden, während die Iteratorimplementierung relativ einfach ist. Der Operator in eckigen Klammern hat auch Auswirkungen auf die Funktionen Ihrer Klasse - die Sie auf einen beliebigen Punkt indizieren können -, deren Implementierung möglicherweise schwierig oder ineffizient ist.
Iteratoren eignen sich auch zur Dekoration . Benutzer können Iteratoren schreiben, die einen Iterator in ihren Konstruktor aufnehmen und dessen Funktionalität erweitern:
Obwohl diese Spielzeuge banal erscheinen mögen, ist es nicht schwer vorstellbar, Iteratoren und Iteratordekoratoren zu verwenden, um leistungsstarke Dinge mit einer einfachen Oberfläche zu erledigen - beispielsweise das Dekorieren eines Nur-Vorwärts-Iterators von Datenbankergebnissen mit einem Iterator, der beispielsweise ein Modellobjekt aus einem einzelnen Ergebnis erstellt . Diese Muster ermöglichen eine speichereffiziente Iteration unendlicher Mengen und mit einem Filter wie dem oben beschriebenen eine möglicherweise verzögerte Auswertung der Ergebnisse.
Ein Teil der Leistungsfähigkeit von C ++ - Vorlagen besteht darin, dass Ihre Iteratorschnittstelle, wenn sie auf C-Arrays mit fester Länge angewendet wird, in einfache und effiziente Zeigerarithmetik zerfällt , was sie zu einer wirklich kostengünstigen Abstraktion macht.
quelle