Strukturen

Oft ist es sinnvoll, nicht nur wie in einem Array Daten desselben Datentyps zusammenzufassen, sondern auch Daten verschiedener Datentypen. Das ist mit Klassen möglich, die zusammen mit der objektorientierten Programmierung noch ausführlich behandelt werden. Hier werden (nur) einige Grundbegriffe in Zusammenhang mit sogenannten Strukturen betrachtet.

Der Datentyp struct

Eine mit dem Schlüsselwert struct definierte Klasse ist ein Datentyp, der auch als Struktur bezeichnet wird. Die wie Variablen zwischen den geschweiften Klammern aufgeführten Elemente einer solchen Klasse werden auch als Datenelemente bezeichnet.

Beispiel 1a: Der umgangssprachliche Begriff „Kalenderdatum“ steht für drei Werte, die einen Tag, einen Monat und ein Jahr bezeichnen. Ein solches Datum kann durch die Klasse CDatum mit den Datenelementen Tag, Monat und Jahr dargestellt werden:

struct CDatum 
{int Tag;
 int Monat;
 int Jahr;
}; // Das Semikolon ist hier notwendig.

Mit diesem Datentyp kann man wie mit einem der vordefinierten Datentypen int usw. eine Variable des Datentyps CDatum definieren:

CDatum d;

Gibt man vor dem Semikolon, das eine Klassendefinition abschließt, einen Bezeichner an, ist dieser eine Variable des zugehörigen Klassentyps. Lässt man den Namen der Klasse aus, erhält man eine Variable des Klassentyps. Der Datentyp hat in diesem Fall keinen eigenen Namen.

Beispiel 1b: Die Definition

struct CDatum 
{int Tag;
 int Monat;
 int Jahr;
} d; // d ist eine Variable des Datentyps CDatum.

ist gleichwertig mit den beiden Definitionen aus dem letzten Beispiel. Durch

struct  // Dieser Datentyp hat keinen eigenen Namen.
{int Tag; 
 int Monat;
 int Jahr;
} d; // Der Datentyp von d ist anonym

wird nur eine Variable des Klassentyps definiert. Dieser hat aber keinen eigenen Namen.

Normalerweise wählt man für eine Klasse einen Namen, der genau dem Konzept entspricht, das sie darstellen soll, wie z.B. Datum für ein Kalenderdatum. Da dieser Name aber später noch für eine Variable dieses Typs verwendet werden soll, wurde CDatum gewählt. Mit englischen Namen kann man solche Namenskonflikte durch Groß- und Kleinschreibung umgehen (Date für die Klasse und date für eine Variable des Klassentyps).

Eine Zusammenfassung inhaltlich zusammengehöriger Daten zu einer Klasse kann zur Verständlichkeit eines Programms beitragen und ist generell empfehlenswert.

Beispiel 2: Die Zusammengehörigkeit und Bedeutung der Daten kommt in

struct CKreis
{int x,y,r;
} k1,k2;

unmittelbar zum Ausdruck. Findet man dagegen die Definitionen

int x1, x2, y1, y2, r1, r2;

in einem Programm, das man nicht selbst geschrieben hat, lassen sich nur aus der Verwendung der Variablen Rückschlüsse auf ihre Bedeutung ziehen. Das kann aber bei einem größeren Programm ziemlich aufwendig werden, insbesondere wenn keine zusätzlichen Kommentare die Bedeutung der Daten erklären.

Der etwas höhere Schreibaufwand für Strukturen wird durch die Ersparnis an Kommentaren und die leichtere Verständlichkeit meist kompensiert.

Typische Anwendungen von Strukturen findet man auch in der betriebswirtschaftlichen Datenverarbeitung. Dabei werden oft die einzelnen Zeilen einer Tabelle durch eine Struktur dargestellt, die auch als Datensatz bezeichnet wird. Aus solchen Strukturen werden dann Arrays oder Dateien aufgebaut. Datenbanken sind im Wesentlichen eine mehr oder weniger große Anzahl von Dateien, die alle aus solchen Datensätzen bestehen.

Verschachtelung von Strukturen

Beispiel 3a: Eine Zeile der Tabelle

bck-240.jpg

kann für eine Kontobewegung stehen und durch die folgende Struktur dargestellt werden:

struct Kontobewegung 
{int KontoNr;
 char NameInhaber[20];
 ...
 double Betrag; 
}; 

Dieser Datensatz enthält mit dem Datum einen weiteren Datensatz. Will man dieses Datum unter einem eigenständigen Begriff ansprechen, kann man es innerhalb der Struktur ebenfalls als Struktur definieren:

struct Kontobewegung 
{int KontoNr;
 char NameInhaber[20];
 struct 
 {int Tag;
  int Monat;
  int Jahr;
 } Datum;
 char BewArt;
 double Betrag;
};

Die Verschachtelung von Strukturen kann im Prinzip unbegrenzt fortgesetzt werden. Wenn zuvor ein Name für die Datenstruktur vereinbart wurde, kann auch dieser verwendet werden:

struct Kontobewegung
{int KontoNr;
 char NameInhaber[20];
 CDatum Datum;
 char BewArt;
 double Betrag;
};

Zugriff auf Datenelemente einer Struktur

Eine Variable, deren Datentyp eine Klasse ist, enthält alle Elemente, die bei der Definition der Klasse angegeben wurden. Diese Elemente werden durch den Namen der Variablen angesprochen, auf den ein Punkt und der Name des Elements folgt. Jeder so gebildete Ausdruck ist ein Ausdruck des Datentyps, der bei der Definition des Elements angegeben wurde.

Beispiel 3b: Nach der Definition

Kontobewegung k;

kann man die Elemente der Variablen k folgendermaßen ansprechen:

k.KontoNr // ein Ausdruck des Datentyps int
k.Datum // ein Ausdruck des Datentyps CDatum

Die drei Elemente von K.Datum lassen sich einzeln ansprechen durch

k.Datum.Tag // ein Ausdruck des Datentyps int
k.Datum.Monat
k.Datum.Jahr

Für den Compiler wird durch jede Definition einer Klasse ein neuer Datentyp erzeugt. Deshalb werden durch die beiden Klassendefinitionen in

struct { int i;} s1;
struct { int i;} s2;

zwei verschiedene Datentypen erzeugt, obwohl man auch erwarten könnte, dass die Datentypen von s1 und s2 gleich sind. Da die Datentypen von s1 und s2 verschieden sind, verweigert der Compiler die Übersetzung der Zuweisung

s1=s2; // Fehler: Konvertierung nicht möglich

Verwendet man dagegen denselben Klassennamen bei der Definition von Variablen, haben sie denselben Datentyp und können einander zugewiesen werden. Nach der Definition

Kontobewegung k1;
Kontobewegung k2;

wird die folgende Zuweisung vom Compiler akzeptiert:

k1=k2;

Struktur - eine zusammengesetzter Datentyp

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};

Beispiel Schule

#include <iostream>
using namespace std;
 
struct PC
{
	string Marke;
	double CPUGeschwindigkeit;
	int RAM;
	int Festplatte;
};
 
struct Raum
{
	int AnzPCs;
	string Name;
	double Groesse;
	int AnzTische;
	PC computer[20];
};
 
struct Schule
{
	string Name;
	string Adresse;
	int AnzSchueler;
	string Administrator;
	double Groesse;
	int AnzLehrer;
	int Gruendungsjahr;
	Raum rarr[100];    //Array von Räumen
	Raum externeraeume[5];
};
 
int main(int argc, char** argv)
{
 
    Schule s;
	s.Name="BG BRG Amstetten";
	s.Adresse="Anzengruberstraße 6";
	s.Gruendungsjahr=1938;
	s.AnzLehrer=90;
	s.AnzSchueler=726;
	s.Administrator="MMag. Matthias Haslauer";
	s.rarr[0].Name="Sekretariat";
	s.rarr[0].AnzTische=2;
	s.rarr[0].Groesse=30;
	s.rarr[0].AnzPCs=2;
	s.rarr[1].Name="TUS1";
	s.rarr[1].AnzTische=0;
	s.rarr[1].Groesse=500;
	s.rarr[1].AnzPCs=0;
	s.rarr[2].Name="TUS2";
	s.rarr[2].AnzTische=0;
	s.rarr[2].Groesse=300;
	s.rarr[2].AnzPCs=0;
	s.rarr[0].computer[0].CPUGeschwindigkeit=3.07;
	s.rarr[0].computer[0].Marke="HP";
    s.rarr[0].computer[0].RAM=4;
   	s.rarr[0].computer[0].Festplatte=256;
 
 
 
	for (int i=0;i<3;i++)
	{
		cout << "Raum: " << s.rarr[i].Name << endl;
		cout << "Anzahl Tische: " << s.rarr[i].AnzTische << endl;
		cout << "Groesse: " << s.rarr[i].Groesse << endl;
		cout << "Anzahl PCs: " << s.rarr[i].AnzPCs << endl;
		cout << "===============";	
	}
}

Aufgaben Strukturen