Programmieren in ANSI-C
| ||||||||||||||||
|
Die erste Anweisung definiert
einfach einen Zeiger vom Typ double, die zweite Anweisung
definiert einen Zeiger vom Typ double und eine Variable vom
selben Typ, NICHT etwa zwei Zeiger vom Typ double!
Der schönste Zeiger nützt uns
nichts, wenn wir nicht auch an den Wert der Variablen herankommen,
auf die er zeigt. Nehmen wir einmal folgendes an:
x sei eine Variable vom Typ Double und p_x ein Zeiger auf eben
diese Variable x. Wie die beiden Typen definiert werden, haben
wir bereits gesehen, wie wir sie miteinander Verknüpfen folgt in
Kapitel 6.2. Wir weißen nun (zum Beispiel durch Benutzereingabe)
der Variablen x einen Wert (zum Beispiel 67.99 ) zu und wollen
diesen Wert in einem Unterprogramm bearbeiten. Dazu bekommt das
Unterprogramm (UP) den Zeiger p_x übergeben. Damit nun das
UP den Wert ermitteln kann, der an der Adresse des Zeigers steht,
benötigen wir den Derefferenzier-Operator.
|
Die Klammern kann man auch weglassen, ich finde nur, es sieht übersichtlicher
aus, wenn man keine zwei Operatoren hintereinander schreibt (y =
2 + *p_x; wäre auch richtig!).
Es reicht natürlich
nicht, nur einen Zeiger zu definieren, man muss den Zeiger auch
mit einer Adresse (konkret: der Adresse einer Variablen) verknüpfen.
Dies gelingt mit dem Adress-Operator, den ich bereits in Kapitel
4 bei der ´scanf-Funktion stillschweigend eingeführt habe.
Bevor ich mir allerdings hier noch die Finger wund schreibe,
sehen wir uns lieber mal wieder ein Beispiel an:
Das folgende Beispiel soll eine Zahl einlesen und diese in
einer Funktion quadrieren. Eine ansich einfache Übung. Wir
wollen jedoch, dass die Funktion keinen Wert zurückgibt sondern
den Parameter selbst verändert.
Um zu verstehen, was Datenfelder sein sollen, bediene ich mich ein wenig der Mathematik. Ich setze voraus, das man weiß, was ein Vektor und eine Matrix ist. Betrachten wir nun folgendes Problem:
Wie stelle ich mit dem
bisherigen Handwerkszeug einen 3-dimensionalen Vektor dar?
Wir definieren drei Variablen x, y und z vom Typ Integer, oder
allgemeiner, drei Variablen x1, x2, x3.
Problem 1: Aufgrund neuer Erkentnisse soll unser Programm
nun von 3 auf 4 Dimensionen erweiter werden. Viel Spaß.
Problem 2: Wie stelle ich einen Vektor mit 25 Dimensionen
dar?
Darstellen könnte ich ihn vielleicht gerade noch, aber auf eine
Addition zweier solcher Vektoren möchte ich dann doch lieber
verzichten. Bitte keine Mails mit Fragen wie: 'Wie kann ich denn
nun eine [25x30]-Matrix multiplizieren?'
Lösung: Wir erstellen uns ein Datenfeld, das aus n
Komponenten besteht, also zum Beispiel 3 Variablen vom Typ
Integer hintereinander. Wir wollen dann über einen einheitlichen
Index auf die einzelnen Feldelemente zugreifen. Diesen Index können
wir nämlich leicht zum Beispiel über eine Schleife berechnen.
Dabei Spielt die Dimension überhaupt keine Rolle mehr, das
Programm kann beliebig erweitert werden, wenn wir die Dimension
als Konstante definieren.
Wir definieren Felder wie folgt:
|
Als Feldgrößen dürfen nur Konstante Werte eingetragen werden,
keine Variablen. Es spielt jedoch keine Rolle, ob die Werte über
#define oder const festgelegt wurden, oder ob sie direkt einen
Zahlenwert darstellen.
Indizierung
Um nun genau eine Komponente in einem Feld anzusprechen, muss man
diese Komponente indizieren. Dies kann direkt über einen
Zahlenwert oder über eine Variable geschehen. Zuvor muss man
allerdings wissen, wo die Grenzen des Feldes sind. Ein Feld
der Größe N beginnt mit dem Index 0 und endet mit dem Index N-1.
Alle anderen Werte für Indizes sind prinzipiell zwar möglich,
erzeugen allerdings Fehler zur Laufzeit des Programmes!!!!!
Beispiel
Wir haben eine Zeichenkette über die Tastatur in die Variable char
String[80]; eingelesen und wollen nun auf das erste Zeichen
dieses Strings zugreifen. Dazu kopieren wir es in die Variable char
zeichen; ein:
zeichen = String[0];
Wir erinnern uns: Der Index des ersten Zeichens ist die 0.
Felder und Zeiger
Denkbar ist natürlich auch der Umgang mit Zeigern auf ein Feld.
Wiederum muss der Zeiger vom selben Typ sein wie das Feld, die
Dimension des Feldes ist dabei egal, der Zeiger beherbergt nur
die Adresse eines Elementes aus dem Feld. Wichtig ist die
Tatsache, dass der Name des Feldes alleine bereits die Adresse
des ersten Elementes repräsentiert. Wir können einen Zeiger
also auf Folgende Arten mit dem ersten Element des Feldes
verbinden:
p_Feld = Feldname;
p_Feld = &Feldname[0];
Beide Anweisungen sind äquivalent, ich werde die erste
Schreibweise vorziehen. Reine Geschmackssache.
Bevor wir uns ein Beispiel ansehen werden, möchte ich noch ein
paar Worte über Zeichenketten verlieren, die einen wichtigen
Bestandteil jedes Programmes darstellen:
Um zu verstehen, wie
C/C++ Zeichenketten (Strings) verwaltet, müssen wir uns klar
machen, was eine Zeichenkette ansich ist. Jedes Wort besteht im
Prinzip aus einer Kette von Zeichen, die auf dem Papier
hintereindander aufgeschrieben oder hintereinander ausgesprochen
werden. Ein Rechner legt diese Ketten hintereinander im Speicher
ab wie wir sie auf ein Papier hintereinander schreiben. Dazu
eigenen sich die gerade eingeführten Felder optimal. Eine
Zeichenkette ist nichts anderes als ein Datenfeld vom Typ char
Wir wollen nun zum Beispiel das Wort "Giraffenhals" in
ein solches Feld speichern. Wir benötigen also ein Feld der Größe
12 (z.B. char Wort[12];). Was aber, wenn wir als nächstes 'HALLO'
in die Variable speichern? Gibt uns der Rechner dann HALLOfenhals
als Ergebnis aus? Eindeutig nicht!
Um zu erkennen, dass eine Zeichenkette an einer bestimmten Stelle
beendet ist, setzt der Rechner hinter den letzten Buchstaben eine
binäre NULL als Ende-Kennzeichen. Um diese NULL von der
Zahl '0' zu unterscheiden, schreibt man \0. Die NULL benötigt
natürlich ebenfalls Platz in unserem Feld, weshalb wir für
unseren GIRAFFENHALS schon 13 Plätze reservieren müssen. Alles,
was nach \0 im Speicher steht, interessiert den Rechner dann gar
nicht mehr. Er bricht bei \0 ab!
Um das ganze jetzt zu konkretisieren, hier ein kleines Beispiel.
Wir Lesen eine Zeichenkette ein, messen ihre Länge und geben
einzelne Buchstaben wieder aus.
Oftmals ist es sinnvoll,
zusammengehörige Daten (z.B. Adressdaten) zu einem Block
zusammenfassen. Dazu lassen sich aus den Standarttypen eigene
strukturierte Typen zusammenbauen. So kann in einem Adressbuch
zum Beispiel folgendes zusammengefasst sein:
|
Wir können alle diese Daten nun
zu einem neuen Datentyp zusammenfassen. Es gibt viele
verschiedene Wege zu diesem Ziel zu kommen. Ich werde hier
allerdings nur auf eine einzige gezielt eingehen, alle anderen
sind abgespeckte Versionen, die im Prinzip zum gleichen Ziel führen.
Grob umrissen benötigen wir zwei getrennte Befehle zur
Definition unseres neuen Typs: struct und typedef.
Mit struct Teilen wir dem Compiler mit, das wir eine neue
Struktur einführen wollen, mit typedef legen wir diese Struktur
als neuen Datentyp fest. Unsere Adressdatenbank sieht dann wie
folgt aus:
|
Das Strukturetikett kann man auch weglassen, es gewinnt erst in Kapitel 6.5 an
Bedeutung. Das Strukturetikett verleiht der Struktur selbst einen
Namen, der Strukturtypname gibt dem Typen seinen Namen. Im Moment
soll uns nur der Strukturtypname interessieren. Mit ihm wird
unser Konstrukt auch im Programm angesprochen. Typdefinitionen dürfen
im übrigen nur außerhalb von Funktionen definiert werden.
Um zu sehen, wie man auf Strukturen konkret zugreift, werde ich
nun anhand eines Beispieles konkretisieren. Ich möchte eine
kleine Adress-Datenbank schreiben, die ich auch in späteren
Kapiteln weiterentwickeln werde. Zunächst werde ich eine
Routiene entwickeln, die die Eingabe von Daten erlaubt und
entweder alle oder nur bestimmte Einträge ausgibt. Dazu gibt es
eine Suche nach dem Nachnamen. In dem Zusammenhang erkläre ich
auch die Verwendung von Operationen zur Bearbeitung von
Zeichenketten in Form von Kommentaren im Quelltext.
Aufgabe: Ergäne das Programm um eine Suchfunktion, die
nach der Postleitzahl sucht und erweitere das Menü um die
entsprechende Funktion 4. Die Lösung gibts bei der nächsten
Verwendung der Datenbank.
Programmieren mit Zeigern:
|
Programmieren mit Strukturen:
|
|
© Gerhard Zapf
|
||||
Letzte Änderung: 11.09.2003 |
||||