Anfang   zurück

Benutzerdefinierte Datentypen

Über die normalen in C definierten Datentypen hinaus gibt es weitere komplexe Datentypen, die die Programmierung einfacher oder effizienter machen.

Aufzählungstyp

In manchen Fällen wäre ein Datentyp angenehm, der nur eine kleine Menge diskreter Werte annehmen kann, wobei diese Werte für den Leser noch mit eindeutigen Begriffen verbunden sind. Für eine Ampelsteuerung wäre es z.B. vorteilhaft eine Variable zu haben, die nur aus den fünf Werten AUS, ROT, GELB, GRUEN und BLINK besteht. Eine Realisierung dieser Forderung wäre mittels der bekannten Präprozessordirektive #define möglich, wobei aber eine nachträgliche (erlaubte) Neubelegung eines der Werte innerhalb des Programmes durchaus möglich ist. Diese Neubelegung würde aber auch den Programmablauf vollkommen verändern. Eine bessere Lösung bietet C mittels der Aufzählungstypen über die enum-Anweisung. Dabei folgt auf das Schlüsselwort enum der Name des Datentyps und zwischen geschweiften Klammern eine mit Kommas getrennte Liste der Werte. Im Programm zur Ampelsteuerung wäre also folgende Anweisung zu finden:
enum AMPEL {AUS, ROT, GELB, GRUEN, BLINK};
womit ein Aufzählungstyp AMPEL deklariert wird, mit dem eine anschließende Variablendeklaration möglich wird. Um eine Variable des Aufzählungstyps zu vereinbaren verwendet man:
enum AMPEL traffic_lights;
Damit wurde eine Variable traffic_lights mit den Werten AUS, ROT, GELB, GRUEN und BLINK erzeugt. Dir eigentlichen zugeordneten Größen für die Werte sind fortlaufende, mit Null beginnende, Ganzzahlen. Die Zuordnung ist also so, dass AUS = 0, ROT = 1, GELB = 2, GRUEN = 3 und BLINK = 4 gesetzt wird. Diese fortlaufende Abbildung auf mit 0 startende Ganzzahlen kann bei der Deklaration auch aufgehoben werden. Die Anweisung
enum AMPEL {AUS, ROT = 10, GELB, GRUEN = 20, BLINK};
bewirkt, dass AUS = 0, ROT = 10, GELB = 11, GRUEN = 20 und BLINK = 21 gesetzt wird. Nach einer speziellen Wertzuweisung wird für die nächste Größe vom aktuellen Wert weitergezählt. Zur Deklaration einer Variable des Aufzählungstyps muß immer das Schlüsselwort enum vorangestellt werden. Mit Hilfe der Typdeklaration typedef kann man dies vereinfachen.

Typdeklaration

Mit dem Schlüsselwort typedef werden neue Namen für bestehende Datentypen deklariert. Die Anweisung
typedef unsigned short int UINT;
deklariert einen neuen Name UINT der im Anschluß bei Variablendeklarationen synonym mit unsigned short int verwendet werden kann. Damit wird die Anweisung
UINT var1;
äquivalent zu
unsigned short int var1;
Mit typedef können natürlich auch Aufzählungsdatentypen als neue Typnamen festgelegt werden.
typedef enum {FALSE, TRUE} BOOL;
deklariert einen Datentypnamen BOOL der später zur Deklaration von Variablen eingesetzt werden kann. Es ist nach dieser Deklaration mit
BOOL v1 = TRUE;
möglich die Variable v1 als Aufzählungstypvariable zu deklarieren und auch mit einem Wert zu initialisieren.

Strukturen

Viele Programmieraufgaben können durch die Verwendung von sogenannten Strukturen vereinfacht werden. Strukturen stellen die mächtigste Möglichkeit dar, neue Datentypen anzulegen. Eine Struktur ist eine Speichermethode von Daten, die vom Programmierer entworfen wird. In ihr werden mehrere Variable zur einfachen Manipulation unter einem einzelnen Namen gruppiert. Im Gegensatz zu Arrays können die Variablen einer Struktur von unterschiedlichem Typ sein. Jede Variable einer Struktur wird als Mitglied (member) dieser Struktur bezeichnet. Zum Beispiel werden in einem Graphikprogramm sehr oft die Koordianten eines Punktes dieser Graphik Verwendung finden. Die Koordianten eines Punktes einer ebenen Graphik bestehen aus zwei Werten, nämlich einem x-Wert und einem y-Wert. Dies läßt sich in einer Struktur folgendermaßen zu einer Einheit zusammenfassen:

struct coord {
  int x;
  int y;
};
Dem Schlüsselwort struct, welches den Beginn einer Strukturdeklaration kennzeichnet, muß unmittelbar der Strukturname (Ettiket oder tag) folgen, der den üblichen C-Regeln für die Namensgebung gehorcht. Innerhalb der, dem Namen folgenden, geschweiften Klammern steht eine Liste von Variablen, die Mitglieder dieser Struktur sind. Für jedes Mitglied muß Variablentyp und Name angegeben werden.

Die vorhergehende Anweisung deklariert eine Struktur mit Namen coord, die zwei Ganzzahlvariablen, x und y, enthält. Dabei wird aber noch keine Speicherreservierung durchgeführt, sondern nur das "Aussehen" der Struktur festgelegt. Für die eigentliche Definition gibt es zwei Möglichkeiten. Eine ist, daß unmittelbar an die Deklaration eine Liste von Variablennamen angefügt wird:

struct coord {
  int x;
  int y;
} first, second;

Hier wird eine Struktur coord deklariert und zwei Strukturen mit Namen first und second definiert. first und second sind beides Variablen vom Typ coord. first wie auch second enthält zwei Ganzzahlvariablen mit Namen x und y.

Die zweite Möglichkeit besteht darin, nach der Daklaration, die Definition von Strukturvariablen später im Programm durchzuführen:

struct coord {
  int x;
  int y;
};

/* Weitere Programmzeilen */

struct coord first, second;

Zugriff auf Strukturmitglieder

Einzelne Strukturmitglieder können wie andere Variablen desselben Typs verwendet werden. Strukturmitglieder werden über den Mitgliedsoperator ( . = =  dot operator), eingefügt zwischen Strukturnamen und Mitgliedsnamen, angesprochen. Um also in der Struktur first die Koordinaten x mit 50 und y mit 100 zu belegen, verwendet man:
first.x = 50;
first.y = 100;
Um den Inhalt der Koordianten second auszugeben, schreibt man:
printf("%d, %d", second.x, second.y);
Ein wesentlicher Vorteil von Strukturen ist, daß mittels einer einfachen Zuweisungsoperation Informationen zwischen Strukturen desselben Typs ausgetauscht werden können.
first = second;
ist also äquivalent zu
first.x = second.x;
first.y = second.y;
Im Falle von komplexen Strukturen stellt diese Möglichkeit natürlich eine wesentliche Vereinfachung dar, insbesondere wenn man diese Möglichkeit mit der Unmöglichkeit der Zuweisung ganzer Arrays aufeinander vergleicht.

Strukturen enthaltende Strukturen

Strukturen können jeden Datentyp enthalten, und damit natürlich auch andere Strukturen. Soll z.B. in einem Graphikprogramm ein Rechteck gezeichnet werden, so kann die oben definierte Struktur coord zur Deklaration einer Struktur, die ein Rechteck beschreibt, verwendet werden.
struct rectangle {
  struct coord topleft;
  struct coord bottomright;
};
Diese Anweisung deklariert eine Struktur vom Typ rectangle, welche zwei Strukutren des Typs coord enthält. Zur Definition einer rectangle Struktur bedarf es noch der Angabe des Namens, wie z.B.:
struct rectangle mybox;
Die Definition von mybox hätte natürlich auch schon bei der Deklaration vorgenommen werden können. Um nun auf die einzelnen Mitglieder dieser neuen Struktur zugreifen zu können, muß der Mitgliedsoperator (.) zweimal angewendet werden.
Das heißt:
mybox.topleft.x
verweist auf die x Koordinate des linken oberen Punktes des Rechteckes. Hier noch ein Beispielprogramm:
/* strctdem.c
	Beispiel einer Struktur die andere Strukturen enthaelt.
 */

#include <stdio.h>

int length, width;
long area;

struct coord{
  int x;
  int y;
};

struct rectangle{
  struct coord topleft;
  struct coord bottomrt;
} mybox;

int main(void)
{
/* Eingabe der Koordinaten */
  printf("\nLinke obere x Koordinate: ");
  scanf("%d", &mybox.topleft.x);
  printf("\nLinke obere y Koordinate: ");
  scanf("%d", &mybox.topleft.y);
  printf("\nRechte untere x Koordinate: ");
  scanf("%d", &mybox.bottomrt.x);
  printf("\nRechte untere y Koordinate: ");
  scanf("%d", &mybox.bottomrt.y);

/* Berechne die Laenge und Breite */
  width = mybox.bottomrt.x - mybox.topleft.x;
  length = mybox.bottomrt.y - mybox.topleft.y;

/* Berechne die Flaeche und gebe sie aus */
  area = width * length;
  printf("Die Flaeche ist %ld Quadrat Einheiten.", area);
  return 0;
}

Arrays enthaltende Strukturen

Man kann auch Strukturen definieren, die Arrays enthalten, wobei jede Art von C Datentypen erlaubt ist.
struct data{
  int x[4];
  char y[10];
};
deklariert eine Struktur welche ein Integerarray von 4 Elementen mit Namen x, und ein 10 elementiges Characterarray namens y enthält.
struct data record;
erstellt eine Struktur mit Namen record, auf deren Mitglieder analog wie in den anderen Beispielen zugegriffen werden kann:
record.x[3] = 100;
record.y[1] = 'a';
Es sollte bei Array enthaltenden Strukturen nicht vergessen werden, daß der Name eines Arrays ohne Klammern ein Zeiger auf das Array ist. Dies gilt natürlich auch für Arrays in Strukturen.
record.y
ist also ein Zeiger auf das Anfangselement des Arrays y[] in der Struktur record. Hierzu ein Beispiel:
/* strctarr.c
	Beispiel einer Strukctur die Arrays als Mitglieder hat. */

#include <stdio.h>

/* Deklariere und definiere eine Struktur um die Daten aufzunehmen.
	Sie enthaelt eine float Variable und zwei char Arrays.
 */

struct data{
  float amount;
  char fname[30];
  char lname[30];
} rec;

int main(void)
{
/* Eingabe der Daten vom Keyboard. */
  printf("\nGeben Sie, getrennt durch einen Zwischenraum,\n");
  printf("Vor- und Zu- Namen des Einzahlers ein: ");
  scanf("%s %s", rec.fname, rec.lname);
  printf("\nGeben Sie den Betrag ein: ");
  scanf("%f", &rec.amount);

/* Anzeige der Daten am Bildschirm. */
  printf("\n%s %s hat ATS %.2f eingezahlt.\n",
			  rec.fname, rec.lname, rec.amount);
  return 0;
}

Arrays von Strukturen

Da es Strukturen gibt, die Arrays enthalten können, ist es naheliegend, daß es möglich ist Arrays von Strukturen zu deklarieren. Um z.B. ein Telephonverzeichnis zu erstellen, kann folgende Struktur Verwendung finden:
struct entry{
 char fname[11];
 char lname[21];
 char phone[11];
};
Ein Telephonverzeichnis enthält sicherlich mehr als einen Eintrag, daher wird also die Definition eines ganzen Arrays von solchen Strukturen sinnvoll sein.
struct entry list[100];
Mit dieser Anweisung wird ein Array namens list mit 100 Elementen erzeugt, wobei jedes Element eine Struktur des Typs entry ist.

Initialisierung von Strukturen

Wie andere C Variable können Strukturen bei der Definition auch gleich initialisiert werden. Der Vorgang ist ähnlich zur der Initialisierung von Arrays. Der Strukturdefinition folgt nach einem Gleichheitszeichen eine, in geschweifte Klammern eingeschlossene, durch Kommas getrennte Liste von Initialisierungswerten.
struct sale{
  char kunde[41];
  char produkt[41];
  float preis;
} mysale = { "Kleinstweich Inc.",
				 "Fenstersturz-Betriebsystem",
				 1000.00
			  };
Enthält eine Struktur weitere Strukturen, werden die Initialisierungswerte der Strukturmitglieder der eingebetteten Struktur, in der Reihenfolge, wie sie in der Struktudeklaration der eingebetteten Struktur vorkommen, in Klammern, aufgelistet.
struct customer {
  char firm[41];
  char contact[31];
};

struct sale{
  struct customer buyer;
  char produkt[41];
  float preis;
} mysale = { { "Kleinstweich Inc.", "Wilhelm Tor"},
				 "Fenstersturz-Betriebsystem",
				 1000.00
			  };
Wie bei der Initialisierung von Arrays, können auch Arrays von Strukutren initialisiert werden. Die Klammerung erfolgt dabei entsprechend den einzelnen Strukturarraykomponenten.

Strukturen und Zeiger

Nachdem Zeiger ein so wesentlicher Bestandteil der Programmiersprache C sind, ist es nicht überraschend, daß Zeiger auch innerhalb von Strukturen Anwendung finden. Man kann Zeiger als Strukturmitglieder einsetzen, aber auch Zeiger auf Strukturen deklarieren.
struct data {
  int *value;
  int *rate;
} first;
Hier wird eine Struktur, die zwei Zeiger vom Typ int enthält deklariert. Wie in allen Fällen, wo ein Zeiger im Spiel ist, reicht die Deklaration nicht aus, es muß dem Zeiger erst die Adresse einer Variablen zugewiesen werden, damit der Zeiger auf etwas deutet.
first.value = &cost;
first.rate = &interest;
Der am ehesten in einer Struktur zur Anwendung gelangende Zeiger, ist ein Zeiger auf Typ char. Vergleichen wir die Struktur:
struct msg {
  char p1[21];
  char p2[21];
} mymsg;
mit der folgenden Definition
struct msg {
  char *p1;
  char *p2;
} myptrs;
Im ersten Fall wird bei der Definition der Variablen mymsg Speicherplatz für die Speicherung der Charcterarrays reserviert. Außerdem ist die Größe des Strings der in p1 Platz findet auf 20 Zeichen beschränkt, es kann also keine längere Zeichenkette in der Struktur abgespeichert werden. Dies ist für myptr nicht der Fall. Es wird bei der Definition nur der Platz für die Aufnahme der Zeiger reserviert, der aktuelle String wird an anderer Stelle im Speicher abgelegt. Zusätzlich besteht keine Längenbeschränkung für die Zeichenkette selbst, da ein Zeiger des Typs char auf einen String beliebiger Länge zu zeigen vermag.

Zeiger auf Strukturen

Genau wie Zeiger auf andere Datentypen kann man in einem C Programm Zeiger auf Strukturen deklarieren und verwenden. Zeiger auf Strukturen werden oft benutzt um Strukturen als Argumente an Funktionen zu übergeben. Zeiger auf Strukturen werden auch in einer Datenspeichermethode, die als verkettete Liste bekannt ist, verwendet. Zuerst jedoch die Verwendung von Zeiger auf Strukturen:
struct part {
  int number;
  char name[11];
};
Diese Anweisung deklariert eine Struktur namens part. Nun die Deklaration eines Zeigers vom Typ part
struct part *p_part;
Der Zeiger p_part kann im Moment noch nicht initialisiert werden, da noch die Erstellung einer Variablen vom Typ part fehlt. Mit der Definition
struct part gizmo;
kann eine Zeigerinitialisierung erfolgen:
 p_part = &gizmo;
Nachdem nun ein Zeiger auf die Struktur gizmo existiert, kann mittels des Referenz Operators (*) auf die Mitglieder von gizmo zugegriffen werden.
(*p_part).number = 100;
setzt den Ganzzahlwert number von gizmo auf 100. Die Klammerung ist notwendig da der Mitgliedsoperator (.) Vorrang vor dem Referenz Operator (*) hat. Eine zweite Methode um Strukturmitglieder unter Verwendung von Zeigern anzusprechen ist die Benutzung des indirekten Mitgliedsoperators, der aus einem Bindestrich (minus) gefolgt von einem Größerzeichen besteht (->). C behandelt diese beiden Zeichen als einzelnen Operator. Das Symbol wird zwischen den Zeigernamen und den Strukturmitgliedsnamen gesetzt. Um also den Ganzzahlwert number von gizmo anzusprechen, kann man auch
p_part->number
schreiben. Existiert also eine Struktur str, ein Zeiger p_str auf dies Struktur, und ist memb ein Mitglied der Struktur, so kann auf dieses Mitglied in folgender Weise zugegriffen werden:
str.memb
(*p_str).memb
p_str->memb
Das heißt, alle drei Ausdrücke sind zueinander äquivalent.

Strukturen als Argumente von Funktionen

Wie andere Datentypen können auch Strukturen als Argumente an Funktionen übergeben werden. Das zeigt folgendes Beispiel:
/* strctdep.c
	Demonstration der Uebergabe einer Struktur an eine Funktion. */

#include <stdio.h>

/* Deklariere und definiere eine Struktur,
	um die Daten aufzunehmen. */

struct data{
  float amount;
  char fname[30];
  char lname[30];
} rec;

/* Funktionsprototyp
	Die Funktion hat keinen Rueckgabewert,
/* sie hat eine Struktur vom Typ data als Argument. */

void print_rec(struct data x);

int main(void)
{
/* Eingabe der Daten vom Keyboard. */
  printf("\nGeben Sie, getrennt durch einen Zwischenraum,\n");
  printf("Vor- und Zu- Namen des Einzahlers ein: ");
  scanf("%s %s", rec.fname, rec.lname);
  printf("\nGeben Sie den Betrag ein: ");
  scanf("%f", &rec.amount);

/* Aufruf der Anzeigefunktion */
  print_rec( rec );
  return 0;
}

void print_rec(struct data x)
{
  printf("\n%s %s hat ATS %.2f eingezahlt.\n",
			  x.fname, x.lname, x.amount);
}

Unionen

Unionen sind ähnlich zu Strukturen, und sie werden ganz analog wie Strukturen deklariert und verwendet. Sie unterscheiden sich von Strukturen nur darin, daß nur jeweils ein Mitglied gleichzeitig verwendet werden kann. Alle Mitglieder einer Union verwenden nämlich denselben Platz im Speicher, sie werden sozusagen aufeinander abgelegt.
union shared_tag {
  char c;
  int i;
  float f;
} shared;
Die Union shared kann also wahlweise ein Zeichen, einen Ganzzahlwert oder eine Realzahl aufnehmen, aber im Gegensatz zu einer Struktur immer nur jeweils eines dieser Typen. Wird also mit
shared.c = 'A';
die Union mit einem Zeichen initialisert, so liefert
printf("%f", shared.f);
kein sinvolles Ergebnis und umgekehrt falls man mit
shared.f = 123.4567;
die Realzahlvariable initialisert, liefert die Ausgabe von
printf("%c", shared.c);
ein bedeutungsloses Ergebnis.

Verkettete Listen

Unter einer verketteten Liste versteht man eine allgemeine Klasse von Datenspeicherelementen, wo jedes Datenelement mit einem oder mehreren anderen Elementen über Zeiger verbunden ist.

Es gibt verschiedenen Arten von verketteten Listen, wobei allen gemeinsam ist, daß die Information der Verkettung in den Elementen der Liste selbst enthalten ist. Dies unterscheidet die verketteten Listen von den Arrays, wo die Verbindung der einzelnen Elemente aus der Struktur des Arrays folgt. Um zu illustrieren, wie verkettete Listen aufgebaut sind, folgt eine einfache Strukturdeklaration:

struct data{
  char name[11];
  struct data *next;
};

In dieser Deklaration ist das zweite Mitglied der Struktur ein Zeiger auf eine Struktur vom Typ data. In anderen Worten, die Struktur enthält einen Zeiger auf den eigenen Typ. Das heißt, daß eine Realisierung des Typs data auf eine andere desselben Typs zu verweisen vermag. Dies ist im folgenden illustriert:

---> data
pointer ---> data
pointer ---> data
pointer --->

Jede Liste muß einen Anfang und ein Ende besitzen. Der Anfang wird durch den "head pointer", der auf die erste Struktur der Liste zeigt repräsentiert. Dieser Zeiger ist ein gewöhnlicher Zeiger und keine Strukutur. Das Ende der Liste wird durch eine Struktur markiert, deren Zeiger den Wert NULL enthält. Damit sieht die verkettete Liste folgendermaßen aus:

head pointer ---> data
pointer ---> data
pointer ---> data
NULL

Der wesentliche Vorteil einer verketteten Liste gegenüber einem Array besteht nun darin, daß es in einer verketteten Liste wesentlich einfacher ist, Mitglieder einzufügen oder zu löschen. Auch eine Sortierung einer solchen Liste gestaltet sich um einiges einfacher, da ja nur Zeiger manipuliert werden müssen und keine Daten. Darüberhinaus ist eine Erweiterung der Liste problemlos möglich. Es muß nicht, wie bei Arrays, von Haus aus eine bestimmte Zahl von Komponenten reserviert werden. In einer verketteten Liste kann während des Programmablaufes mittels malloc() benötigter Speicherplatz für weitere Elemente der Liste geschaffen werden, sofern der Gesamtspeicherplatz es noch zuläßt.

Noch flexibler als die soeben behandelte einfach verkettete Liste, wo nur der nächste Nachfolger über einen Zeiger verbunden ist, ist eine doppelt verkettete Liste, die Zeiger auf den Vorgänger und den Nachfolger enthält. Eine Strukturdeklaration für eine doppelt verkettete Liste könnte so aussehen:

struct data{
 char name[11];
 struct data *prev;
 struct data *next;
};

Die Verkettung erfolgt in diesem Fall so, dass für jedes Element der Liste Vorgänger und Nachfolger bekannt sind. Ein Teil einer solchen Kette sieht dann folgendermaßen aus:

        data(n-1) <------------\
<------ *prev                  |
        *next ------> data(n)  | <-------------\
                      *prev ---/               |
                      *next ------> data(n+1)  |
                                    *prev -----/
                                    *next ------>
Am Anfang und Ende der gesamten Kette können die entsprechenden ins "Leere" zeigenden Pointer der Strukturen wiederum NULL Zeiger enthalten, wie unten zu sehen ist, oder aber die gesamte Liste könnte auch in Form eines Ringes angelegt werden.
  data0 <-------------\
  NULL                |
  *next ---> data1    | <--------\
             *prev ---/          |
             *next ---> data2    |
                        *prev ---/
                        NULL


home