Wenn Sie einen C++11-Compiler haben, würde ich vorschlagen, eine bereichsbasierte for-Schleife zu verwenden (siehe unten); oder verwenden Sie einen Iterator. Sie haben jedoch mehrere Möglichkeiten, die ich im Folgenden alle erläutern werde.
Bereichsbezogene for-Schleife (C++11)
In C++11 (und später) können Sie die neue bereichsbasierte for-Schleife verwenden, die wie folgt aussieht:
std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';
Der Typ char
in der for-Schleife sollte der Typ der Elemente des Vektors sein path
und nicht ein Integer-Indexierungstyp. Mit anderen Worten, da path
ist vom Typ std::vector<char>
ist der Typ, der in der bereichsbasierten for-Schleife erscheinen sollte char
. Allerdings werden Sie wahrscheinlich oft sehen, dass der explizite Typ durch das auto
Platzhaltertyp:
for (auto i: path)
std::cout << i << ' ';
Unabhängig davon, ob Sie den expliziten Typ oder die auto
Schlüsselwort, das Objekt i
hat einen Wert, der eine Kopie des tatsächlichen Elements in der path
Objekt. Daher werden alle Änderungen an i
in der Schleife werden nicht in path
selbst:
std::vector<char> path{'a', 'b', 'c'};
for (auto i: path) {
i = '_'; // 'i' is a copy of the element in 'path', so although
// we can change 'i' here perfectly fine, the elements
// of 'path' have not changed
std::cout << i << ' '; // will print: "_ _ _"
}
for (auto i: path) {
std::cout << i << ' '; // will print: "a b c"
}
Wenn Sie verbieten möchten, dass dieser kopierte Wert von i
in der for-Schleife können Sie auch den Typ der i
zu sein const char
wie diese:
for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}
Wenn Sie die Einträge in path
so dass diese Änderungen in der Zukunft bestehen bleiben path
außerhalb der for-Schleife, dann können Sie einen Verweis wie folgt verwenden:
for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}
und auch wenn Sie nicht ändern wollen path
Wenn das Kopieren von Objekten teuer ist, sollten Sie eine Konst-Referenz verwenden, anstatt nach Wert zu kopieren:
for (const auto& i: path)
std::cout << i << ' ';
Iteratoren
Vor C++11 wäre die kanonische Lösung die Verwendung eines Iterators gewesen, und das ist immer noch vollkommen akzeptabel. Sie werden wie folgt verwendet:
std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Wenn Sie den Inhalt des Vektors in der for-Schleife ändern wollen, dann verwenden Sie iterator
statt const_iterator
.
Ergänzung: typedef / type alias (C++11) / auto (C++11)
Dies ist keine weitere Lösung, sondern eine Ergänzung zu den oben genannten iterator
Lösung. Wenn Sie den C++11-Standard (oder höher) verwenden, können Sie die auto
Schlüsselwort, um die Lesbarkeit zu verbessern:
for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Hier wird die Art der i
wird nicht konstant sein (d.h. der Compiler verwendet std::vector<char>::iterator
als die Art der i
). Dies liegt daran, dass wir die begin
Methode, so dass der Compiler den Typ für i
davon. Wenn wir die cbegin
Methode stattdessen ("c" für const), dann i
wird ein std::vector<char>::const_iterator
:
for (auto i = path.cbegin(); i != path.cend(); ++i) {
*i = '_'; // will produce a compiler error
std::cout << *i << ' ';
}
Wenn Sie nicht damit einverstanden sind, dass der Compiler Typen ableitet, können Sie in C++11 einen Typ-Alias verwenden, um zu vermeiden, dass Sie den Vektor ständig abtippen müssen (eine gute Angewohnheit, die man sich angewöhnen sollte):
using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Wenn Sie keinen Zugang zu einem C++11-Compiler haben (oder die Typ-Alias-Syntax aus irgendeinem Grund nicht mögen), dann können Sie die traditionellere typedef
:
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
Nebenbei bemerkt:
An dieser Stelle sind Sie vielleicht schon einmal auf Iteratoren gestoßen, und Sie haben vielleicht schon einmal gehört, dass man Iteratoren verwenden "soll", und fragen sich vielleicht, warum. Die Antwort ist nicht leicht zu verstehen, aber kurz gesagt geht es darum, dass Iteratoren eine Abstraktion sind, die Sie von den Details der Operation abschirmen.
Es ist praktisch, ein Objekt (den Iterator) zu haben, das die gewünschte Operation durchführt (z. B. sequentieller Zugriff), anstatt die Details selbst zu schreiben (die "Details" sind der Code, der den tatsächlichen Zugriff auf die Elemente des Vektors durchführt). Sie sollten beachten, dass Sie in der for-Schleife den Iterator immer nur auffordern, Ihnen einen Wert zurückzugeben ( *i
, wobei i
ist der Iterator) - Sie interagieren nie mit path
direkt selbst. Die Logik geht wie folgt: Sie erstellen einen Iterator und geben ihm das Objekt, über das Sie eine Schleife ziehen wollen ( iterator i = path.begin()
), und dann bitten Sie den Iterator lediglich, den nächsten Wert für Sie zu ermitteln ( *i
); Sie mussten sich nie darum kümmern, wie der Iterator das genau macht - das ist seine Sache, nicht Ihre.
Gut, aber was soll das bringen? Stellen Sie sich vor, es wäre nicht einfach, einen Wert zu ermitteln. Was, wenn es ein bisschen Arbeit bedeutet? Keine Sorge, denn der Iterator hat das für Sie erledigt - er kümmert sich um die Details, und Sie müssen ihn nur noch nach einem Wert fragen. Und was ist, wenn Sie den Container von std::vector
zu etwas anderem? Theoretisch ändert sich Ihr Code nicht, auch wenn sich die Details des Zugriffs auf Elemente im neuen Container ändern: Denken Sie daran, dass der Iterator alle Details hinter den Kulissen für Sie erledigt, so dass Sie Ihren Code überhaupt nicht ändern müssen - Sie fragen den Iterator einfach nach dem nächsten Wert im Container, genauso wie vorher.
Auch wenn dies wie ein verwirrender Overkill für die Schleifenbildung durch einen Vektor erscheinen mag, gibt es gute Gründe für das Konzept der Iteratoren, so dass man sich durchaus an ihre Verwendung gewöhnen kann.
Indizierung
Sie können auch einen Integer-Typ verwenden, um explizit durch die Elemente des Vektors in der for-Schleife zu indizieren:
for (int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
Wenn Sie dies tun, ist es besser, die Mitgliedstypen des Containers zu verwenden, wenn sie verfügbar und geeignet sind. std::vector
hat einen Membertyp namens size_type
für diesen Auftrag: Es ist der Typ, der von der size
Methode.
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
Warum nicht diese anstelle der iterator
Lösung? Für einfache Fälle können Sie das tun, aber mit einer iterator
bringt mehrere Vorteile mit sich, die ich oben kurz skizziert habe. Daher würde ich Ihnen raten, diese Methode zu vermeiden, es sei denn, Sie haben gute Gründe dafür.
std::copy (C++11)
Voir Josuas Antwort . Sie können den STL-Algorithmus verwenden std::copy
um den Inhalt des Vektors in den Ausgabestrom zu kopieren. Ich habe dem nichts hinzuzufügen, außer dass ich diese Methode nicht verwende, aber dafür gibt es außer der Gewohnheit keinen guten Grund.
std::ranges::copy (C++20)
Der Vollständigkeit halber wurden mit C++20 Bereiche eingeführt, die auf den gesamten Bereich einer std::vector
also keine Notwendigkeit für begin
y end
:
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));
Es sei denn, Sie haben einen aktuellen Compiler (auf GCC offenbar mindestens Version 10.1 ), werden Sie wahrscheinlich keine Ranges-Unterstützung haben, auch wenn Sie vielleicht einige C++20-Funktionen zur Verfügung haben.
Überladung std::ostream::operator<<
Siehe auch Die Antwort von Chris unten . Dies ist eher eine Ergänzung zu den anderen Antworten, da Sie immer noch eine der oben genannten Lösungen in der Überladung implementieren müssen, aber der Vorteil ist ein viel saubererer Code. So könnten Sie die std::ranges::copy
Lösung oben:
#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
using Path = std::vector<char>; // type alias for std::vector<char>
std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}
int main() {
Path path{'/', 'f', 'o', 'o'};
// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;
return 0;
}
Jetzt können Sie Ihre Path
Objekte in den Ausgabestrom, genau wie die Grundtypen. Die Verwendung einer der anderen oben genannten Lösungen sollte ebenso einfach sein.
Schlussfolgerung
Jede der hier vorgestellten Lösungen wird funktionieren. Es liegt an Ihnen (und dem Kontext oder Ihren Codierungsstandards), welche die "beste" ist. Alles, was detaillierter ist als dies ist wahrscheinlich am besten für eine andere Frage verlassen, wo die Vor-und Nachteile richtig bewertet werden können, aber wie immer Benutzerpräferenz wird immer eine Rolle spielen: keine der vorgestellten Lösungen sind objektiv falsch, aber einige werden schöner zu jedem Programmierer aussehen.
Nachtrag
Dies ist eine erweiterte Lösung einer früheren Lösung, die ich veröffentlicht habe. Da dieser Beitrag immer wieder Beachtung fand, beschloss ich, ihn zu erweitern und auf die anderen hervorragenden Lösungen zu verweisen, die hier gepostet wurden, zumindest auf diejenigen, die ich persönlich in der Vergangenheit mindestens einmal verwendet habe. Ich möchte den Leser jedoch ermutigen, sich die Antworten weiter unten anzusehen, da es wahrscheinlich gute Vorschläge gibt, die ich vergessen habe oder nicht kenne.