Der Datenverbund: struct
Willemers Informatik-Ecke

Zusammengesetzter Typ

Mit Arrays können Variablen gleichen Typs zusammengestellt werden. In der realen Welt gehören aber meist Daten unterschiedlichen Typs zusammen. So hat ein Auto einen Markennamen und eine Typbezeichnung, die als Zeichenkette unterzubringen ist. Dagegen eignet sich für Kilometerzahl und Leistung eher der Typ Integer. Für den Preis bietet sich der Typ float an. Bei bestimmten Autohändlern könnte auch double erforderlich sein. Alles zusammen beschreibt ein Auto.

Modell

Vielleicht werden Sie einwerfen, dass ein Auto noch mehr Bestandteile hat. Da gibt es Bremsscheiben, Turbolader und Scheibenwischer. Das ist in der Realität richtig. Ein Programm interessiert sich aber immer nur für bestimmte Eigenschaften, die der Programmierer mit dem Kunden zusammen festlegt. Unser Beispiel würde für einen kleinen Autohändler vielleicht schon reichen. Eine Autovermietung interessiert sich vielleicht überhaupt nicht für den Wert des Autos, aber möchte festhalten, ob es für Nichtraucher reserviert ist. Eine Werkstatt dagegen könnte sich tatsächlich für alle Teile interessieren. Ein Programm, das die Verteilung der Firmenfahrzeuge verwaltet, interessiert sich vielleicht nur für das Kennzeichen. Es entsteht also ein Modell eines Autos, das bestimmte Bestandteile enthält und andere vernachlässigt, je nachdem was das Programm benötigt. Bereits in C gab es für solche Zwecke die Struktur, die mehrere Variablen zu einer zusammenfasst. Das Schlüsselwort für die Bezeichnung solch zusammengesetzter Variablen lautet struct. Nach diesem Schlüsselwort folgt der Name des neuen Typen. In dem folgenden geschweiften Klammernblock werden die Bestandteile der neuen Struktur aufgezählt. Diese unterscheiden sich nicht von der bekannten Variablendefinition. Den Abschluss bildet ein Semikolon.

struct

Um ein Auto zu modellieren, wird ein neuer Variablentyp namens TAutoTyp geschaffen, der ein Verbund mehrerer Elemente ist.

struct TAutoTyp // Definiere den Typ
{
    char Marke[MaxMarke];
    char Modell[MaxModell];
    long km;
    int kW;
    float Preis;
};  // Hier vergisst man leicht das Semikolon!

Syntaxbeschreibung

Das Schlüsselwort struct leitet die Typdefinition ein. Es folgt der Name des neu geschaffenen Typs, hier TAutoTyp. In dem nachfolgenden geschweiften Klammerpaar werden alle Bestandteile der Struktur nacheinander aufgeführt. Am Ende steht ein Semikolon, das man selbst als erfahrener Programmierer immer wieder einmal vergisst.

Variablendefinition

Damit haben wir den Datentyp TAutoTyp geschaffen. Er kann in vieler Hinsicht verwendet werden wie der Datentyp int. Sie können beispielsweise eine Variable von diesem Datentyp anlegen. Ja, Sie können sogar ein Array und einen Zeiger von diesem Datentyp definieren.

TAutoTyp MeinRostSammler; // Variable anlegen
TAutoTyp Fuhrpark[100];   // Array von Autos
TAutoTyp *ParkhausKarte;  // Zeiger auf ein Auto

Elementzugriff

Die Variable MeinRostSammler enthält nun alle Informationen, die in der Deklaration von TAutoTyp festgelegt sind. Um von der Variablen auf die Einzelteile zu kommen, wird an den Variablenname ein Punkt und daran der Name des Bestandteils gehängt.

// Auf die Details zugreifen
MeinRostSammler.km = 128000;
MeinRostSammler.kW = 25;
MeinRostSammler.Preis = 25000.00;

Zeigerzeichen

Wenn Sie über einen Zeiger auf ein Strukturelement zugreifen wollten, müssten Sie über den Stern referenzieren und dann über den Punkt auf das Element zugreifen. Da aber der Punkt vor dem Stern ausgewertet wird, müssen Sie eine Klammer um den Stern und den Zeigernamen legen.

TAutoTyp *ParkhausKarte = 0;      // Erst einmal keine Zuordnung
ParkhausKarte = &MeinRostSammler; // Nun zeigt sie auf ein Auto
(*ParkhausKarte).Preis = 12500;   // Preis für MeinRostSammler

Das mag zwar logisch sein, aber es ist weder elegant noch leicht zu merken. Zum Glück gibt es in C und C++ eine etwas hübschere Variante, über einen Zeiger auf Strukturelemente zuzugreifen. Dazu wird aus Minuszeichen und Größer-Zeichen ein Symbol zusammengesetzt, das an einen Pfeil erinnert.

ParkhausKarte->Preis = 12500;

L-Value

Strukturen sind L-Values. Sie können also auf der linken Seite einer Zuweisung stehen. Andere Strukturen des gleichen Typs können ihnen zugewiesen werden. Dabei wird die Quellvariable Bit für Bit der Zielvariable zugewiesen.

TAutoTyp MeinNaechstesAuto, MeinTraumAuto;
MeinNaechstesAuto = MeinTraumAuto;
Trotzdem die beiden Strukturvariablen nach dieser Operation ganz offensichtlich gleich sind, kann man dies nicht einfach durch eine Anwendung des doppelten Gleichheitszeichen nachprüfen. Sie können bei Strukturen die Typdeklaration und die Variablendefinition zusammenfassen, indem der Name der Variablen direkt nach der geschweiften Klammer eingetragen wird.

struct // hier wird kein Typ namentlich festgelegt
{
    char Marke[MaxMarke];
    char Modell[MaxModell];
    long km;
    int kW;
    float Preis;
} MeinErstesAuto, MeinTraumAuto;
Hier werden im Beispiel die Variablen MeinErstesAuto und MeinTraumAuto gleich mit ihrer Struktur definiert. Werden auf diese Weise gleich Variablen dieser Struktur gebildet, muss ein Name für den Typ nicht unbedingt angegeben werden. Damit ist dann natürlich keine spätere Erzeugung von Variablen dieses Typs möglich.

Initialisierung

Auch Strukturen lassen sich initialisieren. Dazu werden wie bei den Arrays geschweifte Klammern verwendet. Auch hier werden die Werte durch Kommata getrennt.

TAutoTyp JB = {"Aston Martin", "DB5", 12000, 90, 12.95};
TAutoTyp GWB = {0};

Klassenähnlich

Die Struktur ist bereits mit C eingeführt worden und ist dort die einzige Möglichkeit, Datenverbünde zu definieren. In C++ werden zu diesem Zweck normalerweise Klassen eingesetzt, die allerdings wesentlich mehr können als die C-Strukturen. Schon aus Kompatibilität ist das Schlüsselwort in C++ noch vorhanden und wird auch erwartungsgemäß übersetzt. Allerdings kann eine Struktur in C++ wesentlich mehr. Sie ist dort so definiert, dass sie eine Klasse entspricht, deren Elemente öffentlich zugänglich sind.

Im Buch befindet sich an dieser Stelle der Syntaxgraf von struct (grafstruct).

Den Syntaxgraf für VarDef finden Sie auf Seite (grafvardef). An dieser Stelle befinden sich also Variablendefinitionen. Dabei sind auch Arrays, Zeiger oder weitere Strukturen zulässig. Als Vars dürfen Variablen von der Struktur aufgezählt werden. Hier dürfen Zeiger und Arrays gebildet werden. Bei der Definition mehrerer Variablen müssen sie durch Komma getrennt werden.

Beispiel: Bermuda

Nachdem das Spielfeld modelliert wurde, sollen hier die Schiffe nachgebildet werden. Jedes Schiff hat eine x- und eine y"=Koordinate. Des Weiteren müssen Sie ein Schiff markieren können, wenn es gefunden wurde. Also brauchen wir noch eine boolesche Variable namens gefunden. Um das Schiff in dieser Form zusammenzusetzen, wird eine Struktur verwendet. Nach der Spielanleitung gibt es im Spiel insgesamt vier Schiffe, sodass ein Array von Strukturen angelegt werden muss.

const int MaxSchiff=4;

struct tSchiff
{
    int x;
    int y;
    bool gefunden;
};

tSchiff Schiff[MaxSchiff];

Im nächsten Schritt müssen die Schiffe versteckt werden. Natürlich dürfen wie bei den Lottozahlen nicht zwei Schiffe die gleiche Position haben. Demzufolge ist der Algorithmus sehr ähnlich. Hier sehen Sie das komplette Programm:

// Bermuda: Einführung der Strukturen
#include <iostream>
using namespace std;
#include <stdlib.h>

const int X=9; // Spielfeldausdehnung waagerecht
const int Y=7; // Spielfeldausdehnung senkrecht
const int MaxSchiff=4; // Anzahl der Schiffe

struct tSchiff
{ // Nachbildung eines Schiffes
    int x; // Position waagerecht
    int y; // Position senkrecht
    bool gefunden;
};

tSchiff Schiff[MaxSchiff]; // Unsere Flotte

int main()
{
    int i, j; // Zählervariablen
    bool neueZahl; // ist eine Position gefunden?

    srand(0);
    for(i=0; i<MaxSchiff; i++) // alle Schiffe
    {
        Schiff[i].gefunden = false;
        // Bestimmung der Position
        do
        {
            // Einmal Position würfeln
            Schiff[i].x = rand() % X;
            Schiff[i].y = rand() % Y;
            neueZahl = true;
            // Prüfe, ob eines der bisherigen Schiffe die
            // Position schon innehat
            for (j=0; j<i; j++)
            {
                if (Schiff[j].x==Schiff[i].x
                    && Schiff[j].y==Schiff[i].y)
                {
                    // Da saß schon eins!
                    neueZahl = false;
                }
            }
        // erst bei neuer Position fertig
        } while (!neueZahl);
    }
    // Zur Kontrolle alle anzeigen
    for (i=0; i<MaxSchiff; i++)
    {
        cout << Schiff[i].x << "," << Schiff[i].y << " ";
    }
    cout << endl;
}

Nun sind die Datenstrukturen für das Bermuda-Programm geschaffen. Im nächsten Schritt wird es um das Gliedern der Funktionalität gehen.