C++-Programmierung/ Weitere Grundelemente/ Felder

Aus Wikibooks

Wechseln zu: Navigation, Suche


In C++ lassen sich mehrere Variablen desselben Typs zu einem Array (im Deutschen bisweilen auch Feld oder Vektor genannt) zusammenfassen. Auf die Elemente des Arrays wird über einen Index zugegriffen. Bei der Definition sind der Typ der Elemente und die Größe des Arrays anzugeben. Folgende Möglichkeiten stehen zum Anlegen eines Array zur Verfügung:

Crystal Clear app terminal.png
int feld[10];                          // Anlegen ohne Initialisierung
int feld[]   = {1,2,3,4,5,6,7,8,9,10}; // Mit Initialisierung (automatisch 10 Elemente)
int feld[10] = {1,2,3,4,5,6,7,8,9,10}; // 10 Elemente, mit Initialisierung

Soll das Array initialisiert werden, verwenden Sie eine Aufzählung in geschweiften Klammern, wobei der Compiler die Größe des Arrays selbst ermitteln kann. Es wird empfohlen, diese automatische Größenerkennung nicht zu nutzen. Wenn die Größenangabe explizit gemacht wurde, gibt der Compiler einen Fehler aus, falls die Anzahl der Intitiallisierungselemente nicht mit der Größenangabe übereinstimmt.

Wie bereits erwähnt, kann auf die einzelnen Elemente eines Arrays mit dem Indexoperator [] zugegriffen werden. Beim Zugriff auf Arrayelemente beginnt die Zählung bei 0. Das heißt, ein Array mit 10 Elementen enthält die Elemente 0 bis 9. Ein Arrayindex ist immer ganzzahlig.

Crystal Clear app terminal.png
#include <iostream>

int main() {
    int feld[10] = {1,2,3,4,5,6,7,8,9,10}; // 10 Elemente, mit Initalisierung

    for(int i = 0; i < 10; ++i) {
        std::cout << feld[i] << " ";   // Elemente 0 bis 9 ausgeben
    }
}
Crystal Clear app kscreensaver.png
Ausgabe:
1 2 3 4 5 6 7 8 9 10

Mit Arrayelementen können alle Operationen wie gewohnt ausgeführt werden.

Crystal Clear app terminal.png
feld[4] = 88;
feld[3] = 2;
feld[2] = feld[3] - 5 * feld[7 + feld[3]];

if(feld[0] < 1)
    ++feld[9];

for(int n = 0; n < 10; ++n)
    std::cout << feld[n] << std::endl;


Symbol opinion vote.svg
Hinweis
Beachten Sie, dass der Compiler keine Indexprüfung durchführt. Wenn Sie ein nicht vorhandenes Element, z.B. feld[297] im Programm verwenden, kann Ihr Programm bei einigen Durchläufen unerwartet abstürzen. Zugriffe über Arraygrenzen erzeugen undefiniertes Verhalten. In modernen Desktop-Betriebssystemen kann das Betriebssystem einige dieser Bereichsüberschreitungen abfangen und das Programm abbrechen („segmentation fault“). Manchmal überschreibt man auch einfach nur den eigenen Speicherbereich in anderen Variablen, was zu schwer zu findenden Bugs führt.

Inhaltsverzeichnis

[Bearbeiten] Zeiger und Arrays

Der Name eines Arrays wird vom Compiler (ähnlich wie bei Funktionen) als Adresse des Arrays interpretiert. In Folge dessen haben Sie neben der Möglichkeit über den Indexoperator auch die Möglichkeit, mittels Zeigerarithmetik auf die einzelnen Arrayelemente zuzugreifen.

Crystal Clear app terminal.png
#include <iostream>

int main() {
    int feld[10] = {1,2,3,4,5,6,7,8,9,10};

    std::cout << feld[3]   << "\n"; // Indexoperator
    std::cout << *(feld + 3) << "\n"; // Zeigerarithmetik
}
Crystal Clear app kscreensaver.png
Ausgabe:
4
4

Im Normalfall werden Sie mit der ersten Syntax arbeiten, es ist jedoch nützlich zu wissen, wie Sie ein Array mittels Zeigern manipulieren können. Es ist übrigens nicht möglich, die Adresse von feld zu ändern. feld verhält sich also in vielerlei Hinsicht wie ein konstanter Zeiger auf das erste Element des Arrays. Einen deutlichen Unterschied werden Sie allerdings bemerken, wenn Sie den Operator sizeof() auf die Arrayvariable anwenden. Als Rückgabe bekommen Sie die Größe des gesamten Arrays. Teilen Sie diesen Wert durch die Größe eines beliebigen Arrayelements, erhalten Sie die Anzahl der Elemente im Array.

Crystal Clear app terminal.png
#include <iostream>

int main() {
    int feld[10];

    std::cout << "   Array Größe: " << sizeof(feld)                   << "\n";
    std::cout << " Element Größe: " << sizeof(feld[0])                << "\n";
    std::cout << "Element Anzahl: " << sizeof(feld) / sizeof(feld[0]) << "\n";
}
Crystal Clear app kscreensaver.png
Ausgabe:
Array Größe: 40
 Element Größe: 4
Element Anzahl: 10

[Bearbeiten] Mehrere Dimensionen

Auch mehrdimensionale Arrays sind möglich. Hierfür werden einfach Größenangaben für die benötigte Anzahl von Dimensionen gemacht. Das folgende Beispiel legt ein zweidimensionales Array der Größe 4 × 8 an, das 32 Elemente enthält. Theoretisch ist die Anzahl der Dimensionen unbegrenzt.

Crystal Clear app terminal.png
int feld[4][8];    // Ohne Initialisierung

int feld[4][8] = { // Mit Initialisierung
    {  1,  2,  3,  4,  5,  6,  7,  8},
    {  9, 10, 11, 12, 13, 14, 15, 16},
    { 17, 18, 19, 20, 21, 22, 23, 24},
    { 25, 26, 27, 28, 29, 30, 31, 32}
};

Wie Sie sehen, können auch mehrdimensionalen Arrays initialisiert werden. Die äußeren geschweiften Klammern beschreiben die erste Dimension mit 4 Elementen. Die inneren geschweiften Klammern beschreiben dementsprechend die zweite Dimension mit 8 Elementen. Beachten Sie, dass die inneren geschweiften Klammern lediglich der Übersicht dienen, sie sind nicht zwingend erforderlich. Dementsprechend ist es für mehrdimensionale Arrays immer nötig, die Größe aller Dimensionen anzugeben.

Genaugenommen wird eigentlich ein Array von Arrays erzeugt. In unserem Beispiel ist feld ein Array mit 4 Elementen vom Typ „Array mit 8 Elementen vom Typ int“. Dementsprechend sieht auch der Aufruf mittels Zeigerarithmetik auf einzelne Elemente aus.

Crystal Clear app terminal.png
#include <iostream>

int main(){
    // Anlegen des Arrays mit Initialisierung von eben

    std::cout << feld[2][5]         << "\n"; // Indexoperator
    std::cout << *(*(feld + 2) + 5) << "\n"; // Zeigerarithmetik
}
Crystal Clear app kscreensaver.png
Ausgabe:
22
22

Es sind ebenso viele Dereferenzierungen wie Dimensionen nötig. Um sich vor Augen zu führen, wie die Zeigerarithmetik für diese mehrdimensionalen Arrays funktioniert, ist es nützlich, sich einfach die Adressen bei Berechnungen anzusehen:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int feld[4][8];


    std::cout << "Größen\n";
    std::cout << "int[4][8]: " << sizeof(feld)     << "\n";
    std::cout << "   int[8]: " << sizeof(*feld)    << "\n";
    std::cout << "      int: " << sizeof(**feld)   << "\n";

    std::cout << "Adressen\n";
    std::cout << "       feld: " << feld        << "\n";
    std::cout << "   feld + 1: " << feld + 1    << "\n";
    std::cout << "(*feld) + 1: " << (*feld) + 1 << "\n";
}
Crystal Clear app kscreensaver.png
Ausgabe:
Größen
int[4][8]: 128
   int[8]: 32
      int: 4
Adressen
       feld: 0x7fff2be5d400
   feld + 1: 0x7fff2be5d420
(*feld) + 1: 0x7fff2be5d404

Wie Sie sehen, erhöht feld + 1 die Adresse um den Wert 32 (Hexadezimal 20), was sizeof(int[8]) entspricht. Also der Größe aller verbleibenden Dimensionen. Die erste Dereferenzierung liefert wiederum eine Adresse zurück. Wird diese um 1 erhöht, so steigt der Wert lediglich um 4 ( sizeof(int)).

Symbol opinion vote.svg
Hinweis
Beachten Sie, dass auch für mehrdimensionale Arrays keine Indexprüfung erfolgt. Greifen Sie nicht auf ein Element zu, dessen Grenzen außerhalb des Arrays liegen. Beim Array int[12][7][9] können Sie auf die Elemente [0..11][0..6][0..8] zugreifen. Die Zählung beginnt also auch hier immer bei 0 und endet dementsprechend 1 unterhalb der Dimensionsgröße.

[Bearbeiten] Arrays und Funktionen

Arrays und Funktionen arbeiten in C++ nicht besonders gut zusammen. Sie können keine Arrays als Parameter übergeben und auch keine zurückgeben lassen. Da ein Array allerdings eine Adresse hat (und der Arrayname diese zurückliefert), kann man einfach einen Zeiger übergeben. C++ bietet (ob nun zum besseren oder schlechteren) eine alternative Syntax für Zeiger bei Funktionsparametern an.

Crystal Clear app terminal.png
void funktion(int *parameter);
void funktion(int parameter[]);
void funktion(int parameter[5]);
void funktion(int parameter[76]);

Jeder dieser Prototypen ist gleichwertig. Die Größenangaben beim dritten und vierten den Beispiel werden vom Compiler ignoriert. Innerhalb der Funktion können Sie wie gewohnt mit dem Indexoperator auf die Elemente zugreifen. Beachten Sie, dass Sie die Arraygröße innerhalb der Funktion nicht mit sizeof(arrayname) feststellen können. Bei diesem Versuch würden Sie stattdessen die Größe eines Zeigers auf ein Array-Element erhalten.

Aufgrund dieses Verhaltens könnte man die Schreibweise des ersten Prototypen auswählen. Andere Programmierer argumentieren, dass bei der zweiten Schreibweise deutlich wird, dass der Parameter ein Array repräsentiert. Eine Größenangabe bei Arrayparametern ist manchmal anzutreffen, wenn die Funktion nur Arrays dieser Größe bearbeiten kann. Um es noch einmal zu betonen: Diese Größenangaben sind nur ein Hinweis für den Programmierer; Der Compiler wird ohne Fehler und Warnung Ihren Array mit allen Elementen übernehmen, auch wenn die beim Funktionsparameter angegebene Größe nicht mit Ihrem Array übereinstimmt.

Bei mehrdimensionalen Arrays sehen die Regeln ein wenig anders aus, da diese Arrays vom Typ Array sind. Wie Sie wissen ist es zulässig, Zeiger als Parameter zu übergeben. Entsprechend ist es natürlich auch möglich, einen Zeiger auf einen Array zulässig. Die folgenden Prototypen zeigen, wie die Syntax bei mehrdimensionalen Arrays aussieht.

Crystal Clear app terminal.png
void funktion(int (*parameter)[8]);
void funktion(int parameter[][8]);
void funktion(int parameter[4][8]);

Alle diese Prototypen haben einen Parameter vom Typ „Zeiger auf Array mit acht Elementen vom Typ int“. Ab der zweiten Dimension geben Sie also tatsächlich Arrays an, somit müssen Sie natürlich auch die Anzahl der Elemente zwingend angeben. Daher können Sie sizeof() in der Funktion verwenden, um die Größe zu ermitteln. Dies ist allerdings nicht notwendig, da Sie bereits im Vorfeld wissen, wie groß der Array ist und vom welchem Typ er ist. Die Größe berechnet sich wie folgt: sizeof(Typ) * Anzahl der Elemente. In unserem Beispiel entspricht dies 4 * 8 = 32. Auch ein zweidimensionales Array können Sie innerhalb der Funktion mit dem normalen Indexoperator zugreifen.

Beachten Sie, dass beim ersten Prototypen die Klammern zwingend notwendig sind, andernfalls hätten die eckigen Klammern auf der rechten Seite des Parameternamen Vorrang. Somit würde der Compiler dies wie oben gezeigt als einen Zeiger behandeln, natürlich unabhängig von der Anzahl der angegebenen Elemente. Ohne diese Klammern würden Sie also einen Zeiger auf einen Zeiger auf int deklarieren.

[Bearbeiten] Lesen komplexer Datentypen

Sie kennen nun Zeiger, Referenzen und Arrays, sowie natürlich die grundlegenden Datentypen. Es kann Ihnen passieren, dass Sie auf Datentypen treffen, die all das in Kombination nutzen. Im Folgenden werden Sie lernen, solche komplexen Datentypen zu lesen und zu verstehen, wie man Sie schreibt.

Symbol kept vote.svg
Tipp
Als einfache Regel zum Lesen von solchen komplexeren Datentypen können Sie sich merken:
  • Es wird ausgehend vom Namen gelesen.
  • Steht etwas rechts vom Namen, wird es ausgewertet.
  • Steht rechts nichts mehr, wird der Teil auf der linken Seite ausgewertet.
  • Mit Klammern kann die Reihenfolge geändert werden.

Die folgenden Beispiele werden zeigen, dass diese Regeln immer gelten:

Crystal Clear app terminal.png
int i;            // i ist ein int
int *j;           // j ist ein Zeiger auf int
int k[6];         // k ist ein Array von sechs Elementen des Typs int
int *l[6];        // l ist ein Array von sechs Elementen des Typs Zeiger auf int
int (*m)[6];      // m ist ein Zeiger auf ein Array von sechs Elementen des Typs int
int *(*&n)[6];    // n ist eine Referenz auf einen Zeiger auf ein Array von
                  // sechs Elementen des Typs Zeiger auf int
int *(*o[6])[5];  // o ist ein Array von sechs Elementen des Typs Zeiger auf ein
                  // Array von fünf Elementen des Typs Zeiger auf int
int **(*p[6])[5]; // p ist Array von sechs Elementen des Typs Zeiger auf ein Array
                  // von fünf Elementen des Typs Zeiger auf Zeiger auf int

Nehmen Sie sich die Zeit, die Beispiele nachzuvollziehen. Wenn Sie keine Probleme damit haben, sehen Sie sich das nächste sehr komplexe Beispiel an. Es soll die allgemeine Gültigkeit dieser Regel noch einmal demonstrieren:

Crystal Clear app terminal.png
int (*(*(**pFunc[5])(int*, double&))[6])();

pFunc ist ein Array mit fünf Elementen, das Zeiger auf Zeiger auf Funktionen enthält, die einen Zeiger auf int und eine Referenz auf double übernehmen und einen Zeiger auf Arrays mit sechs Elementen vom Typ Zeiger auf Funktionen, ohne Parameter, mit einem int als Rückgabewert zurückgeben. Einem solchen Monstrum werden Sie beim Programmieren wahrscheinlich selten bis nie begegnen, aber falls doch, können Sie es mit den obengenannten Regeln ganz einfach entschlüsseln. Wenn Sie nicht in der Lage sind, dem Beispiel noch zu folgen, brauchen Sie sich keine Gedanken zu machen: Nur wenige Menschen sind in der Lage, sich ein solches Konstrukt überhaupt noch vorzustellen. Wenn Sie es nachvollziehen können, kommen Sie sehr wahrscheinlich mit jeder Datentypdeklaration klar.


Persönliche Werkzeuge