it-swarm.com.de

Iteratorschleife gegen Indexschleife

Mögliches Duplikat:
Warum Iteratoren anstelle von Array-Indizes verwenden?

Ich überprüfe mein Wissen über C++ und bin auf Iteratoren gestoßen. Ich möchte wissen, was sie so besonders macht und warum:

using namespace std;

vector<int> myIntVector;
vector<int>::iterator myIntVectorIterator;

// Add some elements to myIntVector
myIntVector.Push_back(1);
myIntVector.Push_back(4);
myIntVector.Push_back(8);

for(myIntVectorIterator = myIntVector.begin(); 
        myIntVectorIterator != myIntVector.end();
        myIntVectorIterator++)
{
    cout<<*myIntVectorIterator<<" ";
    //Should output 1 4 8
}

ist besser als das:

using namespace std;

vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.Push_back(1);
myIntVector.Push_back(4);
myIntVector.Push_back(8);

for(int y=0; y<myIntVector.size(); y++)
{
    cout<<myIntVector[y]<<" ";
    //Should output 1 4 8
}

Und ja, ich weiß, dass ich den std-Namespace nicht verwenden sollte. Ich habe dieses Beispiel gerade von der Programmier-Website genommen. Kannst du mir bitte sagen, warum letzteres schlimmer ist? Was ist der große Unterschied?

94
CodingMadeEasy

Das Besondere an Iteratoren ist, dass sie die Verbindung zwischen Algorithmen und Containern herstellen. Für generischen Code wäre die Empfehlung, eine Kombination von STL-Algorithmen (z. B. find, sort, remove, copy) usw. zu verwenden, die ausgeführt werden die Berechnung, die Sie für Ihre Datenstruktur im Auge haben (vector, list, map usw.), und diesen Algorithmus mit Iteratoren in Ihren Container zu liefern.

Ihr spezielles Beispiel könnte als eine Kombination aus dem Algorithmus for_each Und dem Container vector (siehe Option 3) geschrieben werden. Es ist jedoch nur eine von vier verschiedenen Möglichkeiten, einen Standard zu durchlaufen: :Vektor:

1) indexbasierte Iteration

for (std::size_t i = 0; i != v.size(); ++i) {
    // access element as v[i]

    // any code including continue, break, return
}

Vorteile: Jeder, der mit C-Code vertraut ist, kann eine Schleife mit verschiedenen Schritten ausführen (z. B. i += 2).

Nachteile: nur für sequentielle Direktzugriffscontainer (vector, array, deque), funktioniert nicht für list, forward_list Oder die assoziativen Container. Auch die Schleifensteuerung ist ein wenig ausführlich (init, check, increment). Die Leute müssen sich der 0-basierten Indizierung in C++ bewusst sein.

2) Iterator-basierte Iteration

for (auto it = v.begin(); it != v.end(); ++it) {
    // if the current index is needed:
    auto i = std::distance(v.begin(), it); 

    // access element as *it

    // any code including continue, break, return
}

Vorteile: generischer, funktioniert für alle Container (selbst die neuen ungeordneten assoziativen Container können auch unterschiedliche Schritte verwenden (z. B. std::advance(it, 2));

Nachteile: Benötige zusätzliche Arbeit, um den Index des aktuellen Elements zu erhalten (könnte O(N) für list oder forward_list sein). Auch hier ist die Schleifensteuerung ein wenig ausführlich (Init, Check, Inkrement).

3) AWL für jeden Algorithmus + Lambda

std::for_each(v.begin(), v.end(), [](T const& elem) {
     // if the current index is needed:
     auto i = &elem - &v[0];

     // cannot continue, break or return out of the loop
});

Vorteile: Wie bei 2) plus geringer Reduzierung der Schleifensteuerung (keine Überprüfung und Inkrementierung) kann dies die Fehlerrate erheblich reduzieren (falsches Starten, Überprüfen oder Inkrementieren, Fehler um eins). .

Nachteile: Entspricht der expliziten Iterator-Schleife plus eingeschränkten Möglichkeiten zur Flusskontrolle in der Schleife (kann nicht Continue, Break oder Return verwenden) und keine Option für verschiedene Schritte (es sei denn, Sie verwenden einen überlasteten Iterator-Adapter) operator++).

4) Range-for-Schleife

for (auto& elem: v) {
     // if the current index is needed:
     auto i = &elem - &v[0];

    // any code including continue, break, return
}

Vorteile: sehr kompakte Schleifensteuerung, direkter Zugriff auf das aktuelle Element.

Nachteile: zusätzliche Anweisung, um den Index zu erhalten. Kann nicht verschiedene Schritte verwenden.

Was ist zu verwenden?

Für Ihr spezielles Beispiel für die Iteration über std::vector: Wenn Sie den Index wirklich benötigen (z. B. auf das vorherige oder nächste Element zugreifen, den Index innerhalb der Schleife drucken/protokollieren usw.) oder einen anderen Schritt als 1 benötigen Ich würde mich für die explizit indizierte Schleife entscheiden, sonst würde ich mich für die Range-for-Schleife entscheiden.

Für generische Algorithmen für generische Container würde ich die explizite Iteratorschleife verwenden, es sei denn, der Code enthält keine Flusskontrolle in der Schleife und benötigt Schritt 1. In diesem Fall würde ich die STL for_each + Ein Lambda verwenden.

162
TemplateRex

Durch Iteratoren wird der Code allgemeiner.
Jeder Standard-Bibliothekscontainer verfügt über einen Iterator. Wenn Sie also in Zukunft Ihre Containerklasse ändern, wird die Schleife nicht beeinflusst.

9
Alok Save

Iteratoren haben Vorrang vor operator[]. C++ 11 bietet die Funktionen std::begin(), std::end().

Da Ihr Code nur std::vector Verwendet, kann ich nicht sagen, dass es einen großen Unterschied zwischen beiden Codes gibt, jedoch funktioniert operator [] Möglicherweise nicht so, wie Sie es beabsichtigen. Wenn Sie beispielsweise eine Karte verwenden, fügt operator[] Ein Element ein, wenn es nicht gefunden wird.

Durch die Verwendung von iterator wird Ihr Code zwischen Containern portabler. Sie können Container von std::vector Zu std::list Oder anderen Containern frei wechseln, ohne viel zu ändern, wenn Sie den Iterator verwenden. Diese Regel gilt nicht für operator[].

7
billz

Mit einem Vektor bieten Iteratoren keinen wirklichen Vorteil. Die Syntax ist hässlicher, länger zu tippen und schwerer zu lesen.

Das Iterieren über einen Vektor mit Iteratoren ist nicht schneller und nicht sicherer (wenn die Größe des Vektors möglicherweise während der Iteration mit Iteratoren geändert wird, treten große Probleme auf).

Die Idee, eine generische Schleife zu haben, die funktioniert, wenn Sie später den Containertyp ändern, ist auch in realen Fällen meistens Unsinn. Leider besteht die Schattenseite einer streng getippten Sprache ohne ernsthaften Tippfehler (jetzt mit C++ 11 jedoch etwas besser) darin, dass Sie bei jedem Schritt die Art von allem angeben müssen. Wenn Sie später Ihre Meinung ändern, müssen Sie immer noch alles ändern. Darüber hinaus haben unterschiedliche Container sehr unterschiedliche Kompromisse, und der Wechsel des Containertyps kommt nicht so häufig vor.

Der einzige Fall, in dem die Iteration nach Möglichkeit generisch beibehalten werden sollte, ist das Schreiben von Vorlagencode, aber dies ist (wie ich hoffe für Sie) nicht der häufigste Fall.

Das einzige Problem in Ihrer expliziten Indexschleife besteht darin, dass size einen vorzeichenlosen Wert zurückgibt (ein Designfehler von C++) und der Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen Werten gefährlich und überraschend ist, um dies besser zu vermeiden. Wenn Sie einen anständigen Compiler mit aktivierten Warnungen verwenden, sollte dies diagnostiziert werden.

Beachten Sie, dass die Lösung darin besteht, kein unsiged als Index zu verwenden, da die Arithmetik zwischen vorzeichenlosen Werten ebenfalls offensichtlich unlogisch ist (es handelt sich um eine Modulo-Arithmetik, und x-1 Kann größer als x sein). Sie sollten die Größe stattdessen in eine Ganzzahl umwandeln, bevor Sie sie verwenden. Es ist may sinnvoll, vorzeichenlose Größen und Indizes (wobei Sie jedem Ausdruck, den Sie schreiben, viel Aufmerksamkeit schenken) nur dann zu verwenden, wenn Sie an einer 16 - Bit - C++ - Implementierung arbeiten ( 16 Bit war der Grund für vorzeichenlose Werte in Größen ).

Ein typischer Fehler, den die Größe ohne Vorzeichen mit sich bringen kann, ist:

void drawPolyline(const std::vector<P2d>& points)
{
    for (int i=0; i<points.size()-1; i++)
        drawLine(points[i], points[i+1]);
}

Hier ist der Fehler vorhanden, da bei Übergabe eines leeren Vektors points der Wert points.size()-1 eine große positive Zahl ist, die Sie in eine Schleife zu einem Segfault führt. Eine funktionierende Lösung könnte sein

for (int i=1; i<points.size(); i++)
    drawLine(points[i - 1], points[i]);

aber ich persönlich ziehe es vor, immer unsinged- ness mit int(v.size()) zu entfernen.

Die Hässlichkeit der Verwendung von Iteratoren in diesem Fall wird dem Leser als Übung überlassen.

5
6502

Es kommt immer darauf an, was Sie brauchen.

Du solltest benutzen operator[] wenn Sie benötigen direkten Zugriff auf Elemente im Vektor (wenn Sie ein bestimmtes Element im Vektor indizieren müssen). Es ist nichts Falsches daran, es über Iteratoren zu verwenden. Sie müssen jedoch selbst entscheiden, welche (operator[] oder Iteratoren) entspricht am besten Ihren Anforderungen.

Durch die Verwendung von Iteratoren können Sie zu anderen Containertypen wechseln, ohne den Code wesentlich zu ändern. Mit anderen Worten: Durch die Verwendung von Iteratoren wird der Code allgemeiner und hängt nicht von einem bestimmten Containertyp ab.

4
Mark Garcia

Indem Sie Ihren Client-Code in Form von Iteratoren schreiben, abstrahieren Sie den Container vollständig.

Betrachten Sie diesen Code:

class ExpressionParser // some generic arbitrary expression parser
{
public:
    template<typename It>
    void parse(It begin, const It end)
    {
        using namespace std;
        using namespace std::placeholders;
        for_each(begin, end, 
            bind(&ExpressionParser::process_next, this, _1);
    }
    // process next char in a stream (defined elsewhere)
    void process_next(char c);
};

kundencode:

ExpressionParser p;

std::string expression("SUM(A) FOR A in [1, 2, 3, 4]");
p.parse(expression.begin(), expression.end());

std::istringstream file("expression.txt");
p.parse(std::istringstream<char>(file), std::istringstream<char>());

char expr[] = "[12a^2 + 13a - 5] with a=108";
p.parse(std::begin(expr), std::end(expr));

Bearbeiten: Betrachten Sie Ihr ursprüngliches Codebeispiel, implementiert mit:

using namespace std;

vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.Push_back(1);
myIntVector.Push_back(4);
myIntVector.Push_back(8);

copy(myIntVector.begin(), myIntVector.end(), 
    std::ostream_iterator<int>(cout, " "));
1
utnapistim

Das Schöne am Iterator ist, dass Sie später Ihren Vektor auf einen anderen STD-Container umstellen wollten. Dann funktioniert die forloop noch.

0
Caesar