Was ist ein Array?

Ein Array ist quasi eine Gruppe von Elementen, die eine Einheit ergeben. Diese Elemente müssen alle vom selben Typ sein (z.B. Integer Zahlen) und der Speicher der einzelnen Elemente liegt fortlaufend hintereinander. Somit kann ich mithilfe eines Indexes auf ein bestimmtes Element des Arrays zugreifen.

Angenommen wir schreiben ein Programm, bei dem der Benutzer fünf Zahlen eingeben muss. Eine Möglichkeit wäre es für diese fünf Zahlen einzelne Variablen zu deklarieren:

int firstNum = 0;
int secondNum = 0;
int thirdNum = 0;
int fourthNum = 0;
int fifthNum = 0;

Falls der Benutzer nun 500 Zahlen eingeben müsste, hätten wir ein Problem. Mit viel Zeit und Geduld könnte man das so machen, aber es geht auch einfacher. Der bessere Weg wäre es, ein Array mit fünf Zahlen zu deklarieren und diese mit 0 zu initialisieren.

int myNumbers[5] = {0};

Und wenn ich nun 500 Integer Zahlen initialisieren müsste, wäre der Aufwand derselbe:

int myNumbers[500] = {0};

Ein Array aus char Elementen würde dementsprechend so aussehen:

char myCharacters[5];

Solche Arrays werden auch statische Arrays genannt, weil ihre Anzahl der Elemente und ihre Größe im Speicher bereits zur Kompilierzeit feststehen.

Weitere Möglichkeiten ein Array zu deklarieren und initialisieren:

// initialize its content on an per-element basis:
int myNumbers[5] = {4, 5, -8, 2, 1};

// initialize all integers to zero:
int myNumbers[5] = {};

// initialize the first two elements to 3 and 6 and the rest to zero:
int myNumbers[5] = {3, 6};

// define a constant for the length of the array
// and use that constant for the initialization:
cont int ARRAY_LENGTH = 5;
int myNumbers[ARRAY_LENGTH] = {4, 5, -8, 2, 1};

// if you know all initial elements you can leave out the length:
int myNumbers[] = {4, 5, -8, 2, 1};

Wie ist so ein Array aufgebaut?

Man muss sich ein Array wie ein kleines Bücherregal vorstellen, in dem die Bücher direkt nebeneinander stehen. Alle Bücher haben allerdings exakt die gleiche Größe, die vom Typ der Elemente abhängt. Man kann direkt auf ein bestimmtes Element des Arrays mithilfe des Indexes zugreifen.

Der Index beginnt immer bei 0 und endet bei LängeDesArrays-1. Wenn ich ein Array habe mit 5 Elementen, dann hat das erste Element den Index 0 und das letzte Element den Index 4. Der Zugriff erfolgt mithilfe von eckigen Klammern. myNumbers[0] wäre das erste Element und myNumbers[4] dementsprechend das letzte Element.

Der Compiler kontrolliert allerdings nicht, ob ein Index zulässig ist. Ich könnte also bei einem Array mit 5 Elementen auch versuchen auf myNumber[100] zuzugreifen (was immer sich in diesem Speicherbereich befindet) und der Compiler wirft keinen Fehler. Wie stabil das Programm dann noch läuft, ist aber schwer zu sagen und sollte deshalb vermieden werden.

Das folgende Programm zeigt, wie ich ein Array initialisiere und im Anschluss über den Index auf die einzelnen Elemente zugreife und diese ausgebe.

#include <iostream>

using namespace std;

int main() {
    int myNumbers[5] = {4, 5, -8, 3, 6};

    cout << "First element at Index 0: " << myNumbers[0] << endl;
    cout << "Second element at Index 1: " << myNumbers[1] << endl;
    cout << "Third element at Index 2: " << myNumbers[2] << endl;
    cout << "Fourth element at Index 3: " << myNumbers[3] << endl;
    cout << "Fifth element at Index 4: " << myNumbers[4] << endl;
    
    return 0;
}

Ich kann über den Index einem Element des Arrays auch einen Wert zuweisen, genauso wie ich einer einfachen Variablen einen Wert zuweise.

// assign 1 to the third element:
int myNumbers[2] = 1;

Das folgende Programm zeigt, wie ich ein Array mithilfe einer Konstanten deklariere und initialisiere und der Nutzer dann durch Eingabe eines Indexes und einer Integer Zahl ein Element des Arrays während des Programmablauf ändern kann.

#include <iostream>

using namespace std;

int main() {
    const int ARRAY_LENGTH = 5;

    // array of 5 integers initialised using a const
    int myNumbers[ARRAY_LENGTH] = {5, 10, 0, -101, 20};

    cout << "Enter index of the element to be changed: ";
    int elementIndex = 0;
    cin >> elementIndex;

    cout << "Enter new value: ";
    int newValue = 0;
    cin >> newValue;

    // changing the element
    myNumbers[elementIndex] = newValue;

    cout << "Element " << elementIndex << " in array myNumbers is: ";
    cout << myNumbers[elementIndex] << endl;

    return 0;
}
Enter index of the element to be changed: 2
Enter new value: 12
Element 2 in array myNumbers is: 12

Normalerweise sollte immer überprüft werden, ob der Index, den der User eingibt, auch gültig ist. Hier könnte man ohne Weiteres über die Grenzen des Arrays hinaus in den Speicher greifen, was natürlich immer gefährlich ist.

Mehrdimensionale Arrays

Ich habe vorhin ein Array mit einer Reihe von Büchern in einem Regal verglichen. Das Regal kann ganz kurz sein, aber auch verdammt lang. Die Länge des Regals wäre allerdings die einzige Dimension, die das Array bestimmt, solange dieses eindimensional ist. In einer Bibliothek finden wir aber normalerweise Bücherregale mit mehreren Ebenen. Eine Reihe mit C++-Büchern, die nächste mit Python und noch eine mit Javascript – Büchern. Dieses zweidimensionale Regal verfügt also über eine Länge und Höhe.

Angenommen, das Regal hat in jeder Ebene 5 Bücher (etwas unrealistisch, aber nun ja…) und besteht aus 3 Ebenen. Das würde man in C++ so deklarieren:

int bookShelf[3][5];

Weiterhin angenommen, dass die Bücher einen Index von 0 bis 14 (15 Bücher) haben, angefangen von oben links bis unten rechts, dann würde man das Array so initialisieren:

int bookShelf[3][5] = {{0, 1, 2, 3, 4}, {5, 6, 7, 8, 9}, {10, 11, 12, 13, 14}};

Die Initialisierung sieht also so ähnlich aus, als würden wir einfach zwei eindimensionale Arrays mit Werten füllen. Im Speicher bleibt das Array allerdings weiterhin eindimensional. Wir könnten es eigentlich auch folgendermaßen initialisieren:

int bookShelf[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};

Wie auch immer, die erste Methode macht es für den Programmierer einfacher zu verstehen und verdeutlichen, worum es sich hier handelt: Ein Array aus Arrays.

Wie greife ich auf die Elemente zu?

int bookShelf[3][3] = {{500, 201, 12}, {23, 42, 88}, {324, 589, -126}};

Dieses Array kann man sich nun als 3 Arrays vorstellen, die alle jeweils 3 Integer Zahlen enthalten. Das Element mit dem Wert 324, wäre demnach an Position[2][0] und das Element mit dem Wert 42 an Position [1][1].

// multArray.cpp

#include <iostream>

using namespace std;

int main() {
    int bookShelf[3][3] = 
        {{500, 201, 12}, {23, 42, 88}, {324, 589, -126}};
    
    cout << "Row 0: " << bookShelf[0][0] << " "
                      << bookShelf[0][1] << " "
                      << bookShelf[0][2] << endl;
    cout << "Row 1: " << bookShelf[1][0] << " "
                      << bookShelf[1][1] << " "
                      << bookShelf[1][2] << endl;
    cout << "Row 2: " << bookShelf[2][0] << " "
                      << bookShelf[2][1] << " "
                      << bookShelf[2][2] << endl;
    return 0;
}
Row 0: 500 201 12
Row 1: 23 42 88
Row 2: 324 589 -126

Dynamische Arrays

Was mache ich, wenn ich vorher nicht wissen kann, wie viele Bücher in ein bestimmtes Bücherregal abgelegt werden? Ich könnte ungefähr schätzen und vielleicht sogar annähernd richtig liegen, allerdings gibt es auch mehr als genug Anwendungsfälle für den Einsatz von Arrays, wo ich zu Beginn nicht wissen kann wie viele Elemente hinzugefügt werden müssen. Es ist also wahrscheinlich, dass ich ein viel zu großes Array anlege, um gut gerüstet zu sein und dann eventuell viel zu viel Speicherplatz verschwende.

Für die meisten Fälle sind statische Arrays, wie wir sie bisher kennengelernt haben nicht geeignet. Besser ist es daher dynamische Arrays zu nehmen, die den Speicher optimal nutzen und in ihrer Größe veränderbar sind, je nachdem wie viele Elemente sie aufnehmen müssen.

C++ hat hierfür ein einfach zu benutzendes, dynamisches Array namens std::vector.

// dynArray.cpp

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<int> dynArray(3);  // dynamic array of int with 3 elements
    
    dynArray[0] = 365;
    dynArray[1] = 120;
    dynArray[2] = 80;

    cout << "Number of integers in array: " << dynArray.size() << endl;

    cout << "Enter another element to insert" << endl;
    int newValue = 0;
    cin >> newValue;
    dynArray.push_back(newValue);  // put new element at the end

    cout << "Numbers of integers in array: " << dynArray.size() 
         << endl;
    cout << "Last element in array: ";
    // easy way to access the last element:
    cout << dynArray[dynArray.size() -1] << endl;

    return 0;
}
Number of integers in array: 3
Enter another element to insert
34
Numbers of integers in array: 4
Last element in array: 34

Keine Angst, die Syntax mag noch etwas verwirrend aussehen, da ich Templates und std:vector bisher noch nicht erklärt habe. Am besten man schaut sich die Ausgabe des Programm an und versucht dies im Quellcode nachzuvollziehen. Die Anfangsgröße des Vektors ist 3. Nun wird der Nutzer aufgefordert ein neues Element einzugeben. Dieses Element wird am Ende des Vektors angefügt. Der Vektor wird also automatisch vergrößert. Das können wir an der erneuten Ausgabe der Größe sehen. Außerdem lassen wir uns das letzte Element des veränderten Vektors anzeigen.

Um das dynamische Array std:vector nutzen zu können, müssen wir den entsprechenden Header am Anfang des Codes angeben. Vektoren werden wir in einem späteren Artikel nochmal behandeln.

C-style Buchstaben Strings

C-style Strings sind eine spezielle Form von Arrays aus Buchstaben. Wir haben bereits C-style Strings gesehen:

cout << "Hello World!";

Das ist quasi dasselbe, wie:

char sayHello[] = 
     {'H'. 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0'};
cout << sayHello << endl;

Wichtig ist hier der Null-Character '\0' am Ende. Er sagt dem Compiler, dass die Zeichenkette zu Ende ist. Wenn wir eine Zeichenkette in unserem Code verwenden, setzt der Compiler am Ende immer automatisch einen Null-Character.

C-style Strings sind allerdings gefährlich. Falls ich vom Nutzer eine Eingabe verlange, kann ich nie ganz sicher sein, dass die Eingabe nicht länger wird, als mein vorher bestimmtes Buchstaben-Array. Aus diesem Grund gibt es in C++ die eigenen Standard-Strings…

C++ Strings: std::string

C++ Strings sind effizienter und sicherer im Umgang mit Texteingaben und Manipulation dieser Zeichenketten. std::string ist nicht statisch in der Größe, wie C-style Strings, die aus char Arrays bestehen.

// stringExample.cpp

#include <iostream>
#include <string>

using namespace std;

int main() {
    string greetString("Hello std::string");
    cout << greetString << endl;

    cout << "Enter a line of text: " << endl;
    string firstLine;
    getline(cin, firstLine);

    cout << "Enter another: " << endl;
    string secondLine;
    getline(cin, secondLine);

    cout << "Result of concatenation: " << endl;
    string concatString = firstLine + " " + secondLine;
    cout << concatString << endl;

    cout << "Copy of concatenated string: " << endl;
    string aCopy;
    aCopy = concatString;
    cout << aCopy << endl;

    cout << "Length of concat string: " 
         << concatString.length() << endl;
    
    return 0;
}
Hello std::string
Enter a line of text:
Dies ist nur ein Test
Enter another:
und dies hier auch.
Result of concatenation:
Dies ist nur ein Test und dies hier auch.
Copy of concatenated string:
Dies ist nur ein Test und dies hier auch.
Length of concat string: 41

Auch hier gilt wieder: Schaut euch die Ausgabe an und versucht den Code anhand dessen zu verstehen. Auch wenn die Syntax teilweise noch unbekannt ist, kann man das Programm trotzdem nachvollziehen.

Wichtig: Wie auch bei std::vector, muss man bei std::string den entsprechenden Header inkludieren. Wir werden auch hier in einem späteren Artikel näher auf die verschiedenen Funktionen für strings eingehen, genauso wie auf weitere Klassen der Standard Template Library (STL) von C++.

Im nächsten Artikel geht es dann nochmal genauer um Anweisungen, Ausdrücke und Operatoren…