Wie Sie diesem Buch bereits entnehmen konnten, beruht ein großer Teil der Leistungsstärke von C auf der C-Standardbibliothek. Heute möchte ich einige Funktionen besprechen, die sich nicht so recht in die Themen der anderen Lektionen einordnen lassen. Dazu gehören:
Die Standardbibliothek von C enthält eine Reihe von Funktionen, mit denen
mathematische Operationen ausgeführt werden können. Die Prototypen für diese
Funktionen befinden sich in der Header-Datei math.h
. Die mathematischen
Funktionen liefern alle einen Wert vom Typ double
zurück. In den trigonometrischen
Funktionen werden die Winkel statt in Grad im Bogenmaß gemessen - wie es auch in
der Mathematik üblich ist. Denken Sie daran, dass ein Radiant (Einheit des
Bogenmaßes) gleich 57,296 Grad ist und ein voller Kreis (360 Grad) aus 2p
Radianten besteht.1
Die trigonometrischen Funktionen führen Berechnungen durch, wie sie in Grafik- und Ingenieursprogrammen benötigt werden.
Exponential- und logarithmische Funktionen werden für bestimmte Arten von mathematischen Berechnungen benötigt.
Die hyperbolischen Funktionen führen hyperbolische trigonometrische Berechnungen aus.
Die Standardbibliothek in C enthält noch diverse andere mathematische Funktionen;
Man könnte ein ganzes Buch mit den Programmen zu den mathematischen Funktionen füllen. Listing 17.1 enthält ein einfaches Programm, in dem mehrere dieser Funktionen verwendet werden. Bitte beachten Sie, dass Sie zum Kompilieren dieses Programms Ihrem gcc-Compiler mitteilen müssen, dass mathematische Funktionen verwendet werden, die in einer separaten Bibliothek untergebracht sind. Zum Kompilieren dieses Programms geben Sie folgenden Befehl ein:
gcc -Wall -ggdb list1701.c -lm -o list1701
Der Parameter -l
fordert gcc auf, eine Bibliothek einzubinden. Im Falle der
mathematischen Bibliothek heißt diese schlicht m
. Wir werden uns am Tag 20,
»Compiler für Fortgeschrittene«, noch eingehender mit dem Thema Bibliotheken
beschäftigen.
Listing 17.1: Mathematische Funktionen der C-Bibliothek.
1: /* Einsatz der mathematischen C-Funktionen */
2:
3: #include <stdio.h>
4: #include <math.h>
5:
6: int main(void)
7: {
8:
9: double x;
10:
11: printf("Geben Sie ein Zahl ein: ");
12: scanf( "%lf", &x);
13:
14: printf("\n\nOriginalwert: %lf", x);
15:
16: printf("\nAufgerundet: %lf", ceil(x));
17: printf("\nAbgerundet: %lf", floor(x));
18: if( x >= 0 )
19: printf("\nQuadratwurzel: %lf", sqrt(x) );
20: else
21: printf("\nNegative Zahl" );
22:
23: printf("\nKosinus: %lf\n", cos(x));
24: return(0);
25: }
Geben Sie eine Zahl ein: 100.95
Originalwert: 100.950000
Aufgerundet: 101.000000
Abgerundet: 100.000000
Quadratwurzel: 10.047388
Kosinus: 0.913482
Dieses Listing enthält nur einige der mathematischen Funktionen, die Ihnen in der C-
Standardbibliothek zur Verfügung stehen. Zeile 12 liest eine Zahl vom Anwender ein,
die in Zeile 14 unverändert ausgegeben wird. Als Nächstes wird der eingegebene Wert
vier mathematischen C-Funktionen übergeben - ceil()
, floor()
, sqrt()
und cos()
.
Beachten Sie, dass sqrt()
nur aufgerufen wird, wenn die Zahl nicht negativ ist, da
per definitionem negative Zahlen keine Quadratwurzeln haben können. Sie können
dieses Programm um beliebige andere mathematische Funktionen erweitern, um
deren Funktionalität zu prüfen.
Die C-Bibliothek enthält mehrere Funktionen, die es Ihnen erlauben, in Ihren
Programmen die Zeit abzufragen. In C bezieht sich der Begriff Zeit sowohl auf die
Uhrzeit als auch auf das Datum. Die Funktionsprototypen und die Definition der
Struktur tm
, die von vielen Zeitfunktionen verwendet werden, stehen in der Header-
Datei time.h
.
Die Zeitfunktionen von C stellen Zeitangaben auf zwei verschiedene Weisen dar. Die
erste Form der Darstellung besteht einfach darin, die Zeit als Anzahl der seit
Mitternacht des 1. Januar 1970 verstrichenen Sekunden anzugeben. Negative Werte
stellen die Zeit vor diesem Datum dar. Die Zeitwerte selbst werden als Integer-Werte
vom Typ long
gespeichert. In den Prototypen der Zeitfunktionen werden allerdings
statt long
die Typbezeichner time_t
und clock_t
verwendet, die in time.h
mittels
typedef
-Anweisungen als long
definiert sind.
Bei der zweiten Form der Darstellung werden die Zeitangaben in ihre Bestandteile
(Jahr, Monat, Tag, usw.) zerlegt. Für diese Art der Darstellung verwenden die
Zeitfunktionen die Struktur tm
, die in time.h
wie folgt definiert ist:
struct tm {
int tm_sec; /* Sekunden nach der vollen Minute - [0,59] */
int tm_min; /* Minuten nach der vollen Stunde - [0,59] */
int tm_hour; /* Stunden seit Mitternacht - [0,23] */
int tm_mday; /* Tag des Monats - [1,31] */
int tm_mon; /* Monate seit Januar - [0,11] */
int tm_year; /* Jahre seit 1900 */
int tm_wday; /* Tage seit Sonntag - [0,6] */
int tm_yday; /* Tage seit dem 1. Januar - [0,365] */
int tm_isdst; /* Flag für Sommerzeit */
};
Dieser Abschnitt beschreibt die verschiedenen Bibliotheksfunktionen in C, die mit der Zeit zu tun haben. Denken Sie jedoch immer daran, dass der Begriff Zeit sowohl das Datum als auch die Stunden, Minuten und Sekunden umfasst. Im Anschluss an die Beschreibungen folgt ein Beispielprogramm.
Um die aktuelle Zeit von der internen Uhr Ihres Systems abzufragen, verwenden Sie
die Funktion time()
, die wie folgt deklariert ist:
time_t time(time_t *ptr);
Denken Sie daran, dass time_t
in time.h
als Synonym für long
definiert ist. Die
Funktion time()
liefert die Anzahl der Sekunden zurück, die seit Mitternacht des 1.
Januar 1970 verstrichen sind. Wenn der Funktion ein Nicht-Null
-Zeiger übergeben
wird, speichert time()
diesen Wert in der Variablen vom Typ time_t
, auf die der
Zeiger ptr
zeigt. Um also die aktuelle Zeit in der time_t
-Variablen jetzt
zu speichern,
würden Sie folgenden Code aufsetzen:
time_t jetzt;
jetzt = time(0);
Oder Sie verwenden die Rückgabe über das Argument:
time_t jetzt;
time_t *zgr_jetzt = &jetzt;
time(zgr_jetzt);
Zu wissen, wie viele Sekunden seit dem 1. Januar 1970 verstrichen sind, ist meist
nicht sehr hilfreich. Aus diesem Grund stellt Ihnen C die Funktion localtime()
zur
Verfügung, mit der Sie einen time_t
-Wert in eine tm
-Struktur umwandeln können.
Eine tm
-Struktur enthält Tag, Monat, Jahr und andere Zeitinformationen in einem
Format, das sich besser zur Ausgabe und Anzeige eignet. Der Prototyp dieser
Funktion lautet:
struct tm *localtime(time_t *ptr);
Diese Funktion liefert einen Zeiger auf eine statische Strukturvariable vom Typ tm
zurück, so dass Sie keine Strukturvariable vom Typ tm
deklarieren müssen, sondern
nur einen Zeiger auf den Typ tm
. Die statische Strukturvariable wird bei jedem Aufruf
von localtime()
wieder verwendet und überschrieben. Wenn Sie den
zurückgegebenen Wert sichern wollen, muss Ihr Programm eine separate
Strukturvariable vom Typ tm
deklarieren und in diese die Werte der statischen
Strukturvariablen kopieren.
Die umgekehrte Konvertierung - von einer Strukturvariablen vom Typ tm
in einen
Wert vom Typ time_t
- erfolgt mit Hilfe der Funktion mktime()
. Der Prototyp lautet:
time_t mktime(struct tm *ntime);
Diese Funktion liefert die Anzahl der Sekunden, die zwischen Mitternacht des 1.
Januar 1970 und der Zeit verstrichen sind, die durch die Strukturvariable vom Typ tm
,
auf die ntime
zeigt, dargestellt wird.
Um Zeitangaben in formatierte Strings zu konvertieren, die ausgegeben werden
können, gibt es die Funktionen ctime()
und asctime()
. Beide Funktionen liefern die
Zeit als einen String in einem speziellen Format zurück. Der Unterschied zwischen
den beiden Funktionen liegt darin, dass ctime()
die Zeit als ein Wert vom Typ time_t
übergeben wird, während asctime()
die Zeit als eine Strukturvariable vom Typ tm
entgegennimmt. Die Prototypen dieser Funktionen lauten:
char *asctime(struct tm *ptr);
char *ctime(time_t *ptr);
Beide Funktionen liefern einen Zeiger auf einen statischen, nullterminierten 26- Zeichen-String zurück, der die Zeit des Funktionsarguments im folgenden 24-Stunden- Format angibt:
Thu Jun 13 10:22:23 1991
Beide Funktionen verwenden einen statischen String, der bei jedem Aufruf der Funktion überschrieben wird.
Wenn Sie das Format der Zeit ändern wollen, steht Ihnen dazu die Funktion
strftime()
zur Verfügung. Dieser Funktion wird die zu formatierende Zeitangabe als
Strukturvariable vom Typ tm
übergeben. Die Formatierung erfolgt anhand eines
Formatstrings. Der Prototyp der Funktion lautet:
size_t strftime(char *s, size_t max, char *fmt, struct tm *ptr);
Die Funktion nimmt die zu formatierende Zeitangabe über die tm
-Strukturvariable
entgegen, auf die der Zeiger ptr
weist, formatiert sie nach Vorgabe des Formatstrings
fmt
und schreibt das Ergebnis als nullterminierten String an die Speicherposition, auf
die s
zeigt. Das Argument max
sollte die Größe des Speicherbereichs angeben, der für
s
reserviert wurde. Wenn der resultierende String (einschließlich des abschließenden
Nullzeichens) mehr als max
Zeichen enthält, liefert die Funktion 0
zurück, und der
String s
ist ungültig. Im anderen Fall liefert die Funktion die Anzahl der geschriebenen
Zeichen zurück - strlen(s)
.
Der Formatstring besteht aus einem oder mehreren der Konvertierungsspezifizierer, die in Tabelle 17.1 aufgeführt sind.
Das inzwischen berüchtigte Jahr-2000-Problem wurde dadurch verursacht, dass Programmierer einen zweistelligen Spezifizierer für die Jahresangaben ausgewählt hatten, wo - wie man heute weiß - ein vierstelliger Spezifizierer besser gewesen wäre. Einige der folgenden Formatspezifizierer geben das Jahr nur zweistellig aus, das heißt, aus 1999 wird 99. Daten nach dem 31. Dezember 1999 können je nach Implementierung als 00 oder als 100 ausgegeben werden. Seien Sie immer vorsichtig, wenn Sie Datumsspezifizierer auswählen
Tabelle 17.1: Konvertierungsspezifizierer, die zusammen mit strftime() verwendet werden können.
Sie können den Zeitunterschied zwischen zwei Zeitangaben mit dem Makro
difftime()
in Sekunden berechnen. Dieses Makro subtrahiert zwei time_t
-Werte und
liefert die Differenz zurück. Der Prototyp lautet:
double difftime(time_t zeit1, time_t zeit0);
Diese Funktion subtrahiert zeit0
von zeit1
und liefert die Differenz, das heißt die
Anzahl der Sekunden zwischen den beiden Zeiten, zurück. Häufig wird difftime()
dazu verwendet, die verstrichene Zeit zu berechnen. Ein Beispiel dafür finden Sie in
Listing 17.2.
Mit der clock()
-Funktion können Sie eine andere Art von Dauer ermitteln. Die
Funktion clock()
gibt an, wie viel Millionstelsekunde seit Beginn der
Programmausführung verstrichen sind. Der Prototyp der Funktion lautet:
clock_t clock(void);
Um herauszufinden, wie viel Zeit für die Ausführung eines bestimmten
Programmabschnitts benötigt wird, müssen Sie clock()
zweimal aufrufen - vor und
nach dem betreffenden Codeblock - und dann die beiden Rückgabewerte voneinander
subtrahieren.
Listing 17.2 zeigt Ihnen, wie Sie die Zeitfunktionen der C-Bibliothek einsetzen können.
Listing 17.2: Die Zeitfunktionen der C-Bibliothek.
1: /* Beispiele für die Verwendung der Zeitfunktionen. */
2:
3: #include <stdio.h>
4: #include <time.h>
5:
6: int main(void)
7: {
8: time_t beginn, ende, jetzt;
9: struct tm *zgr;
10: char *c, puffer1[80];
11: double dauer;
12:
13: /* Die Zeit des Programmstarts festhalten. */
14:
15: beginn = time(0);
16:
17: /* Die aktuelle Zeit festhalten. Ruft time() auf dem */
18: /* zweiten Weg auf. */
19:
20: time(&jetzt);
21:
22: /* Konvertiert den time_t-Wert in eine Struktur vom Typ tm. */
23:
24: zgr = localtime(&jetzt);
25:
26: /* Erzeugt einen formatierten String mit der aktuellen */
27: /* Zeit und gibt ihn aus. */
28:
29: c = asctime(zgr);
30: puts(c);
31: getc(stdin);
32:
33: /* Verwendet die strftime()-Funktion, um verschiedene */
34: /* formatierte Versionen der Zeit zu erzeugen. */
35:
36: strftime(puffer1,80,"Dies ist %U. Woche des Jahres %Y",zgr);
37: puts(puffer1);
38: getc(stdin);
39:
40: strftime(puffer1, 80, "Heute ist %A, %m/%d/%Y", zgr);
41: puts(puffer1);
42: getc(stdin);
43:
44: strftime(puffer1, 80, "Es ist %M Minuten nach %I.", zgr);
45: puts(puffer1);
46: getc(stdin);
47:
48: /* Liest die aktuelle Zeit ein u. berechnet die Programmdauer. */
49:
50: ende = time(0);
51: dauer = difftime(ende, beginn);
52: printf("Ausführungszeit mit time() = %f Sekunden.\n", dauer);
53:
54: /* Gibt die Programmdauer mit clock() in Hundertstel- */
55: /* sekunden an. */
56:
57: printf("Ausführungszeit mit clock() = %ld Hundertstelsekunden.\n",
58: clock());
59: return(0);
60: }
Wed Sep 15 12:21:27 1999
Dies ist 37. Woche des Jahres 1999
Heute ist Mittwoch, 09/15/1999
Es ist 21 Minuten nach 12.
Ausführungszeit mit time() = 11.000000 Sekunden.
Ausführungszeit mit clock() = 10000 Hundertstelsekunden.
Dieses Programm enthält eine ganze Menge Kommentarzeilen, die Ihnen helfen
sollen, das Geschehen besser nachzuvollziehen. Da wir Zeitfunktionen verwenden
wollen, wird in Zeile 4 die Header-Datei time.h
eingebunden. Zeile 8 deklariert drei
Variablen vom Typ time_t
- beginn
, ende
und jetzt
. Diese Variablen können die Zeit
als Anzahl der seit dem 1. Januar 1970 verstrichenen Sekunden aufnehmen. Zeile 9
deklariert einen Zeiger auf eine tm
-Struktur. Die tm
-Struktur wurde bereits
beschrieben. Der restlichen Variablen müssten Ihnen von ihren Typen her bekannt
sein.
Das Programm zeichnet in Zeile 15 seine Startzeit auf, wozu es die Funktion time()
aufruft. In Zeile 20 wiederholt sich das Ganze noch einmal, allerdings verwendet das
Programm diesmal nicht den von der time(
)-Funktion zurückgegebenen Wert,
sondern übergibt time()
einen Zeiger auf die Variable jetzt
. Zeile 24 macht genau
das, was der Kommentar in Zeile 22 vorgibt: sie konvertiert den time_t
-Wert von
jetzt
in eine Strukturvariable vom Typ tm
. Die nachfolgenden Abschnitte des
Programms geben den Wert der aktuellen Zeit in unterschiedlichen Formaten auf dem
Bildschirm aus. Zeile 29 verwendet die Funktion asctime()
, um die Informationen
einem Zeichenzeiger c
zuzuweisen. Zeile 30 gibt die formatierten Informationen aus.
Das Programm wartet dann darauf, dass der Anwender die Eingabetaste drückt.
Die Zeilen 36 bis 46 verwenden die Funktion strftime()
, um das Datum in drei
Formaten auszugeben. Mit Hilfe der Tabelle 17.1 sollten Sie in der Lage sein,
herauszufinden, was mit diesen Zeilen ausgegeben wird. Die Warnungen, die für
einige der Formatspezifizierer ausgegeben werden, können Sie getrost ignorieren.
In Zeile 50 wird noch einmal die aktuelle Zeit ermittelt. Dies ist die Zeit, zu der das
Programm endet. Zeile 51 berechnet mit Hilfe der Funktion difftime()
aus den
Zeiten für Programmstart und -ende die Ausführungszeit des Programms. Dieser Wert
wird in Zeile 52 ausgegeben. Das Programm endet damit, dass es die Ausführungszeit
noch einmal mit Hilfe der clock()
-Funktion berechnet und ausgibt.
Die C-Standardbibliothek enthält eine Reihe von Funktionen und Makros, die bei der Behandlung von Programmfehlern dienlich sind.
Das Makro assert()
kann Programmfehler feststellen. Es ist in assert.h
definiert und
sein Prototyp lautet:
void assert(int Ausdruck);
Bei dem Argument Ausdruck
kann es sich um alles handeln, was Sie testen wollen -
eine einzelne Variable oder einen beliebigen C-Ausdruck. Wenn Ausdruck
als wahr
ausgewertet wird, macht assert()
nichts. Wenn Ausdruck
hingegen als falsch
ausgewertet wird, gibt assert()
eine Fehlermeldung an stderr
aus und bricht die
Programmausführung ab.
Wie wird assert()
verwendet? Meistens wird das Makro dazu benutzt, um
Programmfehler festzustellen (die sich von den Kompilierfehlern unterscheiden). Ein
Programmfehler hat keinen Einfluss auf die Kompilierung des Programms, führt aber
zu falschen Ergebnissen oder unerwünschtem Verhalten des Programms (Absturz
etc.). Angenommen ein Finanzanalyse-Programm, das Sie geschrieben haben, gibt
gelegentlich falsche Antworten und Sie vermuten, dass das Problem darin besteht,
dass die Variable zins_rate
negative Werte annimmt - was eigentlich nicht passieren
dürfte. Um dies zu überprüfen, platzieren Sie die Anweisung
assert(zins_rate >= 0);
an Positionen im Programm, an denen zins_rate
verwendet wird. Wenn die Variable
irgendwann einmal negativ wird, macht das assert()
-Makro Sie darauf aufmerksam.
Sie können dann den betreffenden Code genauer untersuchen und dem Problem auf
den Grund gehen.
Um zu sehen, wie assert()
arbeitet, führen Sie Listing 17.3 aus. Wenn Sie einen
Wert größer Null eingeben, zeigt das Programm den Wert an und endet normal.
Wenn Sie einen Wert eingeben, der kleiner oder gleich Null ist, erzwingt das assert()
-
Makro einen Programmabbruch. Welche Fehlermeldung Sie genau erhalten, hängt
von Ihrem Compiler ab, aber sehen Sie im Folgenden ein typisches Beispiel:
Assertion failed: x, file list1703.c, line 13
Beachten Sie, dass Ihr Programm im Debug-Modus kompiliert worden sein muss,
damit assert()
ordnungsgemäß ausgeführt werden kann. Schauen Sie in Ihrem
Compiler-Handbuch nach, wenn Sie nicht wissen, wie Sie den Debug-Modus
aktivieren (wird in Kürze erklärt). Wenn Sie später die letzte Version im Release-
Modus kompilieren, werden die assert()
-Makros deaktiviert.
Listing 17.3: Das Makro assert().
1: /* Das Makro assert(). */
2:
3: #include <stdio.h>
4: #include <assert.h>
5:
6: int main(void)
7: {
8: int x;
9:
10: printf("\nGeben Sie einen Integer-Wert ein: ");
11: scanf("%d", &x);
12:
13: assert(x >= 0);
14:
15: printf("Ihre Eingabe lautete %d.\n\n", x);
16: return(0);
17: }
Geben Sie einen Integer-Wert ein: 10
Ihre Eingabe lautete 10.
Geben Sie einen Integer-Wert ein: -1
list1703: list1703.c:13: main: Zusicherung »x >= 0« nicht erfüllt.
Aborted (core dumped)
Die Fehlermeldung kann bei Ihnen je nach System und Compiler etwas anders lauten,
aber im Allgemeinen sind sie sich alle gleich. In diesem Beispiel werden Ihnen der
Dateiname der C-Quelltextdatei, list1703.c
, und die Zeilennummer angezeigt.
Beachten Sie in der Fehlermeldung den Begriff »core dumped«. Das bedeutet, dass der
von Ihrem Programm belegte Speicher komplett in eine Datei namens core
geschrieben wurde, die dann zur genaueren Untersuchung in einen Debugger geladen
werden kann.
Führen Sie dieses Programm aus und vergewissern Sie sich selbst, dass die von
assert()
in Zeile 13 ausgegebene Fehlermeldung aus dem fehlgeschlagenen
Ausdruck, dem Namen der Datei und der Zeilennummer des assert()
-Aufrufs
besteht.
Die Arbeit von assert()
hängt von einem anderen Makro namens NDEBUG
(steht für
»nicht debuggen«) ab. Wenn das Makro NDEBUG
nicht definiert ist (der Standard), ist
assert()
aktiv. Ist NDEBUG
definiert, wird asssert()
deaktiviert. Dies ist viel einfacher,
als das ganze Programm nach den assert()
-Anweisungen durchzugehen und diese zu
löschen (nur um später festzustellen, dass Sie sie wieder benötigen). Um das Makro
NDEBUG
zu definieren, verwenden Sie die #define
-Direktive. Wenn Sie sich selbst von
der Wirkung von NDEBUG
überzeugen wollen, fügen Sie die Anweisung
#define NDEBUG
in die zweite Zeile von Listing 17.3 ein. Jetzt gibt das Programm die eingegebenen
Werte aus und endet normal, auch wenn Sie -1
eingeben.
Beachten Sie, dass NDEBUG
als nichts Besonderes definiert werden muss, solange es in
einer #define
-Direktive steht. Mehr zu der #define
-Direktive erfahren Sie am Tag 20.
Die Header-Datei errno.h
definiert mehrere Makros, die dazu dienen, Laufzeitfehler
zu definieren und zu dokumentieren. Diese Makros werden zusammen mit der
Funktion perror()
verwendet, die das erste Mal am Tag 15, »Mit Dateien arbeiten«,
zum Einsatz kam. An dieser Stelle wollen wir uns näher damit beschäftigen.
Die Definitionen in errno.h
beinhalten eine globale Integer-Variable namens errno
.
Viele Bibliotheksfunktion in C weisen dieser Variablen einen Wert zu, wenn während
der Funktionsausführung ein Fehler auftritt. Die Datei errno.h
definiert darüber hinaus
eine Gruppe von symbolischen Konstanten für diese Fehler, die in Tabelle 17.2
aufgelistet sind.
Tabelle 17.2: Die in errno.h definierten symbolischen Fehlerkonstanten.
Sie können errno
auf zweifache Art verwenden. Einige Funktionen signalisieren
anhand ihres Rückgabewertes, dass ein Fehler aufgetreten ist. In einem solchen Fall
können Sie den Wert von errno
prüfen, um die Art des Fehlers festzustellen und
entsprechend darauf zu reagieren. Andernfalls, das heißt, wenn Sie nicht direkt
mitgeteilt bekommen, dass ein Fehler aufgetreten ist, können Sie errno
prüfen. Wenn
der Wert ungleich Null ist, liegt irgendwo ein Fehler vor, und der spezielle Wert von
errno
spiegelt die Art des Fehlers wider. Sie müssen daran denken, errno
wieder auf
Null zurückzusetzen, nachdem Sie den Fehler behoben haben. Der nächste Abschnitt
ist perror()
gewidmet. Listing 17.4 veranschaulicht den Einsatz von errno
und
perror()
.
Die Funktion perror()
ist ein weiteres C-Tool zur Fehlerbehandlung. Wenn perror()
aufgerufen wird, wird in stderr
eine Meldung ausgegeben, die den letzten Fehler
beschreibt, der während eines Bibliotheksfunktions- oder eines Systemaufrufs
aufgetreten ist. Der Prototyp, der in stdio.h
zu finden ist, lautet:
void perror(char *msg);
Das Argument msg
zeigt auf eine optionale, benutzerdefinierte Meldung. Diese
Meldung wird zuerst ausgegeben, gefolgt von einem Doppelpunkt und der
implementierungsbedingten Meldung, die den letzten Fehler beschreibt. Wenn Sie
perror()
aufrufen und kein Fehler aufgetreten ist, lautet die Meldung »Erfolg2.«
Ein Aufruf von perror()
ist noch keine Maßnahme zur Fehlerbehandlung. Es bleibt
dem Programm überlassen, in Aktion zu treten - beispielsweise den Anwender
aufzufordern, das Programm zu beenden oder den Wert von errno
abzufragen und je
nach Art des Fehlers gezielt zu reagieren. Beachten Sie, dass ein Programm die
Header-Datei errno.h
nicht einbinden muss, um die globale Variable errno
zu
verwenden. Die Header-Datei wird nur erforderlich, wenn Ihr Programm die
symbolischen Fehlerkonstanten aus Tabelle 17.2 verwendet. Listing 17.4
veranschaulicht die Verwendung von perror()
und errno
zur Behandlung von
Laufzeitfehlern.
Listing 17.4: Mit perror() und errno Laufzeitfehler behandeln.
1: /* Beispiel für Fehlerbehandlung mit perror() und errno. */
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5: #include <errno.h>
6:
7: int main(void)
8: {
9: FILE *fp;
10: char dateiname[80];
11:
12: printf("Geben Sie einen Dateinamen ein: ");
13: fgets(dateiname,80,stdin);
14: dateiname[strlen(dateiname)-1] = 0;
15:
16: if (( fp = fopen(dateiname, "r")) == NULL)
17: {
18: perror("Sie haben gepatzt");
19: printf("errno = %d.\n", errno);
20: exit(1);
21: }
22: else
23: {
24: puts("Datei zum Lesen geöffnet.");
25: fclose(fp);
26: }
27: return(0);
28: }
Geben Sie einen Dateinamen ein: list1704.c
Datei zum Lesen geöffnet.
Geben Sie einen Dateinamen ein: keinedatei.xxx
Sie haben gepatzt!: Datei oder Verzeichnis nicht gefunden
errno = 2.
Dieses Programm gibt eine von zwei Meldungen aus, je nachdem ob eine Datei zum
Lesen geöffnet werden konnte oder nicht. Zeile 16 versucht, eine Datei zu öffnen.
Wenn die Datei ohne Probleme geöffnet werden kann, wird der else
-Teil der if
-
Schleife ausgeführt und die folgende Meldung ausgegeben:
Datei zum Lesen geöffnet.
Tritt beim Öffnen der Datei ein Fehler auf, etwa weil die Datei nicht existiert, werden
die Zeilen 18 bis 20 der if
-Schleife ausgeführt. Zeile 18 ruft die perror()
-Funktion
mit dem String »Sie haben gepatzt!
« auf. Danach wir die Fehlernummer ausgegeben.
Das Ergebnis sieht wie folgt aus:
Sie haben gepatzt!: Datei oder Verzeichnis nicht gefunden
errno = 2
Sie haben bereits mehrere Bibliotheksfunktionen, wie zum Beispiel printf()
und
scanf()
, kennen gelernt, die eine beliebige Anzahl an Argumenten übernehmen. Sie
können auch eigene Funktionen schreiben, die eine beliebig lange Argumentenliste
übernehmen. Programme, die Funktionen mit beliebig langen Argumentenlisten
enthalten, müssen die Header-Datei stdarg.h
einbinden.
Wenn Sie eine Funktion deklarieren, die eine beliebig lange Argumentenliste
übernimmt, geben Sie zuerst die festen Parameter an - das sind die, die immer
vorhanden sein müssen. Es muss mindestens ein fester Parameter vorhanden sein.
Anschließend setzen Sie an das Ende der Parameterliste eine Ellipse (...)
, um
anzuzeigen, dass der Funktion null oder mehr Argumente übergeben werden. Denken
Sie in diesem Kontext bitte daran, dass es, wie bereits am Tag 4, »Funktionen«,
erwähnt, einen Unterschied zwischen einem Parameter und einem Argument gibt.
Woher weiß eine Funktion, wie viele Argumente ihr bei einem bestimmten Aufruf
übergeben wurden? Sie teilen es ihr mit. Einer der festen Parameter informiert die
Funktion über die Gesamtzahl der Argumente. Wenn Sie zum Beispiel die printf()
-
Funktion verwenden, kann die Funktion an der Zahl der Konvertierungsspezifizierer im
Formatstring ablesen, wie viele weitere Argumente zu erwarten sind. Noch direkter
geht es, wenn eines der festen Funktionsargumente die Anzahl der weiteren
Argumente angibt. Das Beispiel, das ich Ihnen gleich präsentieren werde, verfolgt
diesen Ansatz, aber vorher sollten Sie einige der Hilfsmittel kennen lernen, die C für
die Implementierung von Funktionen mit beliebig langen Argumentenlisten zur
Verfügung stellt.
Die Funktion muss von jedem Argument in der Liste den Typ kennen. Im Falle von
printf()
geben die Konvertierungsspezifizierer den jeweiligen Typ des Arguments an.
In anderen Fällen, wie im folgenden Beispiel, sind alle Argumente der beliebig langen
Liste vom gleichen Typ, so dass es keine Probleme gibt. Um eine Funktion zu
erzeugen, die verschiedene Typen in der Argumentenliste akzeptiert, müssen Sie eine
Methode finden, die Informationen über die Argumenttypen weiterzureichen. Eine
Möglichkeit wäre, einen Zeichencode festzulegen, wie wir dies bei der Funktion
haelfte()
aus Listing 8.7 getan haben.
Die Hilfsmittel zur Implementierung beliebig langer Argumentenlisten sind in stdarg.h
definiert. Diese Hilfsmittel werden innerhalb der Funktion eingesetzt, um auf die
Argumente aus der Argumentenliste zuzugreifen. Sie lauten:
Im Folgenden werde ich kurz beschreiben, wie diese Makros in einer Funktion eingesetzt werden, und Ihnen anschließend ein Beispiel zeigen. Innerhalb der Funktion müssen Sie die folgenden Schritte ausführen, um auf die Argumente zuzugreifen:
va_list
. Dieser Zeiger wird benötigt,
um auf die einzelnen Argumente zuzugreifen. Es ist allgemein üblich, wenn auch
nicht unbedingt erforderlich, diese Variable arg_ptr
zu nennen.
va_start()
auf und übergeben Sie ihm dabei den Zeiger
arg_ptr
und den Namen des letzten festen Arguments. Das Makro va_start()
hat
keinen Rückgabewert. Es initialisiert den Zeiger arg_ptr
so, dass er auf das erste
Argument in der Argumentenliste zeigt.
va_arg()
auf
und übergeben ihm den Zeiger arg_ptr
und den Datentyp des nächsten
Arguments. Wenn die Funktion n
Argumente entgegengenommen hat, rufen Sie
va_arg()
n
-mal auf, um in der Reihenfolge auf die Argumente zuzugreifen, in der
sie im Funktionsaufruf aufgelistet sind.
va_end()
auf und übergeben ihm den Zeiger arg_ptr
. In einigen
Implementierungen wird dieses Makro nicht benötigt, in anderen hingegen führt
es alle notwendigen Aufräumarbeiten durch. Sie sollten es sich zur Regel machen,
va_end()
aufzurufen, für den Fall, dass Ihre C-Implementierung es erfordert.
Kommen wir jetzt zu dem Beispiel: Die Funktion durchschnitt()
aus Listing 17.5
berechnet das arithmetische Mittel für eine Reihe von Integer-Werten. Das Programm
übergibt der Funktion ein einziges festes Argument, das die Zahl der weiteren
Argumente angibt, gefolgt von der Liste der Zahlen.
Listing 17.5: Eine Funktion mit einer beliebig langen Argumentenliste.
1: /* Funktionen mit einer beliebigen Zahl an Argumenten. */
2:
3: #include <stdio.h>
4: #include <stdarg.h>
5:
6: float durchschnitt(int num, ...);
7:
8: int main(void)
9: {
10: float x;
11:
12: x = durchschnitt(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
13: printf("Der erste Durchschnittswert beträgt %f.\n", x);
14: x = durchschnitt(5, 121, 206, 76, 31, 5);
15: printf("Der zweite Durchschnittswert beträgt %f.\n", x);
16: return(0);
17: }
18:
19: float durchschnitt(int anz, ...)
20: {
21: /* Deklariert eine Variable vom Typ va_list. */
22:
23: va_list arg_ptr;
24: int count, gesamt = 0;
25:
26: /* Initialisiert den Argumentenzeiger. */
27:
28: va_start(arg_ptr, anz);
29:
30: /* Spricht jedes Argument in der Variablenliste an. */
31:
32: for (count = 0; count < anz; count++)
33: gesamt += va_arg( arg_ptr, int );
34:
35: /* Aufräumarbeiten. */
36:
37: va_end(arg_ptr);
38:
39: /* Teilt die Gesamtsumme durch die Anzahl der Werte, um den */
40: /* Durchschnitt zu erhalten. Wandelt gesamt in einen float-Typ */
41: /* um, so dass der Rückgabewert vom Typ float ist. */
42:
43: return ((float)gesamt/anz);
44: }
Der erste Durchschnittswert beträgt 5.500000.
Der zweite Durchschnittswert beträgt 87.800003.
Die Funktion durchschnitt()
wird zum ersten Mal in Zeile 12 aufgerufen. Das erste
ihr übergebene Argument - das einzig feste Argument - gibt die Zahl der Werte in der
beliebig langen Argumentenliste an. In der Funktion werden in den Zeilen 32 und 33
alle Argumente aus der Argumentenliste eingelesen und in der Variablen gesamt
aufaddiert. Nachdem alle Argumente aus der Liste aufaddiert worden sind, wandelt
Zeile 43 die Variable gesamt
in den Typ float
und teilt dann gesamt
durch anz
, um
den Durchschnitt zu erhalten.
Auf zwei Punkte in diesem Listing möchte ich Sie noch hinweisen. Zeile 28 ruft
va_start()
auf, um die Argumentenliste zu initialisieren. Dieser Aufruf muss erfolgen,
bevor die Werte aus der Liste gelesen werden. Und Zeile 37 ruft va_end()
für
Aufräumarbeiten auf, nachdem die Funktion die Werte nicht mehr benötigt. Sie
sollten diese beiden Funktionen immer in Ihre Programme aufnehmen, wenn Sie eine
Funktion mit einer beliebigen Anzahl an Argumenten implementieren.
Genau genommen muss eine Funktion, die eine beliebige Anzahl an Argumenten übernimmt, keinen festen Parameter haben, der die Zahl der übergebenen Argumente angibt. Sie könnten zum Beispiel das Ende der Argumentenliste mit einem besonderen Wert markieren, der nirgendwo sonst verwendet wird. Diese Methode schränkt jedoch die Argumente, die übergeben werden können, ein, so dass man sie am besten vermeidet.
Zu den häufigsten Aufgaben, die Programme zu bewältigen haben, gehört das Suchen und Sortieren von Daten. In der C-Standardbibliothek finden Sie allgemeine Funktionen, mit denen sich diese Aufgaben lösen lassen.
Die Bibliotheksfunktion bsearch()
führt eine binäre Suche in einem Datenarray durch,
wobei sie nach einem Array-Element Ausschau hält, das mit einem gesuchten Wert
(dem Schlüssel oder »key«) übereinstimmt. Um bsearch()
anwenden zu können, muss
das Array in aufsteigender Reihenfolge sortiert sein. Außerdem muss das Programm
die Vergleichsfunktion bereitstellen, die bsearch()
benötigt, um festzustellen zu
können, ob ein Datenelement größer als, kleiner als oder gleich einem anderen
Element ist. Der Prototyp von bsearch()
steht in stdlib.h
und lautet:
void *bsearch(const void *key, const void *base, size_t num, size_t size,
int (*cmp)(void *elem1, void *elem2));
Dieser Prototyp ist ziemlich komplex. Deshalb sollten Sie ihn sorgfältig studieren. Das
Argument key
ist ein Zeiger auf das gesuchte Datenelement und base
ein Zeiger auf
das erste Element in dem zu durchsuchenden Array. Beide werden mit dem Typ void
deklariert, so dass sie auf irgendein beliebiges C-Datenobjekt zeigen können.
Das Argument num
ist die Anzahl der Elemente in dem Array und size
die Größe der
Elemente in Byte. Der Typspezifizierer size_t
bezieht sich auf den Datentyp, der vom
sizeof()
-Operator zurückgeliefert wird und vorzeichenlos ist. Üblicherweise wird der
sizeof()
-Operator verwendet, um die Werte für num
und size
anzugeben.
Das letzte Argument, cmp
, ist ein Zeiger auf die Vergleichsfunktion. Dabei kann es sich
um eine vom Programmierer aufgesetzte Funktion handeln, oder - wenn Stringdaten
durchsucht werden - um die Bibliotheksfunktion strcmp()
. Die Vergleichsfunktion
muss die folgenden zwei Kriterien erfüllen:
int
-Werte zurückgeben:
Der Rückgabewert von bsearch()
ist ein Zeiger vom Typ void
. Die Funktion gibt
einen Zeiger auf das erste Array-Element zurück, das dem Schlüssel entspricht, oder
NULL
, wenn keine Übereinstimmung gefunden wird. Der Typ des zurückgegebenen
Zeigers muss entsprechend umgewandelt werden, bevor der Zeiger verwendet werden
kann.
Der sizeof()
-Operator kann die num
- und size
-Argumente wie folgt bereitstellen.
Wenn array[]
das zu durchsuchende Array ist, dann liefert die Anweisung
sizeof(array[0]);
den Wert für size
, das heißt die Größe eines Array-Elements in Byte, zurück. Da der
Ausdruck sizeof(array)
die Größe eines ganzen Arrays in Byte zurückliefert, können
Sie mit der folgenden Anweisung den Wert von num
, der Anzahl der Elemente im
Array, ermitteln:
sizeof(array)/sizeof(array[0])
Der binäre Suchalgorithmus ist sehr effizient. Man kann damit ein großes Array sehr schnell durchsuchen. Er setzt allerdings voraus, dass die Array-Elemente in aufsteigender Reihenfolge sortiert sind. Und so funktioniert der Algorithmus:
Wie Sie sehen, halbiert jeder Vergleich einer binären Suche das durchsuchte Array um
die Hälfte. So kann zum Beispiel ein Array mit 1000 Elementen mit nur 10
Suchläufen durchsucht werden, und ein Array mit 16 000 Elementen mit nur 14
Suchläufen. Im Allgemeinen lässt sich sagen, dass eine binäre Suche n
Suchläufe
benötigt, um ein Array von 2n Elementen zu durchsuchen.
Die Bibliotheksfunktion qsort()
ist eine Implementierung des Quicksort-Algorithmus,
der von C.A.R. Hoare erfunden wurde. Diese Funktion sortiert ein Array in eine
vorgegebene Reihenfolge. Normalerweise wird aufsteigend sortiert, aber qsort()
kann auch absteigend sortieren. Der Prototyp dieser Funktion ist in stdlib.h
definiert
und lautet:
void qsort(void *base, size_t num, size_t size,
int (*cmp)(void *elem1, void *elem2));
Das Argument base
zeigt auf das erste Element in dem Array, num
ist die Anzahl der
Elemente im Array und size
die Größe eines Array-Elements in Byte. Das Argument
cmp
ist eine Zeiger auf eine Vergleichsfunktion. Für diese Vergleichsfunktion gelten die
gleichen Regeln wie für die Vergleichsfunktion, die von bsearch()
verwendet wird und
die im vorigen Abschnitt beschrieben wurde. Oft verwendet man für bsearch()
und
qsort()
die gleiche Vergleichsfunktion. Die Funktion qsort()
hat keinen
Rückgabewert.
Listing 17.6 veranschaulicht den Einsatz von qsort()
und bsearch()
. Das Programm
sortiert und durchsucht ein Array von Werten.
Listing 17.6: Mit den Funktionen qsort() und bsearch() Werte suchen und sortieren.
1: /* qsort() und bsearch() mit Werten verwenden.*/
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5:
6: #define MAX 20
7:
8: int intvgl(const void *v1, const void *v2);
9:
10: int main(void)
11: {
12: int arr[MAX], count, suche, *zgr;
13:
14: /* Werte einlesen. */
15:
16: printf("Geben Sie %d Integer-Werte ein.\n", MAX);
17:
18: for (count = 0; count < MAX; count++)
19: scanf("%d", &arr[count]);
20:
21: puts("Betätigen Sie die Eingabetaste, um die Werte zu sortieren.");
22: getc(stdin);
23:
24: /* Sortiert das Array in aufsteigender Reihenfolge. */
25:
26: qsort(arr, MAX, sizeof(arr[0]), intvgl);
27:
28: /* Gibt das sortierte Array aus. */
29:
30: for (count = 0; count < MAX; count++)
31: printf("\narr[%d] = %d.", count, arr[count]);
32:
33: puts("\nWeiter mit Eingabetaste.");
34: getc(stdin);
35:
36: /* Suchwert eingeben. */
37:
38: printf("Geben Sie einen Wert für die Suche ein: ");
39: scanf("%d", &suche);
40:
41: /* Suche durchführen. */
42:
43: zgr = (int *)bsearch(&suche, arr, MAX, sizeof(arr[0]),intvgl);
44:
45: if ( zgr != NULL )
46: printf("%d bei arr[%d] gefunden.\n", suche, (zgr - arr));
47: else
48: printf("%d nicht gefunden.\n", suche);
49: return(0);
50: }
51:
52: int intvgl(const void *v1, const void *v2)
53: {
54: return (*(int *)v1 - *(int *)v2);
55: }
Geben Sie 20 Integer-Werte ein.
45
12
999
1000
321
123
2300
954
1968
12
2
1999
1776
1812
1456
1
9999
3
76
200
Betätigen Sie die Eingabetaste, um die Werte zu sortieren.
arr[0] = 1.
arr[1] = 2.
arr[2] = 3.
arr[3] = 12.
arr[4] = 12.
arr[5] = 45.
arr[6] = 76.
arr[7] = 123.
arr[8] = 200.
arr[9] = 321.
arr[10] = 954.
arr[11] = 999.
arr[12] = 1000.
arr[13] = 1456.
arr[14] = 1776.
arr[15] = 1812.
arr[16] = 1968.
arr[17] = 1999.
arr[18] = 2300.
arr[19] = 9999.
Weiter mit Eingabetaste.
Geben Sie einen Wert für die Suche ein:
1776
1776 bei arr[14] gefunden
Listing 17.6 vereint in sich alles, was bisher zum Sortieren und Suchen gesagt wurde.
Zu Beginn des Programms können Sie bis zu MAX
Werte eingeben (in diesem Falle 20).
Die Werte werden sortiert und dann in der neuen Reihenfolge ausgegeben. Danach
können Sie einen Wert eingeben, nach dem im Array gesucht werden soll. Eine
abschließende Meldung informiert Sie darüber, ob der Wert im Array gefunden wurde
oder nicht.
Der Code in den Zeilen 18 und 19, mit dem die Werte für das Array eingelesen
werden, sollte Ihnen bereits vertraut sein. In Zeile 26 wird qsort()
aufgerufen, um das
Array zu sortieren. Das erste Argument ist ein Zeiger auf das erste Element im Array.
Darauf folgt das Argument MAX
, die Anzahl der Elemente im Array. Anschließend wird
die Größe des ersten Elements angegeben, so dass qsort()
die Größe der Elemente
kennt. Der Aufruf endet mit dem Argument für die Sortierfunktion intvgl
.
Die Funktion intvgl()
wird in den Zeilen 52 bis 55 definiert. Sie liefert den
Unterschied zwischen den zwei Werten zurück, die ihr übergeben werden. Dies
scheint auf den ersten Blick fast zu einfach, aber denken Sie daran, welche Werte die
Vergleichsfunktion zurückliefern soll. Wenn die Elemente gleich sind, sollte 0
zurückgeliefert werden. Wenn Element eins größer als Element zwei ist, sollte eine
positive Zahl zurückgeliefert werden, und wenn das erste Element kleiner ist als das
zweite Element, sollte eine negative Zahl zurückgeliefert werden. Und das ist genau
das, was intvgl()
macht.
Die Suche wird mit bsearch()
durchgeführt. Beachten Sie, dass die Argumente für
bsearch()
praktisch die gleichen sind wie für qsort()
. Der einzige Unterschied liegt
darin, dass das erste Argument von bsearch()
der Schlüssel ist, nach dem gesucht
wird. bsearch()
liefert einen Zeiger auf die Stelle zurück, an der der Schlüssel
gefunden wurde, oder NULL
, wenn der Schlüssel nicht gefunden wurde. In Zeile 43
wird zgr
der Rückgabewert von bsearch()
zugewiesen. zgr
wird dann in der if
-
Anweisung der Zeilen 45 bis 48 verwendet, um den Anwender über den Erfolg der
Suche zu informieren.
Listing 17.7 macht im Prinzip das Gleiche wie Listing 17.6, nur dass diesmal Strings sortiert und gesucht werden.
Listing 17.7: qsort() und bsearch() für Strings verwenden.
1: /* qsort() und bsearch() für Strings verwenden. */
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5: #include <string.h>
6:
7: #define MAX 20
8:
9: int vergl(const void *s1, const void *s2);
10:
11: int main(void)
12: {
13: char *daten[MAX], puffer[80], *zgr, *suche, **suche1;
14: int count;
15:
16: /* Eine Liste von Wörtern einlesen. */
17:
18: printf("Geben Sie %d Wörter ein.\n",MAX);
19:
20: for (count = 0; count < MAX; count++)
21: {
22: printf("Wort %d: ", count+1);
23: fgets(puffer,80,stdin);
24: puffer[strlen(puffer)-1] = 0;
25: daten[count] = malloc(strlen(puffer)+1);
26: strcpy(daten[count], puffer);
27: }
28:
29: /* Sortiert die Wörter (oder besser die Zeiger). */
30:
31: qsort(daten, MAX, sizeof(daten[0]), vergl);
32:
33: /* Die sortierten Wörter ausgeben. */
34:
35: for (count = 0; count < MAX; count++)
36: printf("\n%d: %s", count+1, daten[count]);
37:
38: /* Einen Suchbegriff einlesen. */
39:
40: printf("\n\nGeben Sie einen Suchbegriff ein: ");
41: fgets(puffer,80,stdin);
42: puffer[strlen(puffer)-1] = 0;
43:
44: /* Führt die Suche durch. suche1 wird zum Zeiger */
45: /* auf den Zeiger auf den Suchbegriff.*/
46:
47: suche = puffer;
48: suche1 = &suche;
49: zgr = bsearch(suche1, daten, MAX, sizeof(daten[0]), vergl);
50:
51: if (zgr != NULL)
52: printf("%s gefunden.\n", puffer);
53: else
54: printf("%s nicht gefunden.\n", puffer);
55: return(0);
56: }
57:
58: int vergl(const void *s1, const void *s2)
59: {
60: return (strcmp(*(char **)s1, *(char **)s2));
61: }
Geben Sie 20 Wörter ein.
Wort 1: Apfel
Wort 2: Orange
Wort 3: Grapefruit
Wort 4: Pfirsich
Wort 5: Pflaume
Wort 6: Birne
Wort 7: Kirsche
Wort 8: Banane
Wort 9: Himbeere
Wort 10: Limone
Wort 11: Tangerine
Wort 12: Sternfrucht
Wort 13: Wassermelone
Wort 14: Stachelbeere
Wort 15: Zwetschge
Wort 16: Erdbeere
Wort 17: Johannisbeere
Wort 18: Blaubeere
Wort 19: Traube
Wort 20: Preiselbeere
1: Apfel
2: Banane
3: Birne
4: Blaubeere
5: Erdbeere
6: Grapefruit
7: Himbeere
8: Johannisbeere
9: Kirsche
10: Limone
11: Orange
12: Pfirsich
13: Pflaume
14: Preiselbeere
15: Stachelbeere
16: Sternfrucht
17: Tangerine
18: Traube
19: Wassermelone
20: Zwetschge
Geben Sie einen Suchbegriff ein: Orange
Orange gefunden.
In Listing 17.7 gibt es ein paar Punkte, auf die wir kurz eingehen sollten. Das Programm verwendet ein Array von Zeigern auf Strings - eine Technik, die am Tag 14, »Zeiger für Fortgeschrittene«, vorgestellt wurde. Wie Sie damals gelernt haben, können Sie die Strings sortieren, indem Sie das Array der Zeiger sortieren. Dazu muss allerdings die Vergleichsfunktion angepasst werden. Der Vergleichsfunktion werden Zeiger auf die zwei Elemente in dem Array übergeben, die verglichen werden. Sie wollen jedoch das Array von Zeigern nicht nach den Werten der Zeiger selbst, sondern nach den Werten der Strings, auf die die Zeiger verweisen, sortieren.
Deshalb müssen Sie eine Vergleichsfunktion verwenden, der Zeiger auf Zeiger
übergeben werden. Jedes Argument an vergl()
ist ein Zeiger auf ein Array-Element
und da jedes Element selbst ein Zeiger (auf einen String) ist, ist das Argument ein
Zeiger auf einen Zeiger. Innerhalb der Funktion selbst dereferenzieren Sie die Zeiger,
so dass der Rückgabewert von vergl()
von den Werten der Strings abhängt, auf die
verwiesen wird.
Die Tatsache, dass die Argumente, die vergl()
übergeben werden, Zeiger auf Zeiger
sind, schafft auch noch in anderer Hinsicht Probleme. Sie speichern den Suchbegriff
in puffer[]
und Sie wissen auch, dass der Name eines Arrays (in diesem Fall puffer
)
ein Zeiger auf das Array ist. Sie müssen jedoch nicht puffer
selbst übergeben, sondern
einen Zeiger auf puffer
. Das Problem dabei ist, dass puffer
eine Zeigerkonstante ist
und keine Zeigervariable. puffer
selbst hat keine Adresse im Speicher; es ist ein
Symbol, das zur Adresse des Arrays auswertet. Deshalb können Sie keinen Zeiger
erzeugen, der auf puffer
zeigt, indem Sie den Adressoperator vor puffer
(wie in
&puffer
) verwenden.
Wie verhält man sich in einem solchen Fall? Zuerst erzeugen Sie eine Zeigervariable
und weisen ihr den Wert von puffer
zu. In unserem Programm trägt diese
Zeigervariable den Namen suche
. Da suche
eine Zeigervariable ist, hat sie eine
Adresse, und Sie können einen Zeiger erzeugen, der diese Adresse als Wert aufnimmt
- hier suche1
. Wenn Sie schließlich bsearch()
aufrufen, übergeben Sie als erstes
Argument suche1
- einen Zeiger auf einen Zeiger auf den Suchstring. Die Funktion
bsearch()
übergibt das Argument an vergl()
, und alles läuft wie geschmiert.
Vergessen Sie nicht, Ihr Such-Array in aufsteigender Reihenfolge zu sortieren, bevor Sie
|
Heute haben wir einige nützliche Funktionen der C-Funktionsbibliothek kennen gelernt: Funktionen zur Durchführung mathematischer Berechnungen, zur Verarbeitung von Zeitangaben und zur Fehlerbehandlung. Besonders die Funktionen zum Suchen und Sortieren von Daten werden Sie gebrauchen können. Sie können Ihnen viel Zeit sparen, wenn Sie Ihre eigenen Programme schreiben.
Frage:
Warum sind die Rückgabewerte fast aller mathematischen Funktionen vom Typ
double
?
Antwort:
Die Antwort zu dieser Frage liegt in dem Streben nach Genauigkeit (statt nach
Konsistenz). Der Typ double
ist genauer als die anderen Datentypen. Durch
die Verwendung von double
-Rückgabewerten erreicht man, dass die
Rückgabewerte so genau wie möglich sind. Lesen Sie hierzu auch die
Ausführungen über Typumwandlung und automatische Konvertierungen von
Tag 18, »Vom Umgang mit dem Speicher«.
Frage:
Sind bsearch()
und qsort()
die einzigen Möglichkeiten, wie man Daten in C
sortieren und suchen kann?
Antwort:
Die beiden Funktionen bsearch()
und qsort()
sind Teil der
Standardbibliothek. Sie müssen sie jedoch nicht verwenden. In vielen
Computer-Lehrbüchern finden Sie Anleitungen, wie Sie Ihre eigenen Such-
und Sortierprogramme schreiben. C enthält alle Befehle, die Sie benötigen,
um eigene Funktionen zu schreiben. Sie können auch spezielle Such- und
Sortierroutinen kaufen. Der größte Vorteil von bsearch()
und qsort()
ist, dass
sie bereits fertig implementiert sind und dass sie mit jedem ANSI/ISO-
kompatiblen Compiler ausgeliefert werden.
Frage:
Prüfen die mathematischen Funktionen, ob die ihnen übergebenen Daten
innerhalb des zulässigen Wertebereichs liegen?
Antwort:
Gehen Sie nie davon aus, dass die eingegebenen Daten korrekt sind. Prüfen
Sie deshalb alle Daten, die vom Anwender eingegeben werden. Wenn Sie zum
Beispiel der Funktion sqrt()
einen negativen Wert übergeben, erzeugt die
Funktion einen Fehler. Sicherlich werden Sie diesen Fehler nicht so, wie er
ist, ausgeben wollen. Warum nicht? Entfernen Sie die if
-Anweisung aus
Listing 17.1 und geben Sie eine negative Zahl ein, um zu sehen, was ich
meine.
Frage:
Warum unterstützen nicht alle Compiler die gleichen Funktionen?
Antwort:
In der heutigen Diskussion haben Sie gelernt, dass bestimmte C-Funktionen
nicht bei allen Compilern oder allen Computersystemen verfügbar sind
Es gibt zwar Standards, an die sich alle ANSI-Compiler halten. Aber diese Standards verbieten es den Herstellern von Compilern nicht, ihre Produkte mit weiterer Funktionalität auszustatten. Diese neue Funktionalität kommt in Form von neuen Funktionen. Jeder Compiler-Hersteller ergänzt seinen Compiler um eine Reihe von Funktionen, die er mit Blick auf seine Kunden für nützlich hält.
Frage:
Ist C nicht eigentlich eine standardisierte Sprache?
Antwort:
C ist in der Tat stark standardisiert. Das amerikanische Normungsinstitut
(ANSI für American National Standards Institute) hat den ANSI-C-Standard
entwickelt, der fast alle Details von C vorgibt, einschließlich der
mitgelieferten Funktionen. Einige Compiler-Hersteller haben zusätzliche
Funktionen, die nicht Teil des ANSI-Standards sind, in ihre Compiler
aufgenommen, um im Wettbewerb vorne zu liegen. Darüber hinaus kann es
passieren, dass Sie auf einen Compiler stoßen, der nach eigenen Angaben
nicht dem ANSI-Standard entspricht. Wenn Sie sich auf ANSI-Standard-
Compiler beschränken, werden Sie feststellen, dass 99% der
Programmsyntax und -funktionen die gleichen sind.
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.
time_t
?
time()
und clock()
?
perror()
, um einen bestehenden Fehler zu beheben?
bsearch()
durchsuchen?
bsearch()
durchführen, um ein Element in
einem Array mit 16 000 Elementen zu finden?
bsearch()
durchführen, um ein Element in
einem Array mit 10 Elementen zu finden?
bsearch()
durchführen, um ein Element in
einem Array mit 2 000 000 Elementen zu finden?
bsearch()
und qsort()
zurückliefern?
bsearch()
zurück, wenn sie das gesuchte Element nicht findet?
bsearch()
-Aufruf auf. Das Array, das durchsucht werden soll,
heißt namen
und die Werte sind Zeichenfolgen. Die Vergleichsfunktion heißt
vergl_namen()
. Gehen Sie davon aus, dass alle Namen gleich groß sind.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int werte[10], count, suche, ctr;
printf("Geben Sie Werte ein");
for( ctr = 0; ctr < 10; ctr++ )
scanf( "%d", &werte[ctr] );
qsort(werte, 10, vergleich_funktion());
}
int intvgl( int element1, int element2)
{
if ( element 1 > element 2 )
return -1;
else if ( element 1 < element2 )
return 1;
else
return 0;
}
Zu den folgenden Übungen gibt es keine Antworten:
sqrt()
auch mit negativen Zahlen
funktioniert. Sortieren Sie dazu nach dem Absolutwert. Eventuell müssen Sie die
Manpages zu Rate ziehen, um die Funktion zu finden, mit der man den
Absolutwert berechnet.
assert()
. Das
Programm sollte eine Meldung ausgeben, wenn ein negativer Wert eingegeben
wurde.
qsort
sortiert.
Das Programm soll die sortierten Namen ausgeben.
ENDE
« keine weiteren Eingaben einliest und die bis dahin eingegebenen
Werte sortiert.
qsort()
durchzuführen.