vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 10

Strukturen

Viele Programmieraufgaben lassen sich mit den so genannten Strukturen, einem speziellen Datenkonstrukt, leichter lösen. Eine Struktur ist ein von Ihnen definierter Datentyp, der direkt auf Ihre Programmierbedürfnisse zugeschnitten ist. Heute lernen Sie:

Einfache Strukturen

Eine Struktur ist eine Sammlung von einer oder mehreren Variablen, die zum Zwecke der leichteren Manipulation unter einem Namen zusammengefasst werden. Die Variablen in einer Struktur können im Gegensatz zu denen in einem Array unterschiedlichen Datentypen angehören. Eine Struktur kann jeden beliebigen C-Datentyp enthalten, einschließlich Arrays und andere Strukturen. Die Variablen einer Struktur werden auch als Elemente dieser Struktur bezeichnet. Im nächsten Abschnitt sehen Sie hierzu ein einfaches Beispiel.

Sie sollten mit einfachen Strukturen beginnen. Beachten Sie, dass C keinen Unterschied zwischen einfachen und komplexen Strukturen macht. Aber zum besseren Verständnis sollte man zuerst die einfachen Strukturen behandeln.

Strukturen definieren und deklarieren

Wenn Sie ein Grafikprogramm schreiben, wird Ihr Code mit den Koordinaten von Bildschirmpunkten arbeiten müssen. Bildschirmkoordinaten setzen sich aus einem x-Wert für die horizontale Position und einem y-Wert für die vertikale Position zusammen. Zum Abspeichern der Koordinaten lohnt es sich, eine Struktur - im nachfolgenden Code heißt sie koord - zu definieren, die sowohl die x- als auch die y- Werte einer Bildschirmposition einschließt:

struct koord {
int x;
int y;
};

Auf das Schlüsselwort struct, das eine Strukturdefinition einleitet, folgt direkt der Name des Strukturtyps. Die sich an den Strukturnamen anschließenden geschweiften Klammern umfassen die Liste der Elementvariablen der Struktur. Für jede Elementvariable müssen ein Datentyp und ein Name angegeben werden.

Es ist zwar zulässig, Strukturen ohne Namen zu erzeugen,
aber solche Strukturen sind nicht besonders nützlich.

Die obigen Anweisungen definieren einen Strukturtyp namens koord, der zwei Integervariablen, x und y, enthält. Die Anweisungen erzeugen allerdings noch keine Instanzen der Struktur koord. Mit anderen Worten, sie deklarieren keine Strukturenvariablen (es wird kein Speicher reserviert). Es gibt zwei Möglichkeiten, Strukturvariablen zu deklarieren. Eine Möglichkeit besteht darin, die Strukturdefinition um eine Liste von einem oder mehreren Variablennamen zu ergänzen:

struct koord {
int x;
int y;
} erste, zweite;

Diese Anweisungen definieren den Strukturtyp koord und deklarieren die zwei Strukturenvariablen erste und zweite. erste und zweite sind beides Instanzen vom Typ koord und enthalten zwei Integer-Elemente namens x und y.

Diese Art, eine Struktur zu deklarieren, kombiniert die Deklaration mit der Definition. Die zweite Möglichkeit besteht darin, die Deklaration der Strukturvariablen unabhängig von der Definition vorzunehmen. Die folgenden zwei Anweisungen deklarieren ebenfalls zwei Instanzen vom Typ koord:

struct koord {
int x;
int y;
};
/* Hier kann zusätzlicher Code stehen */
struct koord erste, zweite;

Zugriff auf Strukturelemente

Die einzelnen Strukturelemente können wie normale Variablen des gleichen Typs verwendet werden. Der Zugriff auf die Strukturelemente erfolgt über den Strukturelement-Operator (.), auch Punktoperator genannt, der zwischen den Strukturnamen und den Elementnamen gesetzt wird. Um die Bildschirmposition mit den Koordinaten x=50 und y=100 in einer Strukturvariablen namens erste abzuspeichern, könnten Sie schreiben:

erste.x = 50;
erste.y = 100;

Um die Bildschirmpositionen, die in der Strukturvariablen zweite gespeichert ist, anzuzeigen, könnten Sie schreiben:

printf("%d,%d", zweite.x, zweite.y);

Vielleicht fragen Sie sich inzwischen, worin der Vorteil der Strukturen gegenüber den einzelnen Variablen liegt. Ein großer Vorteil ist, dass man Daten, die in Strukturen des gleichen Typs abgelegt sind, mit einer einfachen Zuweisung kopieren kann1. So ist die Anweisung

erste = zweite;

äquivalent zu:

erste.x = zweite.x;
erste.y = zweite.y;

Wenn Ihr Programm komplexe Strukturen mit vielen Elementen enthält, kann Ihnen diese Notation viel Zeit sparen. Es gibt noch weitere Vorteile, die sich Ihnen im Laufe der Zeit und beim Erlernen weiter fortgeschrittener Programmiertechniken erschließen werden. Allgemein lässt sich sagen, dass Strukturen immer dann sinnvoll sind, wenn Informationen unterschiedlicher Variablentypen als Gruppe bearbeitet werden sollen. So könnten Sie zum Beispiel die Einträge in einer Adressdatenbank als Variablen einer Struktur betrachten, in der für jede einzelne Information (Name, Adresse, Stadt und so weiter) ein eigenes Strukturelement deklariert ist.

Das Schlüsselwort struct

struct name {
struktur_element(e);
/* Hier kann zusätzlicher Code stehen */
} instanz;

Das Schlüsselwort struct dient dazu, Strukturen zu deklarieren. Eine Struktur ist eine Sammlung von einer oder mehreren Variablen (struktur_elemente), die zur leichteren Bearbeitung unter einem Namen zusammengefasst wurden. Die Variablen müssen nicht vom gleichen Datentyp und auch keine einfachen Variablen sein. So können Strukturen auch Arrays, Zeiger und andere Strukturen enthalten.

Das Schlüsselwort struct kennzeichnet den Anfang einer Strukturdefinition. Es wird gefolgt von dem Namen der Struktur. An den Strukturnamen schließen sich in geschweiften Klammern die Strukturelemente an. Eine Instanz, die Deklaration einer Strukturvariablen, kann ebenfalls definiert werden. Wenn Sie die Struktur ohne die Instanz definieren, erhalten Sie lediglich eine Schablone, die dann später in dem Programm zur Deklaration von Strukturvariablen verwendet werden kann. Das Format einer Schablone sieht wie folgt aus:

struct name {
struktur_element(e);
/* Hier können zusätzliche Anweisungen stehen */
};

Um auf der Grundlage der Schablone Instanzen zu deklarieren, verwenden Sie folgende Syntax:

struct name instanz;

Voraussetzung ist, dass Sie zuvor eine Struktur mit dem angegebenen Namen deklariert haben.

Beispiel 1

/* Deklariert eine Strukturschablone namens kunden_nr */
struct kunden_nr {
int zahl1;
char bindestrich1;
int zahl2;
char bindestrich2;
int zahl3;
};
/* Verwendet die Strukturschablone */
struct kunden_nr aktueller_kunde;

Beispiel 2

/* Deklariert gleichzeitig eine Struktur und eine Instanz */
struct datum {
char monat[2];
char tag[2];
char jahr[4];
} aktuelles_datum;

Beispiel 3

/* Deklariert und initialisiert eine Strukturvariable */
struct zeit {
int stunden;
int minuten;
int sekunden;
} zeitpunkt_der_geburt = { 8, 45, 0 };

Komplexere Strukturen

Nachdem Sie inzwischen die einfachen Strukturen kennen gelernt haben, kommen wir jetzt zu den interessanteren, weil komplexeren Strukturtypen. Dazu gehören Strukturen, die andere Strukturen oder auch Arrays als Elemente enthalten.

Strukturen, die Strukturen enthalten

Wie bereits erwähnt, kann eine Struktur jeden beliebigen C-Datentypen enthalten. So kann eine Struktur unter anderem auch andere Strukturen aufnehmen. Lassen Sie uns das veranschaulichen, indem wir das obige Beispiel etwas ausbauen.

Angenommen Ihr Grafikprogramm soll Rechtecke unterstützen. Ein Rechteck wird definiert durch die Koordinaten zweier gegenüberliegenden Ecken. Wie man eine Struktur zum Abspeichern von Koordinaten (x, y) definiert, haben Sie bereits gesehen. Zur Definition eines Rechtecks benötigen Sie zwei Instanzen dieser Struktur. Vorausgesetzt, Sie haben bereits eine Struktur vom Typ koord definiert, könnte die Definition einer Struktur für die Rechtecke dann wie folgt aussehen:

struct rechteck {
struct koord obenlinks;
struct koord untenrechts;
};

Diese Anweisung definiert eine Struktur vom Typ rechteck, die zwei Strukturen vom Typ koord enthält. Die beiden koord-Strukturen heißen obenlinks und untenrechts.

Die obige Anweisung definiert lediglich den Strukturtyp rechteck. Um eine Instanz der Struktur zu deklarieren, müssen Sie eine Anweisung wie die folgende hinzufügen:

struct rechteck meinfeld;

Sie hätten - wie bereits oben im Falle von koord - Definition und Deklaration auch kombinieren können:

struct rechteck {
struct koord obenlinks;
struct koord untenrechts;
} meinfeld;

Um auf die Speicherstellen der eigentlichen Daten (die Elemente vom Typ int) zuzugreifen, müssen Sie den Element-Operator (.) zweimal verwenden. So bezieht sich der Ausdruck

meinfeld.obenlinks.x

auf das Element x des Elements obenlinks der Strukturvariablen meinfeld vom Typ rechteck. Um ein Rechteck mit den Koordinaten (0,10) und (100,200) zu definieren, würden Sie schreiben:

meinfeld.obenlinks.x = 0;
meinfeld.obenlinks.y = 10;
meinfeld.untenrechts.x = 100;
meinfeld.untenrechts.y = 200;

Wenn Sie hierdurch etwas verwirrt sind, sollten Sie am besten einen Blick auf Abbildung 10.1 werfen, in der die Beziehung zwischen der Struktur vom Typ rechteck, den darin enthaltenen zwei Strukturen vom Typ koord sowie den darin jeweils enthaltenen zwei Variablen vom Typ int deutlich gemacht werden. Die Namen der Strukturen entsprechen dem obigen Beispiel.

Abbildung 10.1:  Die Beziehung zwischen einer Struktur, den Strukturen in dieser Struktur und den Strukturelementen.

Listing 10.1 demonstriert die Verwendung von Strukturen, die andere Strukturen enthalten. Das Programm nimmt vom Anwender die Koordinaten eines Rechtecks entgegen, berechnet daraus die Fläche des Rechtecks und gibt diesen Wert aus. Beachten Sie die Voraussetzungen für dieses Programm, die innerhalb eines Kommentars am Anfang des Programms stehen (Zeilen 3 bis 8).

Listing 10.1: Ein Beispiel für eine Struktur, die andere Strukturen enthält.

1:  /* Beispiel für Strukturen, die andere Strukturen enthalten. */
2:
3: /* Übernimmt die Eingabe der Eck-Koordinaten eines Rechtecks
4: und berechnet die Fläche. Geht davon aus, dass die y-Koordinate
5: der unteren rechten Ecke größer ist als die y-Koordinate der
6: oberen linken Ecke, dass die x-Koordinate der unteren rechten
7: Ecke größer ist als die x-Koordinate der unteren linken Ecke
8: und dass alle Koordinaten positiv sind. */
9:
10: #include <stdio.h>
11:
12: int laenge, hoehe;
13: long flaeche;
14:
15: struct koord{
16: int x;
17: int y;
18: };
19:
20: struct rechteck{
21: struct koord obenlinks;
22: struct koord untenrechts;
23: } meinfeld;
24:
25: int main(void)
26: {
27: /* Eingabe der Koordinaten */
28:
29: printf("\nGeben Sie die x-Koordinate von oben links ein: ");
30: scanf("%d", &meinfeld.obenlinks.x);
31:
32: printf("\nGeben Sie die y-Koordinate von oben links ein: ");
33: scanf("%d", &meinfeld.obenlinks.y);
34:
35: printf("\nGeben Sie die x-Koordinate von unten rechts ein: ");
36: scanf("%d", &meinfeld.untenrechts.x);
37:
38: printf("\nGeben Sie die y-Koordinate von unten rechts ein: ");
39: scanf("%d", &meinfeld.untenrechts.y);
40:
41: /* Länge und Höhe berechnen */
42:
43: hoehe = meinfeld.untenrechts.x - meinfeld.obenlinks.x;
44: laenge = meinfeld.untenrechts.y - meinfeld.obenlinks.y;
45:
46: /* Fläche berechnen und ausgeben */
47:
48: flaeche = hoehe * laenge;
49: printf("\nDie Fläche misst %ld Einheiten.\n", flaeche);
50:
51: return 0;
52: }

Geben Sie die x-Koordinate von oben links ein: 1

Geben Sie die y-Koordinate von oben links ein: 1

Geben Sie die x-Koordinate von unten rechts ein: 10

Geben Sie die y-Koordinate von unten rechts ein: 10

Die Fläche misst 81 Einheiten.

Die Struktur koord mit ihren zwei Elementen x und y ist in den Zeilen 15 bis 18 definiert. Die Zeilen 20 bis 23 deklarieren und definieren eine Instanz (meinfeld) der Struktur rechteck. Die zwei Elemente dieser Rechteckstruktur lauten obenlinks und untenrechts und sind beides Strukturen vom Typ koord.

Die Zeilen 29 bis 39 fordern zur Eingabe der Werte für die Struktur meinfeld auf. Da meinfeld nur zwei Elemente enthält, könnten Sie fälschlicherweise annehmen, dass auch nur zwei Werte benötigt werden. Jedes Element von meinfeld verfügt jedoch wieder über eigene Elemente (die Elemente x und y der koord-Struktur). Dadurch ergeben sich insgesamt vier Elemente, für die Werte eingegeben werden müssen. Nachdem diese Werte eingelesen wurden, wird mit Hilfe der Struktur und ihrer Elemente die Fläche berechnet. Beachten Sie, dass Sie für den Zugriff auf die x- und y- Werte über den Instanznamen der Struktur gehen müssen. Da x und y in einer Struktur innerhalb einer Struktur verborgen sind, müssen Sie für die Berechnung der Fläche sogar über die Instanznamen beider Strukturen auf die x- und y-Werte zugreifen - meinfeld.untenrechts.x, meinfeld.untenrechts.y, meinfeld.obenlinks.x und meinfeld.obenlinks.y.

Die Verschachtelungstiefe für Strukturen ist in C praktisch unbegrenzt. Solange es Ihr Speicher erlaubt, können Sie Strukturen definieren, die Strukturen enthalten, die Strukturen enthalten, die wiederum Strukturen enthalten und so weiter - ich glaube, Sie verstehen, was ich meine! Selbstverständlich gibt es einen Punkt, ab dem eine weitere Verschachtelung keine Vorteile mehr bringt. In der Praxis werden in C- Programmen selten mehr als drei Verschachtelungsebenen verwendet.

Strukturen, die Arrays enthalten

In C ist es möglich, Strukturen zu definieren, die ein oder mehrere Arrays als Elemente enthält. Die Arrays können Elemente eines beliebigen C-Datentyps enthalten (int, char und so weiter). So definieren die Anweisungen

struct daten{
int x[4];
char y[10];
};

eine Struktur vom Typ daten, die zwei Arrays enthält - eines mit vier Integer- Elementen namens x und eines mit zehn Zeichen namens y. Nach der Definition der Struktur daten können Sie eine Strukturvariable (datensatz) vom Typ der Struktur deklarieren:

struct daten datensatz;

Den Aufbau dieser Struktur sehen Sie in Abbildung 10.2. Beachten Sie, dass in dieser Abbildung die Elemente des Arrays x wesentlich mehr Platz beanspruchen als die Elemente des Arrays y. Dies liegt daran, dass der Datentyp int in der Regel 4 Byte Speicher belegt, während char nur 1 Byte beansprucht (dies dürfte Ihnen bereits von Tag 2, »Die Komponenten eine C-Programms: Quellcode und Daten«, her bekannt sein).

Abbildung 10.2:  Der Aufbau einer Struktur, die Arrays als Elemente enthält.

Der Zugriff auf die einzelnen Elemente eines Arrays, das Element einer Struktur ist, erfolgt über Element-Operatoren und Array-Index:

datensatz.x[2] = 100;
datensatz.y[1] = 'x';

Sie erinnern sich wahrscheinlich, dass Zeichenarrays meistens dazu dienen, Strings zu speichern. Sie sollten auch daran denken (siehe Tag 8, »Zeiger«), dass der Name eines Arrays ohne eckige Klammern ein Zeiger auf das Array ist. Da dies auch für Arrays gilt, die Element einer Struktur sind, ist der Ausdruck

datensatz.y

ein Zeiger auf das erste Element im Array y[] der Struktur datensatz. Deshalb können Sie den Inhalt von y[] mit folgender Anweisung auf dem Bildschirm ausgeben:

puts(datensatz.y);

Betrachten wir ein weiteres Beispiel. Listing 10.2 verwendet eine Struktur, die eine Variable vom Typ float und zwei Arrays vom Typ char enthält.

Listing 10.2: Eine Struktur, die Arrays als Elemente enthält.

1:  /* Eine Struktur, die Arrays als Elemente enthält. */
2:
3: #include <stdio.h>
4:
5: /* Definiert und deklariert eine Struktur für die Aufnahme der Daten. */
6: /* Die Struktur enthält eine float-Variable und zwei char-Arrays. */
7:
8: struct daten{
9: float betrag;
10: char vname[30];
11: char nname[30];
12: } rec;
13:
14: int main(void)
15: {
16: /* Eingabe der Daten über die Tastatur. */
17:
18: printf("Geben Sie den Vor- und Nachnamen des Spenders,\n");
19: printf("getrennt durch ein Leerzeichen, ein: ");
20: scanf("%s %s", rec.vname, rec.nname);
21:
22: printf("\nGeben Sie die Höhe der Spende ein: ");
23: scanf("%f", &rec.betrag);
24:
25: /* Zeigt die Informationen an. */
26: /* Achtung: %.2f gibt einen Fließkommawert aus, */
27: /* der mit zwei Stellen hinter dem Dezimalpunkt */
28: /* angegeben wird. */
29:
30: /* Gibt die Daten auf dem Bildschirm aus. */
31:
32: printf("\nDer Spender %s %s gab %.2f DM.\n", rec.vname,
33: rec.nname, rec.betrag);
34:
35: return 0;
36: }

Geben Sie den Vor- und Nachnamen des Spenders,
getrennt durch ein Leerzeichen, ein: Bradley Jones

Geben Sie die Höhe der Spende ein: 1000.00

Der Spender Bradley Jones gab 1000.00 DM.

Dieses Programm weist eine Struktur auf, die zwei Arrays enthält - vname[30] und nname[30]. Beides sind Zeichenarrays und nehmen den Vor- beziehungsweise den Nachnamen einer Person auf. Die in den Zeilen 8 bis 12 deklarierte Struktur erhält den Namen daten. Sie enthält neben den Zeichenarrays vname und nname die float- Variable betrag. Eine Struktur wie diese eignet sich hervorragend, um (in zwei Arrays) den Vor- und Nachnamen einer Person sowie einen Wert aufzunehmen wie zum Beispiel den Betrag, den eine Person für einen sozialen Zweck gespendet hat.

In Zeile 12 wird eine Instanz der Struktur, rec2, deklariert. Der Rest des Programms verwendet rec, um Werte vom Anwender einzulesen (Zeilen 18 bis 23) und diese dann auszugeben (Zeilen 32 und 33).

Arrays von Strukturen

Wenn es Strukturen gibt, die Arrays enthalten, gibt es dann auch Arrays von Strukturen? Aber natürlich! Arrays von Strukturen stellen sogar ein besonders mächtiges und wichtiges Programmkonstrukt dar. Wie man dieses Konstrukt nutzt, wird im Folgenden erläutert.

Sie haben bereits gesehen, wie die Definition einer Struktur auf die Daten, mit denen ein Programm arbeitet, zugeschnitten werden kann. In der Regel muss ein Programm allerdings mit mehr als einer Instanz dieser Daten arbeiten. So könnten Sie zum Beispiel in einem Programm zur Verwaltung einer Telefonnummerliste eine Struktur definieren, die für jeden Eintrag in der Telefonliste den Namen der zugehörigen Person und die Telefonnummer enthält:

struct eintrag{
char vname[10];
char nname[12];
char telefon[8];
};

Eine Telefonliste besteht aus vielen Einträgen. Deshalb wäre eine einzige Instanz der Struktur nicht besonders nützlich. Was Sie hier benötigen, ist ein Array von Strukturvariablen des Typs eintrag. Nachdem die Struktur definiert ist, können Sie das Array wie folgt deklarieren.

struct eintrag liste[1000];

Diese Anweisung deklariert ein Array namens liste mit 1.000 Elementen. Jedes Element ist eine Struktur vom Typ eintrag und wird, wie bei anderen Array- Elementtypen, durch einen Index identifiziert. Jede der Strukturvariablen im Array besteht aus drei Elementen, bei denen es sich um Arrays vom Typ char handelt. Dieses ziemlich komplexe Gebilde ist in Abbildung 10.3 grafisch veranschaulicht.

Abbildung 10.3:  Der Aufbau des im Text definierten Arrays von Strukturen.

Wenn Sie das Array für die Strukturvariablen deklariert haben, können Sie die Daten in vielfältiger Weise manipulieren. Um zum Beispiel die Daten in einem Array- Element einem anderen Array-Element zuzuweisen, können Sie schreiben:

liste[1] = liste[5];

Diese Anweisung weist jedem Element der Struktur liste[1] die Werte der entsprechenden Elemente von liste[5] zu. Sie können aber auch Daten zwischen den einzelnen Strukturelementen hin- und herschieben. Die Anweisung

strcpy(liste[1].telefon, liste[5].telefon);

kopiert den String in list[5].telefon in liste[1].telefon. (Die Bibliotheksfunktion strcpy() kopiert einen String in einen anderen String. Näheres dazu erfahren Sie am Tag 16, »Stringmanipulation«.) Wenn Sie wollen, können Sie sogar Daten zwischen den einzelnen Element-Arrays der Strukturelemente verschieben:

liste[5].telefon[1] = liste[2].telefon[3];

Diese Anweisung verschiebt das zweite Zeichen der Telefonnummer in liste[5] an die vierte Position der Telefonnummer in liste[2]. (Zur Erinnerung, die Indexzählung beginnt mit der 0.)

Listing 10.3 demonstriert die Verwendung von Arrays von Strukturen (sogar von Arrays von Strukturen, die selbst wieder Arrays als Elemente enthalten).

Listing 10.3: Arrays von Strukturen.

1:  /* Beispiel für Arrays von Strukturen. */
2:
3: #include <stdio.h>
4:
5: /* Definiert eine Struktur zur Aufnahme von Einträgen. */
6:
7: struct eintrag {
8: char vname[20];
9: char nname[20];
10: char telefon[10];
11: };
12:
13: /* Deklariert ein Array von Strukturen. */
14:
15: struct eintrag liste[4];
16:
17: int i;
18:
19: int main(void)
20: {
21:
22: /* Durchläuft eine Schleife für die Eingabe der Daten von 4
23: Personen. */
24: for (i = 0; i < 4; i++)
25: {
26: printf("\nBitte Vornamen eingeben: ");
27: scanf("%s", liste[i].vname);
28: printf("Bitte Nachnamen eingeben: ");
29: scanf("%s", liste[i].nname);
30: printf("Bitte Telefonnummer im Format 123-4567 eingeben: ");
31: scanf("%s", liste[i].telefon);
32: }
33:
34: /* Zwei leere Zeilen ausgeben. */
35:
36: printf("\n\n");
37:
38: /* Durchläuft eine Schleife zur Anzeige der Daten. */
39:
40: for (i = 0; i < 4; i++)
41: {
42: printf("Name: %s %s", liste[i].vname, liste[i].nname);
43: printf("\t\tTelefon: %s\n", liste[i].telefon);
44: }
45:
46: return 0;
47: }

Bitte Vornamen eingeben: Bradley
Bitte Nachnamen eingeben: Jones
Bitte Telefonnummer im Format 123-4567 eingeben: 555-1212

Bitte Vornamen eingeben: Peter
Bitte Nachnamen eingeben: Aitken
Bitte Telefonnummer im Format 123-4567 eingeben: 555-3434

Bitte Vornamen eingeben: Melissa
Bitte Nachnamen eingeben: Jones
Bitte Telefonnummer im Format 123-4567 eingeben: 555-1212

Bitte Vornamen eingeben: Deanna
Bitte Nachnamen eingeben: Townsend
Bitte Telefonnummer im Format 123-4567 eingeben: 555-1234

Name: Bradley Jones Telefon: 555-1212
Name: Peter Aitken Telefon: 555-3434
Name: Melissa Jones Telefon: 555-1212
Name: Deanna Townsend Telefon: 555-1234

Dieses Listing hat den gleichen allgemeinen Aufbau wie die vorangehenden Listings. Es beginnt mit dem Kommentar in Zeile 1, gefolgt von der Einbindung der Header- Datei stdio.h für die Ein-/Ausgabefunktionen (#include in Zeile 3). In den Zeilen 7 bis 11 wird eine Strukturschablone namens eintrag definiert, die drei Zeichenarrays enthält: vname, nname und telefon. Zeile 15 verwendet diese Schablone, um ein Array liste für vier Strukturvariablen vom Typ eintrag zu definieren. Zeile 17 definiert eine Variable vom Typ int, die im weiteren Verlauf des Programms als Zähler dient. In Zeile 19 beginnt main(). Die erste Aufgabe von main() besteht darin, eine for-Schleife viermal zu durchlaufen, um das Array der Strukturelemente mit Informationen zu »füttern«. Diese Schleife befindet sich in den Zeilen 24 bis 32. Beachten Sie, dass sich die Indizierung von liste nicht von der Indizierung der Array-Variablen von Tag 7, »Numerische Arrays«, unterscheidet.

Zeile 36 schafft einen Abstand zwischen den Eingabeaufforderungen und der Ausgabe. Die Zeile gibt zwei Zeilenumbrüche aus, was Ihnen sicherlich nicht neu sein dürfte. Die Zeilen 40 bis 44 geben die Daten aus, die der Anwender in dem vorherigen Schritt eingegeben hat. Die Werte in dem Array der Strukturelemente werden über den indizierten Array-Namen gefolgt von dem Elementoperator (.) und dem Namen des Strukturelements ausgegeben.

Machen Sie sich mit den Techniken aus Listing 10.3 vertraut. Viele reale Programmieraufgaben lassen sich mit Arrays von Strukturen (deren Elemente wiederum Arrays sein können) am besten lösen.

Was Sie tun sollten

Was nicht

Deklarieren Sie Strukturinstanzen nach den gleichen Gültigkeitsbereichregeln, die auch für andere Variablen gelten. (Am Tag 11, »Gültigkeitsbereiche von Variablen«, wird dieses Thema erschöpfend behandelt.)

Vergessen Sie nicht den Instanznamen der Struktur und den Elementoperator (.), wenn Sie auf die Elemente einer Struktur zugreifen.

Verwechseln Sie nicht den Strukturnamen mit einer Instanz der Struktur! Der Name dient dazu, die Schablone beziehungsweise das Format der Struktur zu deklarieren. Bei der Instanz handelt es sich um eine Variable, die mit Hilfe des Strukturnamens deklariert wird.

Vergessen Sie nicht das Schlüsselwort struct, wenn Sie eine Instanz einer zuvor definierten Struktur deklarieren.

Strukturen initialisieren

Strukturen können wie andere Variablentypen auch bei ihrer Deklaration initialisiert werden. Die Verfahrensweise ähnelt der zum Initialisieren von Arrays. Die Strukturdeklaration wird gefolgt von einem Gleichheitszeichen und einer Liste von initialisierten Werten, die - durch Kommata getrennt - in geschweiften Klammern stehen. Betrachten Sie dazu folgende Anweisungen:

1: struct verkauf {
2: char kunde[20];
3: char artikel[20];
4: float betrag;
5: } meinverkauf = { "Acme Industries",
6: "Einspritzpumpe",
7: 1000.00
8: };

Bei Ausführung dieser Anweisungen werden folgende Aktionen ausgelöst:

  1. Es wird ein Strukturtyp namens verkauf definiert (Zeilen 1 bis 5).
  2. Es wird eine Instanz des Strukturtyps verkauf namens meinverkauf deklariert (Zeile 5).
  3. Es wird das Strukturelement meinverkauf.kunde mit dem String »Acme Industries« initialisiert (Zeile 5).
  4. Es wird das Strukturelement meinverkauf.artikel mit dem String »Einspritzpumpe« initialisiert (Zeile 6).
  5. Es wird das Strukturelement meinverkauf.betrag mit dem Wert 1000.00 initialisiert (Zeile 7).

Für eine Struktur, die Strukturen als Elemente enthält, werden die Strukturelemente in der Reihenfolge initialisiert, in der sie in der Strukturdefinition aufgelistet wurden. Sehen Sie dazu eine Erweiterung des oberen Beispiels:

1:  struct kunde {
2: char firma[20];
3: char kontakt[25];
4: }
5:
6: struct verkauf {
7: struct kunde kaeufer;
8: char artikel[20];
9: float betrag;
10: } meinverkauf = { { "Acme Industries", "George Adams"},
11: "Einspritzpumpe",
12: 1000.00
13: };

Mit diesen Anweisungen werden folgende Initialisierungen vorgenommen:

  1. Das Strukturelement meinverkauf.kaeufer.firma wird mit dem String »Acme Industries« initialisiert (Zeile 10).
  2. Das Strukturelement meinverkauf.kaeufer.kontakt wird mit dem String »George Adams« initialisiert (Zeile 10).
  3. Das Strukturelement meinverkauf.artikel wird mit dem String »Einspritzpumpe« initialisiert (Zeile 11).
  4. Das Strukturelement meinverkauf.betrag wird mit dem Betrag 1000.00 initialisiert (Zeile 12).

Sie können auch Arrays von Strukturen initialisieren. Die von Ihnen angegebenen Initialisierungsdaten werden dabei der Reihenfolge nach auf die Strukturen im Array angewendet. Um zum Beispiel ein Array von Strukturen vom Typ verkauf zu deklarieren und die ersten zwei Array-Elemente (das heißt die ersten zwei Strukturen) zu initialisieren, würden Sie schreiben:

1:  struct kunde {
2: char firma[20];
3: char kontakt[25];
4: };
5:
6: struct verkauf {
7: struct kunde kaeufer;
8: char artikel[20];
9: float betrag;
10: };
11:
12:
13: struct verkauf j1990[100] = {
14: { { "Acme Industries", "George Adams"},
15: "Einspritzpumpe",
16: 1000.00
17: },
18: { { "Wilson & Co.", "Ed Wilson"},
19: "Typ 12",
20: 290.00
21: }
22: };

Folgendes passiert in diesem Code:

  1. Das Strukturelement j1990[0].kaeufer.firma wird mit dem String »Acme Industries« initialisiert (Zeile 14).
  2. Das Strukturelement j1990[0].kaeufer.kontakt wird mit dem String »George Adams« initialisiert (Zeile 14).
  3. Das Strukturelement j1990[0].artikel wird mit dem String »Einspritzpumpe« initialisiert (Zeile 15).
  4. Das Strukturelement j1990[0].betrag wird mit dem Betrag 1000.00 initialisiert (Zeile 16).
  5. Das Strukturelement j1990[1].kaeufer.firma wird mit dem String »Wilson & Co.« initialisiert (Zeile 18).
  6. Das Strukturelement j1990[1].kaeufer.kontakt wird mit dem String »Ed Wilson« initialisiert (Zeile 18).
  7. Das Strukturelement j1990[1].artikel wird mit dem String »Typ 12« initialisiert (Zeile 19).
  8. Das Strukturelement j1990[1].betrag wird mit dem Betrag 290.00 initialisiert (Zeile 20).

Strukturen und Zeiger

Angesichts der Tatsache, dass Zeiger in C eine wichtige Stellung einnehmen, sollte es Sie nicht überraschen, dass man sie auch zusammen mit Strukturen verwenden kann. Sie können Zeiger sowohl als Strukturelemente verwenden als auch als Zeiger auf Strukturen deklarieren. Diese beiden Anwendungsbereiche sind Thema der folgenden Abschnitte.

Zeiger als Strukturelemente

Was die Verwendung von Zeigern als Strukturelemente angeht, haben Sie sämtliche Freiheiten. Zeigerelemente werden genauso deklariert wie normale Zeiger, die nicht Elemente von Strukturen sind - und zwar mit Hilfe des Indirektionsoperators (*). Sehen Sie dazu ein Beispiel:

struct daten {
int *wert;
int *rate;
} erste;

Diese Anweisungen definieren und deklarieren eine Struktur, deren beide Elemente Zeiger auf den Datentyp int sind. Wie bei anderen Zeigern auch reicht die Deklaration allein nicht aus. Sie müssen diese Zeiger auch initialisieren, damit sie auf etwas zeigen. Denken Sie daran, dass Sie ihnen dazu auch die Adresse einer Variablen zuweisen können. Wenn Sie bereits kosten und zinsen als Variablen vom Typ int deklariert haben, könnten Sie schreiben:

erste.wert = &kosten;
erste.rate = &zinsen;

Nachdem die Zeiger initialisiert sind, können Sie den Indirektionsoperator (*), wie am Tag 8 erläutert, verwenden, um auf die referenzierten Werte zuzugreifen. Der Ausdruck *erste.wert greift auf den Wert von kosten zu und der Ausdruck *erste.rate auf den Wert von zinsen.

Der wahrscheinlich am häufigsten für Strukturelemente verwendete Zeigertyp ist ein Zeiger auf den Typ char. Zur Erinnerung: Am Tag 9, »Zeichen und Strings«, haben Sie gelernt, dass ein String eine Folge von Zeichen ist, die durch einen Zeiger auf das erste Zeichen des Strings und einem Nullzeichen für das Stringende genau umrissen ist. Frischen wir Ihr Gedächtnis noch ein wenig weiter auf. Um einen Zeiger auf char zu deklarieren und auf einen String zu richten, schreiben Sie:

char *z_nachricht;
z_nachricht = "C-Programmierung in 21 Tagen";

Und genauso können Sie auch mit Zeigern auf den Typ char verfahren; die Strukturelemente sind:

struct nrt {
char *z1;
char *z2;
} meinezgr;
meinezgr.z1 = "C-Programmierung in 21 Tagen";
meinezgr.z2 = "Markt & Technik-Verlag";

Abbildung 10.4 illustriert das Ergebnis dieser Anweisungen. Jedes Zeigerelement der Struktur zeigt auf das erste Byte eines Strings, der irgendwo im Speicher abgelegt ist. Vergleichen Sie dies mit Abbildung 10.2, wo gezeigt wurde, wie Daten in einer Struktur gespeichert werden, die Arrays vom Typ char enthält.

Abbildung 10.4:  Eine Struktur mit Zeigern auf char.

Sie können Strukturelement-Zeiger überall dort verwenden, wo Sie auch einen normalen Zeiger verwenden würden. Um zum Beispiel den String, auf den gezeigt wird, auszugeben, würden Sie schreiben:

printf("%s %s", meinezgr.z1, meinezgr.z2);

Was ist der Unterschied zwischen einem Strukturelement, das als Array vom Typ char beziehungsweise als Zeiger auf den Typ char deklariert ist? Beide Deklarationen stellen eine Möglichkeit dar, wie man Strings in Strukturen »speichern« kann. Die nachfolgend definierte Struktur nrt demonstriert beide Methoden:

struct nrt {
char z1[30];
char *z2; /* Achtung: nicht initialisiert */
} meinezgr;

Denken Sie daran, dass ein Array-Name ohne eckige Klammern ein Zeiger auf das erste Array-Element darstellt. Deshalb können Sie diese zwei Strukturelemente in gleicher Weise verwenden (beachten Sie, dass z2 initialisiert sein sollte, bevor Sie einen Wert in z2 kopieren):

strcpy(meinezgr.z1, "C-Programmierung in 21 Tagen");
strcpy(meinezgr.z2, "Markt & Technik-Verlag");
/* hier steht sonstiger Code */
puts(meinezgr.z1);
puts(meinezgr.z2);

Worin aber unterscheiden sich diese beiden Methoden? Der Unterschied ist folgender: Wenn Sie eine Struktur definieren, die ein Array vom Typ char enthält, belegt jede Instanz dieses Strukturtyps einen Speicherbereich für ein Array der angegebenen Größe. Außerdem sind Sie auf die angegebene Größe beschränkt. Sie können keinen größeren String in der Struktur speichern. Sehen Sie dazu folgendes Beispiel:

struct nrt {
char z1[10];
char z2[10];
} meinezgr;
...
strcpy(z1, "Minneapolis"); /* Falsch! String länger als Array. */
strcpy(z2, "MN"); /* OK, aber verschwendet Speicherplatz, */
/* da der String kürzer als das Array ist. */

Wenn Sie dagegen eine Struktur definieren, die Zeiger auf den Typ char enthält, gelten diese Beschränkungen nicht mehr. Die einzelnen Instanzen der Struktur belegen lediglich Speicherbereich für die Zeiger. Die eigentlichen Strings sind irgendwo sonst im Speicher abgelegt (wo genau, braucht Sie nicht zu kümmern). Es gibt weder Längenbeschränkungen noch verschwendeten Speicherplatz. Die eigentlichen Strings werden nicht als Teil der Struktur gespeichert. Jeder Zeiger in der Struktur kann auf einen String einer beliebigen Länge zeigen. Damit wird der String Teil der Struktur, ohne in der Struktur selbst gespeichert zu sein.

Wenn Sie die Zeiger nicht initialisieren, laufen Sie Gefahr, unabsichtlich Speicher zu überschreiben, der bereits anderweitig verwendet wird. Wenn Sie einen Zeiger statt eines Arrays verwenden, müssen Sie vor allem daran denken, den Zeiger zu initialisieren. Dazu können Sie dem Zeiger die Adresse einer anderen Variablen zuweisen oder dynamisch Speicher für den Zeiger reservieren.

Zeiger auf Strukturen

Zeiger auf Strukturen werden genauso deklariert und verwendet wie Zeiger auf jeden anderen Datentyp. Weiter hinten in dieser Lektion werden Sie lernen, dass Zeiger auf Strukturen vornehmlich dann verwendet werden, wenn Strukturen als Argumente an Funktionen übergeben werden sollen. Zeiger auf Strukturen werden darüber hinaus auch in einem sehr mächtigen Konstrukt zur Datenspeicherung, den so genannten verketteten Listen, eingesetzt. Verkettete Listen werden am Tag 14, »Zeiger für Fortgeschrittene«, genauer beschrieben.

An dieser Stelle wollen wir uns anschauen, wie Sie in einem Programm Zeiger auf Strukturen erzeugen und verwenden. Dazu müssen Sie zuerst eine Struktur definieren:

struct teil {
int zahl;
char name[10];
};

Deklarieren Sie jetzt einen Zeiger auf den Typ teil:

struct teil *z_teil;

Denken Sie daran, dass der Indirektionsoperator (*) in der Deklaration besagt, dass z_teil ein Zeiger auf den Typ teil und keine Instanz des Typs teil ist.

Kann der Zeiger jetzt initialisiert werden? Nein, denn auch wenn die Struktur teil bereits definiert ist, wurden noch keine Instanzen der Struktur deklariert. Denken Sie daran, dass Speicherplatz für Datenobjekte nur durch eine Deklaration und nicht durch eine Definition reserviert werden kann. Da ein Zeiger eine Speicheradresse benötigt, auf die er zeigen kann, müssen Sie zuerst eine Instanz vom Typ teil deklarieren, auf die Sie verweisen können. Die Deklaration der Strukturvariablen sieht folgendermaßen aus:

struct teil gizmo;

Jetzt können Sie den Zeiger initialisieren:

z_teil = &gizmo;

Diese Anweisung weist dem Zeiger z_teil die Adresse von gizmo zu. (Vergessen Sie dabei nicht den Adressoperator &.) Abbildung 10.5 veranschaulicht die Beziehung zwischen einer Struktur und einem Zeiger auf eine Struktur.

Abbildung 10.5:  Ein Zeiger auf eine Struktur zeigt auf das erste Byte der Struktur.

Was machen Sie jetzt mit diesem Zeiger auf die Struktur gizmo? Eine Möglichkeit bestünde darin, den Indirektionsoperator (*) zu verwenden. Wenn zgr ein Zeiger auf ein Datenobjekt ist, dann bezieht sich der Ausdruck *zgr auf das Objekt, auf das gezeigt wird.

Übertragen wir dies auf unser Beispiel. Wir wissen, dass z_teil ein Zeiger auf die Strukturvariable gizmo ist. Demzufolge verweist *z_teil auf gizmo. Mit dem Punktoperator (.) können Sie auf die einzelnen Elemente von gizmo zugreifen. Um gizmo.zahl den Wert 100 zuzuweisen, könnten Sie schreiben:

(*z_teil).zahl = 100;

*z_teil muss in Klammern stehen, da der Punktoperator (.) eine höhere Priorität hat als der Indirektionsoperator (*).

Der zweite Weg, um über einen Zeiger auf eine Struktur auf die Elemente der Struktur zuzugreifen, verwendet den Elementverweis-Operator, der aus den Zeichen -> (einem Bindestrich gefolgt von einem Größer-Zeichen) besteht. Beachten Sie, dass diese Zeichen in der Kombination als ein einziger Operator und nicht als zwei Operatoren betrachtet werden.) Dieses Symbol wird zwischen den Zeigernamen und den Elementnamen gesetzt. Um zum Beispiel mit Hilfe des Zeigers z_teil auf das gizmo-Element zahl zuzugreifen, schreiben Sie:

z_teil->zahl

Betrachten wir ein weiteres Beispiel: Wenn str eine Struktur, z_str ein Zeiger auf str und elem ein Element von str ist, können Sie auf str.elem auch auf folgendem Wege zugreifen:

z_str->elem

Demzufolge gibt es drei Wege, um auf die Elemente einer Struktur zuzugreifen:

Wenn z_str ein Zeiger auf die Struktur str ist, das sind die folgenden drei Ausdrücke alle äquivalent:

str.elem
(*z_str).elem
z_str->elem

Zeiger und Arrays von Strukturen

Sie haben gesehen, dass Arrays von Strukturen genauso wie Zeiger auf Strukturen sehr mächtige Programmkonstrukte darstellen. Sie können beide Konstrukte auch kombinieren und mit Zeigern auf Strukturen zugreifen, die Elemente eines Arrays sind.

Zur Veranschaulichung erst einmal die Definition einer Struktur aus einem früheren Beispiel:

struct teil {
int zahl;
char name[10];
};

Nachdem die Struktur teil definiert ist, können Sie ein Array mit Elementen vom Typ teil deklarieren:

struct teil daten[100];

Als Nächstes können Sie einen Zeiger auf eine Variable vom Typ teil deklarieren und so initialisieren, dass er auf die erste Struktur im Array daten zeigt:

struct teil *z_teil;
z_teil = &daten[0];

Denken Sie daran, dass der Name eines Arrays ohne eckige Klammern ein Zeiger auf das erste Array-Element ist, so dass die zweite Zeile auch folgendermaßen hätte geschrieben werden können:

z_teil = daten;

Damit haben Sie jetzt ein Array mit Strukturelementen des Typs teil und einen Zeiger auf das erste Array-Element (das heißt, die erste Struktur in dem Array). Sie könnten jetzt zum Beispiel den Inhalt des ersten Elements mit der folgenden Anweisung ausgeben:

printf("%d %s", z_teil->zahl, z_teil->name);

Und wie gehen Sie vor, wenn Sie alle Array-Elemente ausgeben wollen? Wahrscheinlich würden Sie eine for-Schleife verwenden und bei jedem Durchgang der Schleife ein Array-Element ausgeben. Um mit Hilfe der Zeigernotation auf die Elemente zuzugreifen, müssen Sie den Zeiger z_teil so inkrementieren, dass er bei jedem Durchlauf der Schleife auf das nächste Array-Element zeigt (das heißt, die nächste Struktur in dem Array). Die Frage ist nur, wie?

Abhilfe schafft in diesem Falle die Zeigerarithmetik von C. Der unäre Inkrementoperator (++) hat in Kombination mit einem Zeiger - dank der Regeln der Zeigerarithmetik - eine besondere Bedeutung. Diese besagt: »Inkrementiere den Zeiger um die Größe des Objekts, auf das der Zeiger zeigt.« Anders ausgedrückt: Wenn Sie einen Zeiger zgr haben, der auf ein Datenobjekt vom Typ obj zeigt, hat die Anweisung

zgr++;

die gleiche Wirkung wie

zgr += sizeof(obj);

Dieser Aspekt der Zeigerarithmetik ist besonders für Arrays wichtig, da Array- Elemente sequentiell im Speicher abgelegt werden. Wenn ein Zeiger auf das Array- Element n zeigt, dann zeigt der Zeiger nach der Inkrementierung mittels des (++)- Operators auf das Element n+1. Schauen Sie sich dazu Abbildung 10.6 an, in der ein Array namens x[] zu sehen ist, das aus Elementen besteht, die 4 Byte lang sind (beispielsweise Strukturen, die zwei Elemente vom Typ short, der üblicherweise 2 Byte umfasst, enthalten). Nach der Initialisierung zeigt der Zeiger zgr auf x[0] und nach jeder Inkrementierung auf das jeweils nächste Array-Element.

Abbildung 10.6:  Mit jeder Inkrementierung rückt der Zeiger um ein Array-Element weiter.

Dies bedeutet, dass Ihr Programm ein Array von Strukturen (oder ein Array eines beliebigen anderen Datentyps) durchlaufen kann, indem es einen Zeiger inkrementiert. Diese Art der Notation ist normalerweise leichter und kürzer als die Verwendung von Array-Indizes für die gleiche Aufgabe. Listing 10.4 demonstriert, wie das geht.

Listing 10.4: Zugriff auf sequentielle Array-Elemente durch Inkrementierung eines Zeigers.

1 : /* Beispiel für den Durchlauf eines Arrays von Strukturen  */
2 : /* mit Hilfe der Zeiger-Notation. */
3 :
4 : #include <stdio.h>
5 :
6 : #define MAX 4
7 :
8 : /* Definiert eine Struktur und deklariert und initialisiert */
9 : /* dann ein Array mit 4 Elementen der Strukturen. */
10:
11: struct teil {
12: int zahl;
13: char name[10];
14: } daten[MAX] = { {1, "Schmidt"},
15: {2, "Meier"},
16: {3, "Adams"},
17: {4, "Walter"}
18: };
19:
20: /* Deklariert einen Zeiger auf den Typ teil und eine Zähler-Variable. */
21:
22: struct teil *z_teil;
23: int count;
24:
25: int main()
26: {
27: /* Initialisiert den Zeiger mit dem ersten Array-Element. */
28:
29: z_teil = daten;
30:
31: /* Durchläuft das Array und inkrementiert den Zeiger */
32: /* mit jeder Iteration */
33:
34: for (count = 0; count < MAX; count++)
35: {
36: printf("An Adresse %lu: %d %s\n",
37: (unsigned long)z_teil, z_teil->zahl,
38: z_teil->name);
39: z_teil++;
40: }
41:
42: return 0;
43: }

An Adresse 134517984: 1 Brand
An Adresse 134518000: 2 Meier
An Adresse 134518016: 3 Adams
An Adresse 134518032: 4 Walter

Zuerst deklariert und initialisiert dieses Programm ein Array von teil-Strukturen, das den Namen daten erhält (Zeilen 11 bis 18). Achten Sie darauf, dass jede Struktur in der Array-Initialisierung von einem Paar geschweifter Klammern umschlossen sein muss. Danach wird in Zeile 22 ein Zeiger namens z_teil definiert, der auf die Struktur daten zeigen soll. Die erste Aufgabe der Funktion main() besteht darin, in Zeile 29 den Zeiger z_teil auf das erste teil -Strukturelement in daten zu setzen. Anschließend werden mit Hilfe der for-Schleife in den Zeilen 34 bis 39 alle Elemente ausgegeben, indem der Zeiger auf das Array bei jedem Durchlauf inkrementiert wird. Gleichzeitig gibt das Programm zu jedem Element die Adresse aus.

Schauen Sie sich die Adressen einmal genauer an. Die tatsächlichen Werte werden auf Ihrem System anders aussehen, aber die Inkrementierung sollte in der gleichen Schrittweite erfolgen. In diesem Fall besteht die Struktur aus einem int (4 Byte) und einem Array von zehn Zeichen namens char, was zusammen 14 Byte macht. Wo kommen die zusätzlichen 2 Byte her? Der C-Compiler versucht den Speicher so aufzuteilen, dass die CPU schnell und möglichst effizient auf die Daten zugreifen kann. Die CPU bevorzugt für die Speicherung von int-Variablen von 4 Byte Länge Speicheradressen, die durch vier teilbar sind. Deshalb werden zwischen Strukturen von 14 Byte zwei Füllbyte untergebracht, so dass die nachfolgenden Strukturelemente wieder an 4-Byte-Grenzen ausgerichtet sind.

Strukturen als Argumente an Funktionen übergeben

Wie Variablen anderer Datentypen kann man auch Strukturen als Funktionsargumente übergeben. Listing 10.5 zeigt Ihnen, wie das geht. Das Programm ist eine Neufassung des Programms aus Listing 10.2. In Listing 10.5 werden die Daten mit Hilfe einer eigenen Funktion auf den Bildschirm ausgegeben, während in Listing 10.2 dazu Anweisungen verwendet werden, die Teil von main() sind.

Listing 10.5: Eine Struktur als Funktionsargument übergeben.

1:  /* Beispiel für die Übergabe einer Struktur als Argument. */
2:
3: #include <stdio.h>
4:
5: /* Deklariert und definiert eine Struktur zur Aufnahme der Daten. */
6:
7: struct daten {
8: float betrag;
9: char vname[30];
10: char nname[30];
11: } rec;
12:
13: /* Der Funktionsprototyp. Die Funktion hat keinen Rückgabewert */
14: /* und übernimmt eine Struktur vom Typ daten als einziges Argument. */
15:
16: void ausgabe(struct daten x);
17:
18: int main(void)
19: {
20: /* Eingabe der Daten über die Tastatur. */
21:
22: printf("Geben Sie den Vor- und Nachnamen des Spenders,\n");
23: printf("getrennt durch ein Leerzeichen, ein: ");
24: scanf("%s %s", rec.vname, rec.nname);
25:
26: printf("\nGeben Sie die Höhe der Spende ein: ");
27: scanf("%f", &rec.betrag);
28:
29: /* Aufruf der Funktion zur Ausgabe. */
30: ausgabe( rec );
31:
32: return 0;
33: }
34: void ausgabe(struct daten x)
35: {
36: printf("\nSpender %s %s gab %.2f DM.\n", x.vname, x.nname,
37: x.betrag);
38: }

Geben Sie den Vor- und Nachnamen des Spenders,
getrennt durch ein Leerzeichen, ein: Bradley Jones

Geben Sie die Höhe der Spende ein: 1000.00

Spender Bradley Jones gab 1000.00 DM.

In Zeile 16 finden Sie den Funktionsprototyp für die Funktion, die die Struktur als Argument übernehmen soll. Wie bei allen anderen Datentypen auch müssen Sie die korrekten Parameter deklarieren. In diesem Fall ist es eine Struktur des Typs daten. Zeile 34 wiederholt dies im Funktionsheader. Wenn Sie die Funktion aufrufen, müssen Sie nur den Namen der Strukturinstanz - hier rec (Zeile 30) - übergeben. Das ist schon alles. Die Übergabe einer Struktur an eine Funktion unterscheidet sich nicht sehr von der einer einfachen Variablen.

Sie können eine Struktur einer Funktion auch übergeben, indem Sie die Adresse der Struktur übergeben (das heißt einen Zeiger auf die Struktur). Tatsächlich war dies in älteren Versionen von C die einzige Möglichkeit, um eine Struktur als Argument zu übergeben. Heutzutage ist das nicht mehr nötig, aber Sie können immer noch auf ältere Programme stoßen, die diese Methode verwenden. Wenn Sie einen Zeiger auf eine Struktur als Argument übergeben, müssen Sie allerdings daran denken, den Elementverweis-Operator (->) zu verwenden, um auf die Elemente der Struktur in der Funktion zuzugreifen.

Was Sie tun sollten

Was nicht

Nutzen Sie die Vorteile, die die Deklaration von Zeigern auf Strukturen bietet - besonders wenn Sie Arrays von Strukturen verwenden.

Verwenden Sie den Elementverweis- Operator (->), wenn Sie mit Zeigern auf Strukturen arbeiten.

Verwechseln Sie Arrays nicht mit Strukturen!

Vergessen Sie bei der Inkrementierung eines Zeigers nicht, dass er dadurch um einen Betrag verschoben wird, der der Größe der Daten entspricht, auf die er gerade zeigt. Im Falle eines Zeiger auf eine Struktur ist dies die Größe der Struktur.

Unions

Unions sind den Strukturen sehr ähnlich. Eine Union wird genauso deklariert und verwendet wie eine Struktur. Der einzige Unterschied liegt darin, dass in einer Union immer nur eines der deklarierten Elemente zur Zeit verwendet werden kann. Der Grund dafür ist einfach. Alle Elemente einer Union belegen den gleichen Speicherbereich - sie liegen quasi übereinander.

Unions definieren, deklarieren und intialisieren

Unions werden auf die gleiche Art und Weise definiert und deklariert wie Strukturen. Der einzige Unterschied in den Deklarationen besteht darin, dass anstelle des Schlüsselwortes struct das Schlüsselwort union verwendet wird. Um eine einfache Union zu definieren, die eine char-Variable und eine Integer-Variable enthält, würden Sie schreiben:

union shared {
char c;
int i;
};

Diese Union namens shared kann dazu verwendet werden, Instanzen einer Union zu erzeugen, die entweder einen Zeichenwert c oder einen Integerwert i aufnehmen. Dabei handelt es sich um eine ODER-Bedingung. Im Gegensatz zu einer Struktur, die beide Werte aufnehmen könnte, kann die Union nur einen Wert zur Zeit enthalten. Abbildung 10.7 demonstriert, wie die Union shared im Speicher angelegt würde.

Abbildung 10.7:  Eine Union kann nur einen Wert zur Zeit enthalten.

Unions können bei ihrer Deklaration initialisiert werden. Da nur immer eines ihrer Elemente zur Zeit verwendet werden kann, kann auch nur eines initialisiert werden. Um Verwirrung zu vermeiden, kann nur das erste Element der Union initialisiert werden. Der folgende Code zeigt die Deklaration und Initialisierung einer Instanz der Union shared:

union shared generische_variable = {'@'};

Beachten Sie, dass die Union generische_variable genauso initialisiert wurde wie das erste Element einer Struktur.

Zugriff auf Unionelemente

Einzelne Unionelemente können genauso verwendet werden wie die Elemente einer Struktur - mit Hilfe des Punktoperators (.). Beim Zugriff auf die Unionelemente gibt es jedoch einen wichtigen Unterschied: Da eine Union ihre Elemente übereinander speichert, ist es wichtig, immer nur auf ein Element zur Zeit zuzugreifen.

Listing 10.6: Ein Beispiel für die falsche Anwendung von Unions.

1:   /* Beispiel für den Zugriff auf mehr als ein Unionelement zur Zeit */
2: #include <stdio.h>
3:
4: int main(void)
5: {
6: union u_shared {
7: char c;
8: int i;
9: long l;
10: float f;
11: double d;
12: } shared;
13:
14: shared.c = '$';
15:
16: printf("\nchar c = %c", shared.c);
17: printf("\nint i = %d", shared.i);
18: printf("\nlong l = %ld", shared.l);
19: printf("\nfloat f = %f", shared.f);
20: printf("\ndouble d = %f", shared.d);
21:
22: shared.d = 123456789.8765;
23:
24: printf("\n\nchar c = %c", shared.c);
25: printf("\nint i = %d", shared.i);
26: printf("\nlong l = %ld", shared.l);
27: printf("\nfloat f = %f", shared.f);
28: printf("\ndouble d = %f\n", shared.d);
29:
30: return 0;
31: }

char c   = $
int i = 36
long l = 36
float f = 0.000000
double d = -1.998047

char c = 7
int i = 1468107063
long l = 1468107063
float f = 284852666499072.000000
double d = 123456789.876500

In diesem Listing sehen Sie wie in den Zeilen 6 bis 12 eine Union definiert und eine Unionvariable namens shared deklariert wird. shared enthält fünf Elemente von jeweils unterschiedlichem Datentyp. Die Zeilen 14 und 22 weisen verschiedenen Elementen von shared Werte zu. Die Zeilen 16 bis 20 und 24 bis 28 geben dann die Werte der einzelnen Elemente mit printf()-Anweisungen aus.

Beachten Sie, dass mit Ausnahme von char c = $ und double d = 123456789.876500 die Ausgabe auf Ihrem Computer nicht gleichlautend sein muss. Da die Zeichenvariable c in Zeile 14 initialisiert wurde, ist dies der einzige Wert, der verwendet werden sollte, bis ein anderes Element initialisiert wird. Die Ausgabe der anderen Unionelement-Variablen (i, l, f und d) kann zu unvorhersehbaren Ergebnissen führen (Zeile 16 bis 20). Zeile 22 legt einen Wert in der double-Variablen d ab. Beachten Sie, dass auch hier wieder die Ausgabe der anderen Unionelemente zu unvorhersehbaren Ergebnissen führen kann. Der Wert, mit dem c in Zeile 14 initialisiert wurde, ist jetzt verloren, da er mit der Zuweisung an d überschrieben wurde (Zeile 22). Dies beweist, dass die Elemente alle den gleichen Speicherplatz belegen.

Das Schlüsselwort union

union name {
union_element(e);
/* hier stehen weitere Anweisungen */
}instanz;

Unions werden mit dem Schlüsselwort union deklariert. Eine Union ist eine Sammlung von einer oder mehreren Variablen (union_elemente), die unter einem Namen zusammengefasst wurden. Darüber hinaus belegen alle Unionelemente den gleichen Speicherplatz.

Das Schlüsselwort union markiert den Anfang einer Uniondefinition. Es wird gefolgt von dem Namen der Union. Auf den Namen folgen in geschweiften Klammern die Unionelemente. Gleichzeitig kann eine Instanz, eine Unionvariable, deklariert werden. Wenn Sie eine Union ohne eine Instanz definieren, erhalten Sie nur eine Schablone, die Sie später in einem Programm verwenden können, um Unionvariablen zu deklarieren. Das Format einer Schablone sieht folgendermaßen aus:

union name {
union_element(e);
/* stehen weitere Anweisungen. */
};

Um auf der Grundlage der Schablone Instanzen zu deklarieren, verwenden Sie folgende Syntax:

union name instanz;

Dies Format setzt allerdings voraus, dass Sie bereits vorher eine Union mit dem angegebenen Namen definiert haben.

Beispiel 1

/* Deklariert eine Unionschablone namens meine_union. */
union meine_union {
int nbr;
char zeichen;
};
/* Nutzen der Unionschablone */
union meine_union beliebig_variable;

Beispiel 2

/* Deklariert eine Union zusammen mit einer Instanz. */
union generischer_typ {
char c;
int i;
float f;
double d;
} generisch;

Beispiel 3

/* Initialisiert eine Union. */
union u_datum {
char volles_datum[9];
struct s_datum {
char monat[2];
char trennwert1;
char tag[2];
char trennwert2;
char jahr[2];
} teil_datum;
}datum = {"01.01.00"};

Listing 10.7 gibt Ihnen ein etwas praxisnäheres Beispiel für den Einsatz einer Union. Das Beispiel ist zwar noch sehr einfach, beschreibt aber einen typischen Einsatzbereich einer Union.

Listing 10.7: Praxisnaher Einsatz einer Union.

1:   /* Typische Verwendung einer Union. */
2:
3: #include <stdio.h>
4:
5: #define CHARACTER 'C'
6: #define INTEGER 'I'
7: #define FLOAT 'F'
8:
9: struct s_generisch{
10: char typ;
11: union u_shared {
12: char c;
13: int i;
14: float f;
15: } shared;
16: };
17:
18: void ausgabe( struct s_generisch generisch );
19:
20: int main(void)
21: {
22: struct s_generisch var;
23:
24: var.typ = CHARACTER;
25: var.shared.c = '$';
26: ausgabe( var );
27:
28: var.typ = FLOAT;
29: var.shared.f = (float) 12345.67890;
30: ausgabe( var );
31:
32: var.typ = 'x';
33: var.shared.i = 111;
34: ausgabe( var );
35: return 0;
36: }
37: void ausgabe( struct s_generisch generisch )
38: {
39: printf("\nDer generische Wert ist...");
40: switch( generisch.typ )
41: {
42: case CHARACTER: printf("%c\n", generisch.shared.c);
43: break;
44: case INTEGER: printf("%d\n", generisch.shared.i);
45: break;
46: case FLOAT: printf("%f\n", generisch.shared.f);
47: break;
48: default: printf("Typ unbekannt: %c\n",
49: generisch.typ);
50: break;
51: }
52: }

Der generische Wert ist...$

Der generische Wert ist...12345.678711

Der generische Wert ist...Typ unbekannt: x

Dieses Programm gibt ein einfaches Beispiel für den Einsatz einer Union. Das Programm ist in der Lage, Variablen unterschiedlicher Datentypen an einer Speicherstelle abzulegen. Die Struktur s_generisch ermöglicht es, ein Zeichen, einen Integer oder eine Fließkommazahl in dem gleichen Bereich zu speichern. Dieser Bereich ist eine Union, die den Namen u_shared trägt und sich genauso verhält wie die Union in Listing 10.6. Beachten Sie, dass die Struktur s_generisch ein zusätzliches Element namens typ deklariert. Dieses Element wird verwendet, um Informationen zu dem Typ der in u_shared enthaltenen Variablen aufzunehmen. Mit typ wird verhindert, dass shared falsch verwendet wird und fehlerhafte Daten wie in Listing 10.6 ausgegeben werden.

Ein kurzer Blick auf das Programm zeigt, dass in den Zeilen 5, 6 und 7 die Konstanten CHARACTER, INTEGER und FLOAT definiert werden. Diese sollen später das Programm lesbarer machen. Die Zeilen 9 bis 16 definieren die Struktur s_generisch, die später verwendet wird. Zeile 18 enthält den Prototyp der Funktion ausgabe(). In Zeile 22 wird die Strukturvariable var deklariert und in den Zeilen 24 und 25 mit einem Zeichenwert initialisiert. Ein Aufruf von ausgabe() in Zeile 26 gibt den Wert aus. Die Zeilen 28 bis 30 und 32 bis 34 wiederholen diesen Vorgang für andere Werte.

Die Funktion ausgabe() ist das Kernstück dieses Listings. Im Programm wird diese Funktion dazu verwendet, den Wert einer s_generisch-Variablen auszugeben. Eine ähnliche konstruierte Funktion hätte auch zur Initialisierung verwendet werden können. Die Funktion ausgabe() prüft den Inhalt des Elements typ und gibt dann den Inhalt der Union als Wert des gefundenen Variablentyps aus. Damit werden fehlerhafte Ausgaben wie in Listing 10.6 vermieden.

Was Sie tun sollten

Was nicht

Merken Sie sich, welches Unionelement gerade verwendet wird. Wenn Sie ein Element eines Typs aufnehmen und dann versuchen, einen anderen Typ auszulesen, kann es zu unvorhergesehenen Ergebnissen kommen.

Beachten Sie, dass Unions zu den fortgeschrittenen C-Themen gehören.

Versuchen Sie nicht, mehr als das erste Unionelement zu initialisieren.

Vergessen Sie nicht, dass die Größe einer Union gleich dem größten Element ist.

Mit typedef Synonyme für Strukturen definieren

Sie können mit dem Schlüsselwort typedef ein Synonym für eine Struktur oder einen Uniontyp erzeugen. Zum Beispiel definiert folgende Anweisung koord als ein Synonym für die angegebene Struktur:

typedef struct {
int x;
int y;
} koord;

Instanzen dieser Struktur können dann mit dem koord-Bezeichner deklariert werden:

koord obenlinks, untenrechts;

Beachten Sie, dass sich typedef von einem Strukturnamen, wie er weiter vorn in dieser Lektion beschrieben wurde, unterscheidet. Wenn Sie schreiben:

struct koord {
int x;
int y;
};

ist der Bezeichner koord der Name der Struktur. Sie können den Namen zur Deklaration von Instanzen der Struktur verwenden, müssen aber im Gegensatz zu typedef das Schlüsselwort struct voranstellen:

struct koord obenlinks, untenrechts;

Es macht eigentlich keinen großen Unterschied, ob Sie typedef oder den Strukturnamen zur Deklaration von Strukturenvariablen verwenden. Mit typedef erhalten Sie etwas kürzeren Code, da Sie auf das Schlüsselwort struct verzichten können. Andererseits ist es vielleicht gerade das Schlüsselwort struct im Zusammenspiel mit dem Strukturnamen, das deutlich macht, dass hier eine Strukturvariable deklariert wird.

Zusammenfassung

Die heutige Lektion hat Ihnen gezeigt, wie sich Strukturen einsetzen lassen - ein Datentyp, den Sie individuell den Bedürfnissen Ihres Programms anpassen können. Strukturen können Elemente jeden beliebigen C-Datentyps enthalten, einschließlich anderer Strukturen, Zeiger und Arrays. Der Zugriff auf die einzelnen Datenelemente einer Struktur erfolgt über den Punktoperator (.), der zwischen den Strukturnamen und den Elementnamen gesetzt wird. Strukturen können einzeln, aber auch in Arrays verwendet werden.

Unions sind den Strukturen sehr ähnlich. Der Hauptunterschied besteht darin, dass eine Union alle ihre Elemente im gleichen Speicherbereich ablegt. Dies hat zur Folge, dass immer nur ein Element einer Union zur Zeit genutzt werden kann.

Fragen und Antworten

Frage:
Gibt es Gründe, eine Struktur ohne eine Instanz zu deklarieren?

Antwort:
Heute haben Sie drei Wege kennen gelernt, eine Struktur zu deklarieren. Zum einen können Sie Strukturrumpf, Namen und Instanz alle gleichzeitig deklarieren, oder Sie deklarieren einen Strukturrumpf und eine Instanz, aber keinen Strukturnamen. Die dritte Möglichkeit besteht darin, die Struktur und den Namen ohne eine Instanz zu deklarieren. Die Instanz kann dann später mit dem Schlüsselwort struct, dem Strukturnamen und einem Namen für die Instanz »nachdeklariert« werden. Unter Programmierern ist es allgemein üblich, entweder die zweite oder dritte Variante anzuwenden. Viele Programmierer deklarieren den Strukturrumpf und den Namen ohne irgendwelche Instanzen. Diese werden dann bei Bedarf später im Programm nachgeholt. Am Tag 11 widmen wir uns dem Gültigkeitsbereich von Variablen. Gültigkeitsbereiche gelten auch für Instanzen, hingegen nicht für den Strukturnamen oder den Strukturrumpf.

Frage:
Was wird in der Praxis häufiger verwendet: typedef oder der Strukturname?

Antwort:
Viele Programmierer verwenden typedef, um ihren Code leichter lesbar zu machen. In der Praxis ist der Unterschied allerdings gering. Viele der mit Linux ausgelieferten Bibliotheken verwenden hauptsächlich typedef, um dem Bibliotheksverwalter die Arbeit zu erleichtern.

Frage:
Kann ich eine Strukturvariable mit Hilfe des Zuweisungsoperators einer anderen Strukturvariablen zuweisen?

Antwort:
Ja und nein. Neuere C-Compiler, einschließlich des GNU-C-Compilers, erlauben die Zuweisung einer Strukturvariablen (oder einer Unionvariablen) an eine des gleichen Typs. In älteren C-Versionen kann es passieren, dass Sie jedes Element der Struktur einzeln zuweisen müssen! Wenn Sie wollen, dass Ihr Code auch auf ältere Compiler portierbar ist, sollten Sie lieber zweimal überlegen, bevor Sie von dieser Möglichkeit Gebrauch machen.

Frage:
Wie groß ist eine Union?

Antwort:
Da jedes Element einer Union an ein und derselben Speicherstelle steht, ist der Speicherplatz, der von der Union belegt wird, gleich dem Platz, der von ihrem größten Element eingenommen wird.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, sowie Übungen, die Sie anregen sollen, das Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Die Lösungen zu den Fragen und den Übungen finden Sie in Anhang C.

Quiz

  1. Worin unterscheidet sich eine Struktur von einem Array?
  2. Was versteht man unter einem Punktoperator und wozu dient er?
  3. Wie lautet das Schlüsselwort, mit dem man in C eine Struktur erzeugt?
  4. Was ist der Unterschied zwischen einem Strukturnamen und einer Strukturinstanz?
  5. Was geschieht in dem folgenden Codefragment?
    struct adresse {
    char name[31];
    char adr1[31];
    char adr2[31];
    char stadt[11];
    char staat[3];
    char plz[11];
    } meineadresse = { "Bradley Jones",
    "RTSoftware",
    "P.O. Box 1213",
    "Carmel", "IN", "46082-1213"};
  6. 6. Angenommen Sie haben ein Array von Strukturen deklariert und einen Zeiger zgr auf das erste Array-Element (das heißt das erste Strukturelement in dem Array) gesetzt. Wie richten Sie zgr auf das zweite Array-Element?

Übungen

  1. Schreiben Sie Code, der eine Struktur namens zeit definiert, die drei int- Elemente enthält.
  2. Schreiben Sie Code, der zwei Aufgaben ausführt: Er soll eine Struktur namens daten definieren, die ein Element vom Typ int und zwei Elemente vom Typ float enthält, und er soll eine Instanz namens info vom Typ daten deklarieren.
  3. Fahren Sie mit Übung 2 fort und weisen Sie dem Integer-Element der Struktur info den Wert 100 zu.
  4. Deklarieren und initialisieren Sie einen Zeiger auf info.
  5. Fahren Sie mit Übung 4 fort und demonstrieren Sie zwei Möglichkeiten, wie man dem ersten float-Element von info mit Hilfe der Zeigernotation den Wert 5.5 zuweisen kann.
  6. Definieren Sie einen Strukturtyp namens daten, der einen einzigen String von bis zu 20 Zeichen enthalten kann.
  7. Erzeugen Sie eine Struktur, die fünf Strings enthält: adresse1, adresse2, stadt, staat und plz. Erzeugen Sie mit typedef ein Synonym namens DATENSATZ, das zur Erzeugung von Instanzen dieser Struktur verwendet werden kann.
  8. Verwenden Sie das typedef-Synonym aus Übung 7 und initialisieren Sie ein Element namens meineadresse.
  9. FEHLERSUCHE: Was ist falsch an folgendem Code?
    struct {
    char tierkreiszeichen[21];
    int monat;
    } zeichen = "Löwe", 8;
  10. FEHLERSUCHE: Was ist falsch an folgendem Code?
    /* eine Union einrichten */
    union daten{
    char ein_wort[4];
    long eine_zahl;
    }generische_variable = { "WOW", 1000 };
12

vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH