Das Array
Willemers Informatik-Ecke

Ein Array ist eine Kombination mehrerer Variablen gleichen Typs. Die Elemente des Arrays werden über ihre Positionsnummer angesprochen.

Der Begriff "Array" wird in der deutschen Fachliteratur oft mit dem Wort "Feld" übersetzt. Leider ist dieser Begriff nicht sehr markant, so dass viele Programmierer den Begriff "Array" bevorzugen. Zu diesen gehöre ich auch, und darum halte ich es auch hier so.

Realitätsabbildung

Sie können mit einem Array also eine Reihe gleicher Elemente ansprechen. Ein Beispiel sind die Lottozahlen. Es werden immer sechs ganze Zahlen gezogen. Wenn Sie also die Lottozahlenziehung in einem Programm simulieren wollen, bietet es sich an, ein Array von sechs Integern zu verwenden.

In der Abbildung sehen Sie wie die Lottozahlen nebeneinander stehen. Unter jedem Kasten befindet sich in eckigen Klammern die Position jeder Lottozahl. Entgegen der Gewohnheit der meisten Menschen beginnt ein Array immer an der Position 0. Wenn also sechs Zahlen im Array stehen, befindet sich die letzte an Position 5.

Definition

Um das Array für die Lottozahlen im Programm zu definieren, geben Sie zunächst den Typ eines einzelnen Elements an. Als nächstes kommt der Name des Arrays. Es folgt in eckigen Klammern die Anzahl der Elemente, die das Array maximal aufnehmen können soll.
int Lotto[6];

Zugriff

Wenn Sie auf ein Element des Arrays zugreifen wollen, nennen Sie zuerst den Namen des Arrays. Es folgt in eckigen Klammern die Position des Elements, auf das Sie zugreifen wollen. Denken Sie dabei daran, dass die Array-Positionen immer bei 0 beginnen.
lotto[2] = srand() % 49 + 1;
cout << lotto[0];
Die erste Zeile zeigt, wie das dritte Element -- nicht das zweite -- mit einer Zufallszahl zwischen 1 und 49 gefüllt wird. Die zweite Zeile gibt das erste Element auf dem Bildschirm aus.

Eckige Klammern

Das syntaktische Erkennungszeichen eines Arrays sind die eckigen Klammern. Sie werden bei der Definition eines Arrays verwendet, um anzugeben, wie viele Variablen zu dem Array gehören. Die eckigen Klammern werden auch verwendet, wenn auf ein Element zugegriffen werden soll. Darum werden die eckigen Klammern Indexoperator genannt. Das Beispiel zeigt, wie das Lottozahlen-Array mit Zufallszahlen gefüllt wird:
int lotto[6];
srand(0);
for(i=0; i<6; i++)
{
    lotto[i] = rand() % 49 + 1;
}

Index

Die Variable lotto ist ein Array für den Typ Integer. Die eckigen Klammern mit der 6 in der Mitte sagen aus, dass hier sechs Integer-Variablen definiert sind. Wie man auf diese sechs Werte zugreift, sehen Sie in der for-Schleife. Dort wird den sechs Variablen nacheinander eine Zufallszahl zwischen 1 und 49 zugewiesen. Welche der sechs Variablen verwendet wird, steht in den eckigen Klammern. Die Positionsnummer nennt man auch Index. Der Index beginnt bei C++ immer bei 0. Da es sechs Elemente gibt, läuft der Index von 0 bis 5.

Da die Elemente zufällig bestückt wurden, ist es möglich, dass zwei der Elemente gleich sind. Im nächsten Schritt wird bei Gleichheit zweier Zahlen neu gezogen. Sie können in einer Schleife alle bisherigen Zahlen durchlaufen und prüfen, ob die neue Zahl bereits gezogen wurde. Das folgende Programm garantiert, dass die gezogenen Lottozahlen nicht doppelt auftreten:

#include <iostream>
#include <stdlib.h>
using namespace std;

int main()
{
    int lotto[6];
    int i, j;
    bool neueZahl;

    srand(0);
    for(i=0; i<6; i++) // ziehe nacheinander sechs Zahlen
    {
        do  // wiederhole die Ziehung, bis die neue Zahl
        {   // nicht mit einer der vorigen identisch ist.
            lotto[i] = rand() % 49 + 1;
            neueZahl = true; // positive Grundeinstellung
            for (j=0; j<i; j++)
            {  // durchlaufe alle bisher gezogenen Kugeln
                if (lotto[j]==lotto[i])
                { // Hier wurde ein Doppelter entdeckt
                    neueZahl = false;
                }
            }
        } while (!neueZahl);
    }
    for (i=0; i<6; i++)
    {
        cout << lotto[i] << " ";
    }
    cout << endl;
}
Die äußere Schleife mit der Variablen i als Index durchläuft die Anzahl der zu ziehenden Lottozahlen. Die Ziehung selbst erfolgt innerhalb der do-while-Schleife, denn sie soll so lange wiederholt werden, bis man eine Zahl findet, die bisher noch nicht gezogen wurde. Die Entscheidung kann also erst nach der Aktion im Schleifenkörper gefällt werden. Damit ist eine fußgesteuerte Schleife die Idealbesetzung. Nach der Ziehung erfolgt die Prüfung. In einer inneren for-Schleife werden alle bisher gezogenen Zahlen durchlaufen. Das bedeutet, der Index j startet bei 0 und bleibt kleiner als i, d. h. kleiner als der Index für die aktuell gezogene Zahl. Im Falle einer Gleichheit mit den bisher gezogenen Zahlen wird die boolesche Variable neueZahl auf false gesetzt. Das führt zu einer Wiederholung der Ziehung. Nach jeder Ziehung muss diese Variable auf true gesetzt werden, sonst wird die Schleife nie verlassen, sobald einmal eine Doppelziehung entdeckt wurde.

Index und Größe

Wie schon erwähnt, beginnt jedes Array mit dem Index 0. Das ist für den allgemeinen Sprachgebrauch oft irritierend, da das erste Element nicht den Index 1, sondern den Index 0 hat. Ein Array mit sechs Elementen hat 5 als höchsten Index. Um die Verwirrung komplett zu machen: Bei der Deklaration wird bei einem Array mit sechs Elementen wiederum eine 6 zwischen die rechteckigen Klammern geschrieben. Beim Zugriff darf zwischen den eckigen Klammern aber maximal eine 5 stehen. Diese Zusammenhänge sollten Ihnen in Fleisch und Blut übergehen.

int lotto[6];
lotto[0] = 1; // erstes Element!
...
lotto[5] = 3; // sechstes Element, ok!
lotto[6] = 4; // Das siebte Element: Fehler!!!

Achtung!

Ein solcher Griff über den Rand eines existierenden Arrays wird weder vom Compiler verhindert noch während der Laufzeit erkannt. Aus diesem Grund ist es besonders wichtig, dass Sie kontrollieren, ob sich das Programm an die Grenzen der Arrays hält. Bevor Sie über den immer schlechteren Service in der Dienstleistungswüste lamentieren, sollten Sie bedenken, dass eine Kontrolle dieser Grenzen nicht umsonst zu haben ist. Das Laufzeitsystem müsste bei jedem Array-Zugriff prüfen, ob die Grenzen überschritten sind. Damit würde die Performance jedes Programms darunter leiden, dass einige Programmierer nicht aufpassen können, ob ihre Arrays nicht überschritten werden. Die Sprache C++ überlässt Ihnen die Verantwortung und bietet Ihnen als Entschädigung höchstmögliche Geschwindigkeit beim Zugriff.

for-Schleife

Bereits zu Anfang des Abschnitts wurde die for-Schleife zur Bearbeitung von Arrays verwendet. Die for-Schleife und die Arrays gehören quasi zusammen. Es sind nur wenige Handgriffe zu beachten, um mit einer solchen Schleife ein Array zu durchlaufen. Zunächst muss die Indexvariable bei 0 beginnen. In der Bedingung muss die Indexvariable kleiner als die Größe des Arrays bleiben. In der Schlussanweisung wird die Indexvariable einfach hochgezählt. Besonders deutlich wird dies, wenn die Größe durch eine Konstante festgelegt wird, in diesem Fall MAXLOTTO.

const int MAXLOTTO=6;
int lotto[MAXLOTTO];
for (int i=0; i<MAXLOTTO; i++)
{
    lotto[i] = ...
}

Initialisierung

Auch Array-Variablen können initialisiert werden. Dazu werden die Konstanten für die Werte der einzelnen Felder durch Kommata getrennt und in geschweiften Klammern zusammengefasst.

const int MAXLOTTO=6;
int lotto[MAXLOTTO] = { 12, 7, 45, 2, 21, 9 };

Hier muss darauf hingewiesen werden, dass das nur bei einer Initialisierung funktioniert und nicht bei einer Zuweisung. Sie können an anderer Stelle im Programm einem Array nicht auf diese Art Werte zuweisen, sondern müssen dies elementweise tun.

Null-Initialisierung

Eine Besonderheit ist die Initialisierung mit Nullwerten. Sie können in C++ jede Datenstruktur bei ihrer Definition mit Nullwerten füllen, indem Sie sie mit {0} initialisieren. Dadurch wird der gesamte Speicherraum, den die Variable einnimmt, auf 0 gesetzt.

const int MAXLOTTO=6;
int lotto[MAXLOTTO] = {0};

Speicherkosten

Um die Größe des belegten Speichers zu ermitteln, können Sie auch bei Arrays die Pseudofunktion sizeof verwenden. Das folgende Beispiel zeigt, wie damit die Größe eines Arrays, die Größe eines Elements und die Anzahl der Elemente ermittelt werden kann.

#include <iostream>
using namespace std;
int main()
{
    double c[13];
    cout << "Gesamtspeicherbedarf von double c[13]: ";
    cout << sizeof(c) << endl;
    cout << "Speicherbedarf eines Elements: ";
    cout << sizeof(c[0]) << endl;
    cout << "Anzahl der Elemente: ";
    cout << sizeof(c) / sizeof(c[0]) << endl;
}

Beispiel Bubblesort

Gleichartige Daten in größerem Umfang wird man meist sortiert ausgeben wollen. So wird Ihnen das Sortieren in Ihrem Programmiererleben immer wieder begegnen. Aus diesem Grunde verfügt die Standard C++-Bibliothek auch über ein Sortierverfahren. Dieses würden Sie vermutlich auch sinnvollerweise in der Praxis einsetzen. Dennoch ist es nicht verkehrt, als Übung für den Umgang mit Arrays einmal unter die Haube zu schauen und einen einfachen Sortieralgorithmus selbst zu programmieren.

Anschaulich

Damit Sie sich vorstellen zu können, wie ein Computer sortiert, sollten Sie fünf oder zehn Karten offen vor sich auf den Tisch legen und überlegen, wie ein Verfahren abläuft, das dazu führt, dass schließlich alle Karten sortiert vor Ihnen liegen. Dabei müssen Sie berücksichtigen, dass der Computer immer nur zwei Karten direkt miteinander vergleichen kann.

Schauen und tauschen

Ein praktikabler Weg ist es, alle Karten durchzugehen und jede mit ihrem rechten Nachbarn zu vergleichen. Ist die rechte Karte kleiner als die linke, so werden sie getauscht. Die folgende Darstellung zeigt, wie fünf Karten in einem Durchlauf miteinander verglichen werden. Die Karten, die miteinander verglichen werden, sind unterstrichen, und die Pfeile sollen andeuten, an welche Position die Karten nach dem Vergleich kommen.

Sie sehen, dass sich die Zahlen bereits im ersten Durchlauf dahingehend geändert haben, dass nun die höchste Zahl nach rechts gewandert ist. Alle anderen Zahlen sind noch unsortiert, so dass der Vorgang wiederholt werden muss. Im nächsten Schritt müssen allerdings nur noch die ersten drei Karten bearbeitet werden, weil die höchste Karte ja bereits an der richtigen Position liegt. Auch im zweiten Durchgang wandert die höchste der verbleibenden Karten nach rechts. Zu guter Letzt müssen nur noch die ersten beiden Karten getauscht werden.

Umsetzung

Der Bubblesort benötigt also zwei ineinander verschachtelte Schleifen. Die äußere Schleife gibt die Anzahl der Vergleiche vor. Im ersten Lauf startet sie mit drei Vergleichen. Da die Karte immer mit dem rechten Nachbarn verglichen wird, darf sie nicht mit vier beginnen. In der zweiten Runde brauchen wir zwei Vergleiche und in der letzten Runde nur noch einen. Also zählt die äußere Schleife rückwärts. Die innere Schleife läuft immer vom linken Rand bis zur Begrenzung durch die äußere Schleife, also deren Index. Im Listing sieht das so aus:

for (i=MAX-1; i>0; i--)
{
    for (j=0; j<i; j++)
    {
        ...
    }
}
Anstatt die Lösung im Listing zu entwickeln, ist es vielleicht klüger, ein Nassi-Schneidermann-Diagramm zu verwenden. Innerhalb der inneren Schleife wird das Feld mit seinem rechten Nachbarn verglichen. Ist das linke Feld größer als das rechte, müssen sie getauscht werden.

Die im Buch erscheinende Grafik Struktogramm Bubblesort (bubblediagramm) fehlt hier.

Nun muss das Struktogramm nur noch in C++-Code umgesetzt werden. Das folgende Listing zeigt das Programm inklusive der Erzeugung von zufälligen Testdaten und der Bildschirmausgaben.

#include <iostream>
using namespace std;
#include <stdlib.h>

const int MAX=5;

int main()
{
    int feld[MAX], hilf;
    int i, j, k;

    srand(56); // Zufallsgenerator vorbereiten
    for (i=0; i<MAX; i++)
    { // Array besetzen und anzeigen
        feld[i] = rand() % 100 + 1;
        cout << feld[i] << " ";
    }
    cout << endl;

    for(i=MAX-1; i>0; i--)
    {
        for (j=0; j<i; j++)
        {
            cout << "(" << j << "-" << j+1 << "): " ;
            if (feld[j]>feld[j+1])
            { // Tauschen erforderlich
                hilf = feld[j];
                feld[j] = feld[j+1];
                feld[j+1] = hilf;
            }
            cout << feld[j] << " - " << feld[j+1] << "  ";
        }
        // Zeige das Array in diesem Durchlauf
        cout << endl << MAX-i << ". Durchlauf beendet: ";
        for (k=0; k<MAX; k++)
        {
            cout << feld[k] << " ";
        }
        cout << endl;
    }

}

Bildschirmausgabe

Die folgenden Zeilen zeigen einen Probelauf des Programms. In der ersten Zeile sehen Sie die zufällig erzeugten Array-Werte. In der folgenden Zeile sehen Sie, wie die Werte verglichen werden. In der Klammer stehen jeweils die beteiligten Indizes, dahinter die Elemente nach dem Tausch. Nach jedem Durchlauf wird Bilanz gezogen und Sie sehen, dass jeweils das größte Element nach rechts wandert.
80 91 14 78 17
(0-1): 80 - 91  (1-2): 14 - 91  (2-3): 78 - 91  (3-4): 17 - 91
1. Durchlauf beendet: 80 14 78 17 91
(0-1): 14 - 80  (1-2): 78 - 80  (2-3): 17 - 80
2. Durchlauf beendet: 14 78 17 80 91
(0-1): 14 - 78  (1-2): 17 - 78
3. Durchlauf beendet: 14 17 78 80 91
(0-1): 14 - 17
4. Durchlauf beendet: 14 17 78 80 91

Übung

Zuweisung von Arrays

Kein L-Value

Ein Array gehört zu den wenigen Variablenkonstrukten, die vom Standard nicht als L-Value akzeptiert werden. Sie dürfen also nicht auf der linken Seite einer Zuweisung stehen. Arrays sind darum auch nicht als Rückgabewert von Funktionen zugelassen.

GNU-Compiler

Bei manchen Compilern wie dem GNU-Compiler ist es dennoch möglich, komplette Arrays mit einer einzigen Zuweisung zu kopieren. Das funktioniert allerdings nur bei gleichdimensionalen Arrays gleichen Typs. Bei der Zuweisung werden die eckigen Klammern weggelassen, da ansonsten ja nur einzelne Elemente kopiert würden.

int quelle[MAX];
int ziel[MAX];
ziel = quelle; // kein Standard!

L-Value

Andere Compiler, wie beispielsweise Visual C++ von Microsoft, werden dagegen eine Fehlermeldung ausgeben, dass die Variable ziel kein L-Value sei. Wollen Sie also ein Array kopieren, müssen Sie dies in einer Schleife tun.

int quelle[MAX];
int ziel[MAX];
int i;

for (i=0; i<MAX; i++)
{
    ziel[i] = quelle[i];
}

memcpy

Wenn Sie ein Array schnell in ein anderes Array gleichen Typs und gleicher Größe kopieren wollen, können Sie die Funktion memcpy() verwenden. Die Funktion kopiert einen beliebigen Speicherbereich Byte für Byte. Der erste Parameter ist das Ziel, der zweite die Quelle und der dritte die Größe. Der Vorteil von memcpy() liegt in der hohen Geschwindigkeit. Der Nachteil ist, dass der Compiler nicht kontrolliert, ob Größe und Typ von Quelle und Ziel zueinander passen. Für das Beispiel sieht der Aufruf so aus:

#include <string.h>
int quelle[MAX];
int ziel[MAX];
memcpy(ziel, quelle, sizeof(quelle));

Da diese Variante recht fehleranfällig ist, sollten Sie wirklich nur darauf zurückgreifen, wenn Geschwindigkeit um jeden Preis gefragt ist. Um die Funktion memcpy() verwenden zu können, müssen Sie die Datei string.h einbinden.

C-Zeichenketten

Ein wichtiges Anwendungsgebiet des Arrays ist die Aneinanderreihung von Buchstaben. Dadurch können Wörter, Sätze und andere Texte gespeichert werden. Man nennt eine solche Folge von Buchstaben einen String. Einen String als ein char-Array zu realisieren ist recht nahe liegend. Aus diesem Grunde wurde dies bereits in den ersten Versionen von C so umgesetzt. Allerdings hat ein String durch seine Umsetzung als Array auch diverse Nachteile. So kann ein String nicht durch einfache Zuweisung in eine andere Stringvariable kopiert werden, da ein Array ja kein L-Value ist. Darum wurde in der C++-Standardbibliothek ein eigener Typ namens string zur Verfügung gestellt. Die Zeichenketten, die auf einem char-Array basieren, werden in Abgrenzung dazu auch C-Strings genannt. Sie sind aber nach wie vor auch in C++ gegenwärtig. Immerhin sind die Zeichenkettenkonstanten als C-Strings definiert. Auch viele Schnittstellen sind als char-Array implementiert. Da auch der neue Typ string Möglichkeiten anbietet, beide Formen ineinander zu überführen, können sie nebeneinander existieren. Insofern löst der Typ string den klassischen C-String nicht ab, sondern ergänzt ihn.

Feste Größe

Da C-Strings normale Arrays sind, können Sie auch deren Größe nicht während der Laufzeit ändern. Darum wird ein Programmierer das Array so groß dimensionieren, dass der Text auch im schlechtesten Fall noch hineinpasst.

Endemarkierung

Um auch Texte in einem Array ablegen zu können, die kürzer sind als die Größe des Arrays, wird an seinem Ende eine 0 abgelegt. Dabei handelt es sich nicht um eine Ziffer '0', sondern um ein Nullbyte, das als '\0' oder als Zahlenkonstante 0 dargestellt wird. Eine Stringkonstante impliziert immer als letztes Zeichen diese Abschluss-Null. Damit ist der leere String ("") nicht wirklich leer. Er enthält bereits ein Element, die 0. Im folgenden Beispiel wird ein C-String definiert und mit einer Stringkonstanten initialisiert:

char Vorname[6] = "Kai";
In diesem Fall sind sechs Zeichen für das Array reserviert worden. In den ersten drei Elementen befinden sich die Buchstaben 'K', 'a' und 'i'. Im vierten Element mit dem Index 3 befindet sich eine 0. Der Zustand der restlichen zwei Elemente ist unbestimmt. Dort können noch alte Speicherleichen herumliegen, hier durch ein 'z' und ein 'M' angedeutet.

Dimensionierung durch Initialisierung

Sie können die Größe eines Arrays auch durch die Initialisierung festlegen. In diesem Fall steht kein Wert zwischen den eckigen Klammern. Das Array wird dann so groß, wie es die Zeichenkettenkonstante benötigt. Dabei wird die Endekennung mitberechnet. Im folgenden Beispiel würde also ein vierelementiges Array angelegt. Eine nachträgliche Änderung der Größe ist nicht möglich.

char Vorname[] = "Kai";

Funktionen

Zu den C-Strings gibt es eine große Zahl von Hilfsfunktionen, die immer noch oft verwendet werden. In neueren Programmen wird allerdings mehr mit string gearbeitet.

Beispiel: Zahleneingabe auswerten

Die Umwandlung von Zeichenketten in Zahlenwerte ist ein immer wiederkehrendes Thema. Benutzereingaben liegen oft als Zeichenketten vor. Das betrifft sowohl die Übergabe von Aufrufparametern als auch bei grafischen Oberflächen, die den Inhalt ihrer Eingabefelder als Zeichenkette abliefern. Natürlich stellen bereits die C-Bibliotheken Funktionen wie beispielsweise atoi() zur Konvertierung zur Verfügung . Allerdings sind die Konvertierungsfunktionen nicht unbedingt sehr flexibel. Eine solche Konvertierung ist relativ einfach selbst geschrieben und kann später leicht erweitert werden.

Das folgende Beispiel liest eine Zeichenkette von der Konsole ein und konvertiert den Inhalt in eine long-Variable.

#include <iostream>
using namespace std;
const int MAX=256;
int main()
{
    char input[MAX];
    int i = 0;
    long Wert = 0;

    cin.getline(input, MAX);
    while (input[i]>='0' && input[i]<='9')
    {
        Wert *= 10;
        Wert += input[i] - '0';
        i++;
    }
    cout << Wert << endl;
}
Betrachten wir die wichtigsten Zeilen im Programm:

cin.getline(input, MAX);

Zeichenketteneingabe

Durch den Aufruf der Funktion cin.getline(input, MAX) wird eine Benutzereingabe eingeleitet und die komplette Zeile im char-Array input abgelegt. Die Eingabe wird auf MAX Zeichen begrenzt.

while (input[i]>='0' && input[i]<='9')

Schleife

Die while-Schleife läuft über der Eingabezeile, solange das aktuelle Zeichen im Bereich der Ziffern liegt. Das wird durch die Bedingung gesichert. Das erste Zeichen, das keine Ziffer ist, bewirkt sofort das Verlassen der Schleife. Damit ist gleichzeitig sichergestellt, dass nicht über das Ende der Zeichenkette hinaus gelaufen wird, weil die Abschluss-Null ja nicht im Bereich der Ziffern liegt.

Wert *= 10;
Wert += input[i] - '0';

Berechnung

Das Ergebnis wird in der Variablen Wert abgelegt. Zunächst ist es wichtig, dass Wert mit 0 initialisiert wurde. Innerhalb der Schleife wird bei jedem Durchlauf das bisherige Ergebnis mit zehn multipliziert und damit quasi eine Stelle nach links geschoben. Die neue Ziffer muss zwischen 0 und neun liegen. Durch das Subtrahieren von '0' wird erreicht, dass beispielsweise aus dem Buchstaben '2' die Zahl 2 wird. Dieser Wert wird dann zu dem bisherigen Wert addiert. Lassen Sie uns das einmal am Beispiel der Zeichenkette "735" durchspielen.

i Wert input[i] Nach dem Befehl
0 0 '7' Wert *= 10;
0 7 '7' Wert += input[i] - '0';
1 7 '3' i++;
1 70 '3' Wert *= 10;
1 73 '3' Wert += input[i] - '0';
2 73 '5' i++;
2 730 '5' Wert *= 10;
2 735 '5' Wert += input[i] - '0';
3 735 '\0' i++;

Fließkommazahlen

Auf der Basis dieses Vorgehens ist es recht einfach, Fließkommazahlen einzulesen, deren Nachkommastellen durch ein Komma abgetrennt werden. Normalerweise geht C++ von der anglo-amerikanischen Schreibweise aus und verwendet den Punkt als Dezimaltrenner. Nach dem Durchlaufen der while-Schleife für die Vorkommastellen wird geprüft, ob das folgende Zeichen ein Komma ist. Dann wird fast die gleiche Schleife noch einmal durchlaufen. Allerdings wird in der Variablen NK der Zehnerexponent ermittelt, durch den die nächste Ziffer zu dividieren ist, bevor sie zu Wert hinzuaddiert werden kann.

#include <iostream>
using namespace std;
const int MAX=256;
int main()
{
    char input[MAX];
    int i=0;
    double Wert = 0;

    cin.getline(input, MAX);
    while (input[i]>='0' && input[i]<='9')
    {
        Wert *= 10;
        Wert += input[i] - '0';
        i++;
    }
    if (input[i]==',')
    {
        double NK = 1;
        i++;
        while (input[i]>='0' && input[i]<='9')
        {
            NK *= 10;
            Wert += (input[i]-'0')/NK;
            i++;
        }
    }
    cout << input << Wert << endl;
}

Übung

Die Lösungen zu den Übungsaufgaben finden sich im Buch.

Mehrere Dimensionen

Definition

C++ ermöglicht auch mehrdimensionale Arrays. Bei der Definition eines mehrdimensionalen Arrays wird für jede Dimension eine weitere rechteckige Klammer angehängt. Eine solche Konstruktion ist ein Array von Arrays.

double matrix[MAXSPALTEN][MAXZEILEN];
Hier wird ein zweidimensionales Array definiert. Es gibt MAXZEILEN viele Zeilen, die jeweils wiederum MAXSPALTEN viele Spalten besitzen. Das Array nimmt einen Speicherraum von sizeof(double) * MAXSPALTEN * MAXZEILEN ein.

Zugriff

Auch der Zugriff auf das zweidimensionale Array erfolgt über zwei rechteckige Klammern. Wenn Sie das Element der vierten Spalte und der dritten Zeile verwenden wollen, greifen Sie durch matrix[3][2] darauf zu.

Beispiel: Bermuda

Als Programmierbeispiel für zweidimensionale Arrays bieten sich viele Spiele an, die ein zweidimensionales Spielfeld besitzen. Das hier vorgestellte Computerspiel Bermuda ähnelt dem klassischen Schiffeversenken, ist aber etwas anspruchsvoller. Das Spiel wird Ihnen in diesem Buch an verschiedenen Stellen begegnen. Hier implementieren wir zunächst das Spielfeld als Beispiel für mehrdimensionale Arrays.

Spielanleitung

In einem Spielfeld von neun mal sieben Feldern sind vier Schiffe versteckt. Der Spieler kann jede Position anfunken. Er bekommt entweder die Mitteilung, dass hier ein Schiff gefunden wurde, oder den Hinweis, in wie viele Richtungen von dieser Position aus Schiffe zu sehen sind. Dabei wird nach links, rechts, oben, unten und in alle vier diagonalen Richtungen so lange gepeilt, bis das erste Schiff entdeckt oder der Rand des Spielfelds erreicht wurde. Zwei Schiffe, die hintereinander in einer Richtung liegen, werden nur als ein Schiff gezählt.

Spielfeld anzeigen

Der erste Schritt besteht darin, das Spielfeld zu definieren. In einem zweidimensionalen Array werden die bisherigen Rateversuche des Spielers gespeichert. Für die Darstellung der Rateversuche eignet sich der Datentyp char am besten. Das folgende Listing zeigt, wie das Feld zunächst mit Punkten initialisiert wird. Anschließend läuft das Programm in eine Schleife, in der das Spielfeld angezeigt wird und der Anwender die Koordinaten eingeben kann. Dazu gibt er die Zeilennummer direkt gefolgt vom Spaltenbuchstaben an, beispielsweise 2C. Alle durch die eingegebenen Koordinaten bezeichneten Felder werden durch ein x im Spielfeld markiert. Auf diese Weise haben Sie eine Rückmeldung, ob auch wirklich die richtigen Koordinaten angesprochen wurden.

#include <iostream>
using namespace std;

const int X=9;
const int Y=7;

int main()
{
char Spielfeld[X][Y];
int x, y;
char cx, cy;


    for (x=0; x<X; x++)
    {
        for (y=0; y<Y; y++)
        {
            Spielfeld[x][y] = '.';
        }
    }
    // Anzeige und Eingabe
    bool SchleifenEnde=false;
    int xin, yin;
    do
    {
        cout << "      1  2  3  4  5  6  7  8  9" << endl;
        for (y=0; y<Y; y++)
        {
            cout << (char)('A'+y) << "   ";
            for (x=0; x<X; x++)
            {
                cout << "  " << Spielfeld[x][y];
            }
            cout << endl;
        }
        cin >> cx >> cy;
        xin = cx - '1';
        yin = cy - 'A';
        if (xin>=0 && xin<9 && yin>=0 && yin<7)
        {
            Spielfeld[xin][yin] = 'x';
        }
        else
        {
             SchleifenEnde = true;
        }
    }
    while (!SchleifenEnde);
}
Wenn das Programm gestartet wird, zeigt es das Spielfeld umgeben von den Koordinaten. Das Programm stoppt zur Eingabeaufforderung. Nach der Eingabe von 2C -- gefolgt von Return -- erscheint an der entsprechenden Stelle ein x auf dem Bildschirm. Die Eingabe 6F führt dazu, dass an den beiden Stellen je ein x steht. Mit irgendeiner Eingabe, die keiner Koordinate entspricht, wird das Programm beendet.
     1  2  3  4  5  6  7  8  9
A    .  .  .  .  .  .  .  .  .
B    .  .  .  .  .  .  .  .  .
C    .  .  .  .  .  .  .  .  .
D    .  .  .  .  .  .  .  .  .
E    .  .  .  .  .  .  .  .  .
F    .  .  .  .  .  .  .  .  .
G    .  .  .  .  .  .  .  .  .
2C
     1  2  3  4  5  6  7  8  9
A    .  .  .  .  .  .  .  .  .
B    .  .  .  .  .  .  .  .  .
C    .  x  .  .  .  .  .  .  .
D    .  .  .  .  .  .  .  .  .
E    .  .  .  .  .  .  .  .  .
F    .  .  .  .  .  .  .  .  .
G    .  .  .  .  .  .  .  .  .
6F
     1  2  3  4  5  6  7  8  9
A    .  .  .  .  .  .  .  .  .
B    .  .  .  .  .  .  .  .  .
C    .  x  .  .  .  .  .  .  .
D    .  .  .  .  .  .  .  .  .
E    .  .  .  .  .  .  .  .  .
F    .  .  .  .  .  x  .  .  .
G    .  .  .  .  .  .  .  .  .
xx


[2] Arrays sind auch nicht als Rückgabewert von Funktionen zugelassen).