Warum ein vector statt eines rohen Arrays
Ein rohes Array hat eine zur Kompilierzeit fest verdrahtete Größe und vergisst seine Länge in dem Moment, in dem du es an eine Funktion übergibst. std::vector behebt beide Probleme: Es ist ein größenveränderliches Array, das seine eigene Länge verfolgt, bei Bedarf wächst und seinen eigenen Speicher aufräumt. In modernem C++ ist vector der Standard-Container - greife nur dann zu einem rohen Array, wenn du einen konkreten Grund dagegen hast.
Binde <vector> ein und deklariere dann einen, indem du den Elementtyp in spitze Klammern setzt:
scores.size() nennt dir immer die aktuelle Länge - kein separates int n, das synchron gehalten werden muss, und keine sizeof-Tricks. Das {90, 75, 100, 60} ist ein Klammer-Initialisierer; der vector erkennt selbst, dass er vier Plätze braucht.
Einen vector erstellen und initialisieren
Es gibt mehrere Wege, einen zu bauen, je nachdem, was du im Voraus weißt:
Achte auf die Falle Runde- vs. Geschweifte-Klammern: vector<int> tens(5, 10) erstellt fünf Kopien von 10, während vector<int> tens{5, 10} einen zweielementigen vector erzeugt, der 5 und 10 enthält. Runde Klammern bedeuten „Größe und Füllwert"; geschweifte Klammern bedeuten „diese konkreten Elemente".
Elemente hinzufügen und entfernen
Der ganze Sinn eines vector ist, dass er wächst. push_back hängt am Ende an, und pop_back entfernt am Ende:
back() gibt das letzte Element zurück und front() das erste - sauberer als v[v.size() - 1] und v[0]. Seit C++11 kannst du außerdem emplace_back(args...) verwenden, um ein Element direkt an Ort und Stelle zu konstruieren, was bei schwereren Typen eine temporäre Kopie vermeidet.
Ein häufiger Anfängerfehler ist, front() oder back() auf einem leeren vector aufzurufen. Das ist undefiniertes Verhalten, kein Fehler - sichere dich immer zuerst mit if (!v.empty()) ab.
Elemente lesen: [] vs. at()
Du indizierst einen vector genau wie ein Array mit []. Aber [] führt keine Grenzprüfung durch - ein Index außerhalb des Bereichs ist undefiniertes Verhalten, das stillschweigend Müll lesen oder später an einer verwirrenden Stelle abstürzen kann:
vector<int> v = {1, 2, 3};
cout << v[10]; // UNDEFINIERTES VERHALTEN - keine Prüfung, kein Fehler
Wenn du Sicherheit willst, verwende at(). Es prüft den Index und wirft std::out_of_range bei einem ungültigen Zugriff, sodass du einen klaren Fehler statt einer Datenbeschädigung bekommst:
Faustregel: Verwende [] in engen Schleifen, in denen du bereits bewiesen hast, dass der Index gültig ist, und at() an Grenzen, an denen sich fehlerhafte Eingaben einschleichen könnten.
Über einen vector iterieren
Der sauberste Weg, einen vector zu durchlaufen, ist die bereichsbasierte for-Schleife. Nimm die Elemente per const auto&, um ohne Kopieren zu lesen, oder per auto&, um sie an Ort und Stelle zu bearbeiten:
Wenn du den Index tatsächlich brauchst (etwa um Nachbarn zu vergleichen), verwende eine klassische Zählschleife - beachte aber, dass size() einen vorzeichenlosen Typ (size_t) zurückgibt. Ein vorzeichenbehaftetes int i dagegen zu vergleichen kann Compiler-Warnungen und überraschende Überläufe auslösen, bevorzuge daher size_t i oder, wenn möglich, eine bereichsbasierte Schleife:
for (size_t i = 0; i < v.size(); i++) { // size_t, nicht int
cout << v[i];
}
size, capacity und reserve
Ein vector hält zwei Zahlen: size() (wie viele Elemente er enthält) und capacity() (wie viele er aufnehmen kann, bevor er wachsen muss). Wenn ein push_back die Kapazität überschreitet, alloziert der vector einen größeren Block, kopiert jedes Element hinüber und gibt den alten Block frei. Deshalb ist wiederholtes push_back amortisiert günstig, aber jede einzelne Reallokation ist nicht kostenlos:
Wenn du ungefähr weißt, wie viele Elemente du hinzufügen wirst, rufe zuerst reserve() auf, um die wiederholten Reallokationen zu überspringen. Beachte, dass reserve() die Kapazität ändert, nicht die Größe - der vector hat weiterhin null Elemente, bis du sie hinzufügst.
Diese Reallokation ist auch die Quelle des fiesesten vector-Bugs. Da das Wachsen den Speicher verschiebt, wird jeder Zeiger, jede Referenz und jeder Iterator, den du in den vector gespeichert hast, nach einem push_back, das realloziert, zu einem hängenden Verweis:
vector<int> v = {1, 2, 3};
int& first = v[0]; // Referenz in den vector
v.push_back(4); // kann reallozieren...
cout << first; // HÄNGEND - kann auf freigegebenen Speicher zeigen
Dasselbe gilt für Iteratoren: Mache kein push_back oder erase, während du mit einem gespeicherten Iterator iterierst. Wenn du Elemente während des Schleifens entfernen musst, verwende den Rückgabewert von erase oder das Erase-Remove-Idiom mit std::remove.
Als Nächstes: map
Ein vector ist perfekt, wenn du Dinge nach Position nachschlägst - Element 0, Element 1 und so weiter. Aber oft willst du Dinge stattdessen nach einem Schlüssel nachschlagen: einen Benutzernamen, eine Produkt-ID, ein Wort. Genau dafür ist std::map da. Als Nächstes behandeln wir map, den Schlüssel-Wert-Container von C++, einschließlich des Einfügens, Nachschlagens und Iterierens von Einträgen - und den []-erzeugt-einen-Standardwert-Fallstrick, der fast jeden stolpern lässt.
Häufig gestellte Fragen
Was ist ein vector in C++?
Ein std::vector ist ein dynamisches (größenveränderliches) Array aus der C++-Standardbibliothek. Anders als ein rohes Array kennt er seine eigene Größe, wächst automatisch, wenn du mit push_back Elemente hinzufügst, und gibt seinen Speicher für dich frei. Binde <vector> ein und schreibe vector<int> v;, um einen zu erstellen.
Was ist der Unterschied zwischen [] und at() bei einem C++-vector?
v[i] führt keine Grenzprüfung durch - ein Index außerhalb des Bereichs ist undefiniertes Verhalten (Absturz oder stille Datenbeschädigung). v.at(i) prüft den Index und wirft std::out_of_range, wenn er ungültig ist. Verwende [] in heißen Schleifen, in denen du den Index bereits validiert hast, und at(), wenn du ein sicheres, gut debugbares Fehlverhalten möchtest.
Macht push_back Zeiger und Referenzen in einen C++-vector ungültig?
Ja, möglicherweise. Wenn einem vector die Kapazität ausgeht, realloziert push_back seinen Speicher in einen neuen Block, was jeden Zeiger, jede Referenz und jeden Iterator auf die alten Elemente ungültig macht. Halte keine Referenz auf ein Element über ein push_back hinweg und rufe nach Möglichkeit vorab reserve() auf, um überraschende Reallokationen zu vermeiden.