C-Arrays vs. C++-Container In C verwendet man (mangels Alternativen) sehr häufig das C-Array (= Feld): im Speicher hintereinander abgelegte Elemente vom gleichen Typ. Arrays haben einen Basistyp (Typ des einzelnen Elementes), die Anzahl der Elemente (Dimension) muss bei der Definition als Konstante vorgegeben werden: int i, x[20], y[1]; // x enthält 20 Elemente, y nur 1 // ABER: y ist KEIN int, sondern ein int-array char zeile[81]; // Platz für 80 Zeichen + das Nullbyte!!!! const int dim = 13; double x[dim]; // Fehler: keine Konstante als Dimension constexpr int dim = 13; double x[dim]; // ok WICHTIG: Die Array-Elemente beginnen immer mit dem Index 0; der Index wird in eckigen Klammern geschrieben, es ist nur ein Index möglich (also nicht z.b. Zeile/Spalte) : x[0] = 17; x[20] = 0; // setzt allererstes Array-Element // Fehler: dieses Element gibt es nicht mehr for (i = 0; i < 20; ++i) x[i] = i; // x = {0, 1, 2,..., 19} for (i = 0; i <= 20; ++i) x[i] = i; // Fehler: x[20] existiert nicht for (auto i : x) // nur in C++ ab C++11 cout << i << '\n'; // gib alle Werte aus for (auto& i : x) // nur in C++ ab C++11 i = 1; // setze alle Werte auf 1 Arrays können als Ganzes oder teilweise initialisiert werden. int x[20] = {1, 2, 3, 4, 5, 6}; // nur die ersten 6 Werte werden gesetzt, der Rest wird mit 0 aufgefüllt int x[20]{1, 2, 3, 4, 5, 6}; // (ohne =) bedeutet dasselbe (ab C++11)
int y[] = {1, 2, 3, 4}; // y wird dadurch zu einem int[4] int z[10] = {}; // alle 10 Elemente werden mit 0 belegt x = {1, 2, 3}; // FEHLER: Das geht nur bei der Initialisierung Die auch in C++ verwendeten C-String-Literale (Zeichenketten-Konstante) wie "blabla" sind genau solche (konstante) C-Arrays aus char (hier char[7]). Arrays haben natürlich auch Nachteile: Man kann unbemerkt die Arraygrenzen überschreiten, die Dimension ist schon zur Übersetzungszeit festzulegen und kann nachträglich nicht geändert werden usw. Aus diesem Grund besitzen die moderneren Varianten C++ (und auch Java) Ersatzlösungen, die diesen Komfort zusätzlich bieten: Container bzw. genauer Container-Template-Klassen. Es gibt einige vordefinierte Containertypen (Array, Vector, Menge, Liste,...). Die STL = Standard Template Library Diese Bibliothek hat sich seit C++11 als zentraler Bestandteil von C++ etabliert. Mit jeder C++ Evolution wird der Inhalt ausgebaut und optimiert. Sie definiert die sogenannten Container- Templates, die beliebige Datentypen in nahezu beliebigen Speicherformen aufnehmen können, sowie einige wichtige Algorithmen für diese Container. Der Typ der gespeicherten Objekte steht dabei in Spitzklammern, es handelt sich demnach um Templates (genaueres dazu kommt später). Das std::array-template Nach dem Inkludieren der Headerdatei array steht dem C++-Programmierer das Klassentemplate std::array<t, Dim> zur Verfügung, welches ähnlich wie ein C-Array von T mit der Dimension Dim verwendet werden kann. Der Grundtyp T und die konstante Dimension Dim muss in den Spitzklammern angegeben werden std::array<double, 100> x; // erzeugt array mit 100 double-werten std::array <double, 3> x = { 1., 2., 3.}; // erzeugt array mit den 3 Werten 1.0, 2.0, 3.0 std::array <double, 3> x{ 1., 2., 3.}; // dasselbe x[1] = 2.0; // setzt 2. Element auf 2.0 (gleich wie bei C-Array) std::cout << "x hat " << x.size() << " Elemente\n";
Das std::vector-template Nach dem Inkludieren der Headerdatei vector steht dem C++-Programmierer das Klassentemplate std::vector<t> zur Verfügung, welches ähnlich wie ein T-Array verwendet werden kann. Der Grundtyp T muss in Spitzklammern angegeben werden: std::vector<double> x; // erzeugt leeren vector, der wachsen kann std::vector<double> x(100); // erzeugt vector mit 100 Werten 0.0 std::vector<double> x(100, 1.); // erzeugt vector mit 100 Werten 1.0 std::vector<double> x = {1., 2., 3.}; // erzeugt vector mit den 3 Werten 1.0, 2.0, 3.0 std::vector<double> x{1., 2., 3.}; // dasselbe x[1] = 2.0; // setzt 2. Element auf 2.0 (gleich wie bei Array) std::cout << "x hat derzeit " << x.size() << " Elemente\n"; x.push_back(20.); // hänge hinten 20.0 an den vector an, dieser wächst Das std::list-template Nach dem Inkludieren der Headerdatei list steht dem C++-Programmierer das Klassentemplate std::list<t> zur Verfügung, welches die Daten als (doppelt verkettete Liste verwaltet). Der Grundtyp T muss in Spitzklammern angegeben werden. Die Liste gestattet keinen wahlfreien Zugriff mit Indexklammer, kann dafür an jeder Position schnell neue Elemente einfügen und ist daher gut für Datenbank-Anwendungen geeignet. std::list<double> x; // erzeugt leere list, die wachsen kann std::list<double> x{ 1., 2., 3.}; // erzeugt Liste mit diesen 3 Elementen x[1] = 2.0; // Fehler: eine Liste hat keinen Index-Zugriff std::cout << "x hat derzeit " << x.size() << " Elemente\n"; x.push_back(20.); // hänge hinten 20.0 an die Liste an Unterschiede zwischen C-Array und einigen C++-Containern: C-Array: + geht in C/C++/Java gleich
+ ist ein bisschen schneller im Zugriff - Größe muss zur Compilezeit festgelegt werden - kann weder wachsen noch schrumpfen - bei zu großen Arrays besteht die Gefahr eines Programmabsturzes (am Stack) - weiß nicht, wie viele Elemente tatsächlich enthalten sind - Zugriff mit Index [] auch außerhalb der Arraygrenzen möglich - bei Übergabe an Funktionen zerfällt das Array (zu einem Pointer auf den Beginn) std::array: + kann wie ein C-Array verwendet werden (Indexzugriff) + weiß seine fixe Größe (size() Methode) + kann ohne Zeitverlust bei Bedarf in ein C-Array umgewandelt werden + Zugriff innerhalb der Grenzen kann überwacht werden (at() Methode) + bei Übergabe an Funktionen bleibt der Typ erhalten + besitzt noch weitere nützliche Methoden (z.b. size()) - Größe muss zur Compilezeit festgelegt werden - kann weder wachsen noch schrumpfen - bei zu großen arrays besteht die Gefahr eines Programmabsturzes (am Stack) std::vector: + kann beliebig groß werden + kann wie ein C-Array verwendet werden + weiß seine Größe (size() Methode) + kann ohne Zeitverlust bei Bedarf in ein C-Array umgewandelt werden + Zugriff innerhalb der Grenzen kann überwacht werden (at() Methode) + bei Übergabe an Funktionen bleibt der Typ erhalten + keine Gefahr eines Programm-Absturzes (da am Heap) + besitzt noch weitere nützliche Methoden (z.b. size()) + ist sehr schnell (ist daher der meistverwendete Containertyp, der wachsen kann) - beim Wachsen kann sich der Speicherort der Daten ändern std::list: + kann beliebig groß werden + weiß seine Größe (size() Methode) + keine Gefahr eines Programm-Absturzes (am Heap) + kann an jeder Stelle schnell Elemente einfügen oder löschen + bei Übergabe an Funktionen bleibt der Typ erhalten + besitzt noch weitere nützliche Methoden (z.b. size()) - kein Zugriff mit dem Index-Operator möglich
- nur sequenzieller Zugriff möglich, daher langsam std::string: (entspricht einem std::vector<char> mit weiteren nützlichen Methoden) Stack: Ein Speicherbereich mit fester Größe. Ist dieser voll, stürzt das Programm ab. Hier werden z.b. die Funktionsargumente und die lokalen Variablen angelegt. Heap: Ein Speicherbereich, der wachsen kann. Ist dieser voll, besorgt sich C++ vom Betriebssystem Nachschub. Hier werden dynamische Daten (deren Umfang sich ändern kann) angelegt. Die Container-Typen verwenden eine feste Größe an Stack-Speicher (für die Verwaltung der Daten) und speichern die eigentlichen Daten am Heap. Sie bestehen also aus 2 getrennten Datenblöcken. Im Allgemeinen fällt die Entscheidung in C++ fast immer zugunsten eines Containers. Wir verwenden in den Übungen fast ausschließlich std::string, std::array, std::vector, da in der Mathematik sehr häufig (Matrizen, Vektoren) solche Container mit wahlfreiem Indexzugriff benötigt werden. Abseits der Mathematik (z.b. bei Datenbanken) benötigt man statt des Indexzugriffs eher die Möglichkeit, neue Elemente überall einzufügen oder alte Elemente zu löschen, wofür etwa (doppelt) verkettete Listen manchmal besser geeignet sind (std::list). Weitere Speicherstrukturen der STL sind z.b. für Bäume: #include <set> (oder #include <unordered_set>) für Hash-Tabellen: #include <map> Wichtige Methoden der gebräuchlichen Container-Templates Eine vollständige Liste aller Containermethoden findet man im WWW! http://www.cplusplus.com/reference http://en.cppreference.com/ Die folgenden Methoden werden von fast allen Containertypen unterstützt: size() front() back() begin(), end() Anzahl der Elemente des Containers; Ergebnis std::size_t (meist unsigned long) Das erste Elemente des Containers; Ergebnis ist Referenz auf das Element Das letzte Elemente des Containers; Ergebnis ist Referenz auf das Element Iteratoren auf das 1., hinter das letzte Element Ergebnis: iterator
cbegin(), cend() konstante Iteratoren auf das 1., hinter das letzte Element Ergebnis: const_iterator rbegin(), rend() reverse Iteratoren auf das letze, vor das 1. Element Ergebnis: reverse_iterator crbegin(), crend() konstante reverse Iter. auf das letze, vor das 1. Element Ergebnis: const_reverse_iterator empty() ob der Container Elemente enthält Ergebnis: bool push_back(val) fügt val hinten an den Container an (nicht bei array) Ergebnis: void insert(it, val) fügt val an der Iterator-Position it ein (nicht bei array) Ergebnis: void Konstruktoren mit Werten in geschwungen Klammern (nicht bei array, diese können aber ebenfalls mit solchen Listen initialisiert werden. Dabei handelt es sich aber genau genommen um keine Konstruktoren.) std::list<int> v{1, 2, 3, 5}; // kreiert int-list mit Werten 1, 2, 3, 5 for (auto it = v.cbegin(); it!= v.cend(); ++it) cout *it << '\n ' ; // klassische Iteratoren-Schleife // statt range-based for Ich verwende in dieser Lehrveranstaltung die Methodenaufrufe wie v.cbegin(), v.cend(), nicht sondern rufe statt dessen die analogen Funktionen der STL auf: std::cbegin(v), std::cend(v)... Der Grund dafür ist, dass diese Funktionen sowohl für die Containertypen als auch für C-Arrays funktionieren, letztere aber keine Methoden besitzen. for (auto i : {-1, -5, 10}) v.push_back(i); // an v hinten anhängen cout << "erstes Element: " << v.front() << '\n ' ; cout << "letztes Element: " << v.back() << '\n ' ; std::array<t>, std::vector<t> (und std::deque<t>, std::string) haben auch: Indexzugriff mit eckigen Klammern [3] Index-Zugriff mit Überprüfung, ob vorhanden: at(3)