vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 17

Die Bibliothek der C-Funktionen

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:

Mathematische Funktionen

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

Trigonometrische Funktionen

Die trigonometrischen Funktionen führen Berechnungen durch, wie sie in Grafik- und Ingenieursprogrammen benötigt werden.

Funktion

Prototyp

Beschreibung

acos()

double acos(double x)

Liefert den Arkuskosinus des Arguments zurück. Das Argument muss im Bereich -1 <= x <= 1 liegen. Der Rückgabewert liegt im Bereich 0 <= acos <= p.

asin()

double asin(double x)

Liefert den Arkussinus des Arguments zurück. Das Argument muss im Bereich -1 <= x <= 1 liegen. Der Rückgabewert liegt im Bereich -p/2 <= asin <= p/2.

atan()

double atan(double x)

Liefert den Arkustangens des Arguments zurück. Der Rückgabewert liegt im Bereich
-p/2 <= atan <= p/2.

atan2()

double atan2

(double x, double y)

Liefert den Arkustangens von x/y zurück. Der Rückgabewert liegt im Bereich -p <= atan2 <= p.

cos()

double cos(double x)

Liefert den Kosinus des Arguments zurück.

sin()

double sin(double x)

Liefert den Sinus des Arguments zurück.

tan()

double tan(double x)

Liefert den Tangens des Arguments zurück.

Exponential- und logarithmische Funktionen

Exponential- und logarithmische Funktionen werden für bestimmte Arten von mathematischen Berechnungen benötigt.

Funktion

Prototyp

Beschreibung

exp()

double exp(double x)

Liefert den natürlichen Exponenten des Arguments zurück - das heißt ex, wobei e gleich 2,7182818284590452354 ist.

log()

double log(double x)

Liefert den natürlichen Logarithmus des Arguments zurück. Das Argument muss größer als Null sein.

log10()

double log10(double x)

Liefert den Logarithmus zur Basis 10 des Arguments zurück. Das Argument muss größer als Null sein.

frexp()

double frexp

(double x, int *y)

Die Funktion berechnet die normalisierte Mantisse zu dem Wert x. Der Rückgabewert r der Funktion ist eine Bruchzahl im Bereich 0.5 <= r <= 1.0. Die Funktion weist y einen Integer-Exponenten zu, so dass x = r * 2y ist. Wenn der Funktion der Wert 0 übergeben wird, sind r und y 0.

ldexp()

double ldexp

(double x, int y)

Liefert x * 2y zurück.

Hyperbolische Funktionen

Die hyperbolischen Funktionen führen hyperbolische trigonometrische Berechnungen aus.

Funktion

Prototyp

Beschreibung

cosh()

double cosh(double x)

Liefert den hyperbolischen Kosinus des Arguments zurück.

sinh()

double sinh(double x)

Liefert den hyperbolischen Sinus des Arguments zurück.

tanh()

double tanh(double x)

Liefert den hyperbolischen Tangens des Arguments zurück.

Weitere mathematische Funktionen

Die Standardbibliothek in C enthält noch diverse andere mathematische Funktionen;

Funktion

Prototyp

Beschreibung

sqrt()

double sqrt(double x)

Liefert die Quadratwurzel des Arguments zurück. Das Argument muss gleich oder größer Null sein.

ceil()

double ceil(double x)

Liefert den kleinsten Integer, der nicht kleiner als das Argument ist, zurück. So liefert zum Beispiel ceil(4.5) 5.0 und ceil(-4.5) -4.0 zurück. Obwohl ceil() einen Integer-Wert zurückliefert, ist dieser vom Typ double.

abs()

int abs(int x)

Liefert den absoluten Wert des Arguments zurück.

floor()

double floor(double x)

Liefert den größten Integer zurück, der nicht größer ist als das Argument. So liefert zum Beispiel floor(4.5) 4.0 und floor(-4.5) -5.0 zurück.

modf()

double modf

(double x, double *y)

Trennt x in einen ganzzahligen und einen Bruchteil auf, die beide das gleiche Vorzeichen wie x erhalten. Der Bruchteil wird von der Funktion zurückgeliefert, der ganzzahlige Teil wird *y zugewiesen.

pow()

double pow

(double x, double y)

Liefert xy zurück. Es tritt ein Fehler auf, wenn x == 0 und y <= 0 ist oder wenn x < 0 und y kein Integer ist.

fmod()

double fmod

(double x, double y)

Liefert den Fließkommarest von x/y mit dem gleichen Vorzeichen wie x zurück. Die Funktion liefert 0 zurück wenn x == 0 ist.

Ein Beispiel für die mathematischen 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.

Zeit und Datum

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.

Darstellung der Zeit

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

Die Zeitfunktionen

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.

Die aktuelle Zeit ermitteln

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

Die Zeitdarstellungen untereinander konvertieren

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.

Zeitangaben ausgeben

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

Spezifizierer

Wodurch er ersetzt wird

%a

Abgekürzter Wochentagsname

%A

Voller Wochentagsname

%b

Abgekürzter Monatsname

%B

Voller Monatsname

%c

Datums- und Zeitdarstellung (zum Beispiel, Tue Apr 18 10:41:50 2000)

%d

Tag des Monats als Dezimalzahl von 01 bis 31

%H

Die Stunde als Dezimalzahl von 00 bis 23

%I

Die Stunde als Dezimalzahl von 00 bis 11

%j

Der Tag des Jahres als Dezimalzahl von 001 bis 366

%m

Der Monat als Dezimalzahl von 01 bis 12

%M

Die Minute als Dezimalzahl von 00 bis 59

%p

AM oder PM

%S

Die Sekunde als Dezimalzahl von 00 bis 59

%U

Die Woche des Jahres als Dezimalzahl von 00 bis 53. Der Sonntag wird als erster Tag der Woche betrachtet

%w

Der Wochentag als Dezimalzahl von 0 bis 6 (Sonntag = 0)

%W

Die Woche des Jahres als Dezimalzahl von 00 bis 53. Der Montag wird als erster Tag der Woche betrachtet

%x

Datumsdarstellung (zum Beispiel, 30-Jun-91)

%X

Zeitdarstellung (zum Beispiel, 10:41:50)

%y

Das Jahr, ohne Jahrhunndert, als Dezimalzahl von 00 bis 99

%Y

Das Jahr, mit Jahrhundert, als Dezimalzahl

%Z

Der Name der Zeitzone, wenn die Information verfügbar ist, oder leer, wenn er nicht bekannt ist

%%

Ein einfaches Prozentzeichen %

Tabelle 17.1: Konvertierungsspezifizierer, die zusammen mit strftime() verwendet werden können.

Zeitunterschiede berechnen

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.

Die Zeitfunktionen verwenden

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.

Funktionen zur Fehlerbehandlung

Die C-Standardbibliothek enthält eine Reihe von Funktionen und Makros, die bei der Behandlung von Programmfehlern dienlich sind.

Die Funktion assert()

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

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.

Name

Wert

Meldung und Bedeutung

E2BIG

1000

Argumentliste zu lang (Listenlänge überschreitet 128 Byte)

EACCES

5

Zugriff verweigert (zum Beispiel beim Versuch, in eine Datei zu schreiben, die nur zum Lesen geöffnet wurde)

EBADF

6

Fehlerhafter Datei-Deskriptor

EDOM

1002

Ein Argument, das einer mathematischen Funktion übergeben wurde, befindet sich außerhalb des zulässigen Definitionsbereichs

EEXIST

80

Datei existiert

EMFILE

4

Zu viele offene Dateien

ENOENT

2

Datei oder Verzeichnis existiert nicht

ENOEXEC

1001

Exec-Formatfehler

ENOMEM

8

Nicht genug Speicher (zum Beispiel nicht genug Speicher, um die exec()-Funktion auszuführen)

ENOPATH

3

Pfad nicht gefunden

ERANGE

1003

Ergebnis außerhalb des gültigen Bereichs (zum Beispiel ist das Ergebnis, das von einer mathematischen Funktion zurückgegeben wird, zu groß oder zu klein für den Datentyp des Rückgabewertes)

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()

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

Was Sie tun sollten

Was nicht

Binden Sie die Header-Datei errno.h mit ein, wenn Sie die symbolischen Fehlercodes aus Tabelle 17.2 verwenden wollen.

Prüfen Sie Ihre Programme auf mögliche Fehler. Gehen Sie niemals davon aus, dass alles fehlerfrei ist.

Binden Sie die Header-Datei errno.h nicht ein, wenn Sie die symbolischen Fehlercodes aus Tabelle 17.2 nicht verwenden wollen.

Funktionen mit einer variablen Zahl von Argumenten

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:

va_list

Ein Zeiger-Datentyp

va_start()

Ein Makro zum Initialisieren der Argumentenliste

va_arg()

Ein Makro, mit dem man nacheinander auf die Argumente in der Argumentenliste zugreifen kann

va_end()

Ein Makro für die Aufräumarbeiten

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:

  1. Deklarieren Sie eine Zeigervariable vom Typ 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.
  2. Rufen Sie das Makro 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.
  3. Um die einzelnen Argumente anzusprechen, rufen Sie das Makro 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.
  4. Wenn alle Argumente aus der Argumentenliste angesprochen wurden, rufen Sie das Makro 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.

Suchen und sortieren

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.

Suchen mit bsearch()

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:

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:

  1. Der Schlüssel wird mit dem Element in der Mitte des Arrays verglichen. Gibt es bereits an dieser Stelle eine Übereinstimmung, hat sich eine weitere Suche erledigt. Andernfalls muss der Schlüssel entweder kleiner oder größer als das Array-Element sein.
  2. Wenn der Schlüssel kleiner als das Array-Element ist, muss sich das gesuchte Element, falls überhaupt vorhanden, in der ersten Hälfte des Arrays befinden. Entsprechend befindet sich das gesuchte Element, wenn es größer als das Array- Element ist, in der zweiten Hälfte des Arrays.
  3. Die Suche wird auf die entsprechende Hälfte des Arrays beschränkt und der Algorithmus beginnt wieder mit Schritt 1.

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.

Sortieren mit qsort()

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.

Suchen und sortieren: zwei Beispiele

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.

Was Sie tun sollten

Vergessen Sie nicht, Ihr Such-Array in aufsteigender Reihenfolge zu sortieren, bevor Sie bsearch() verwenden.

Zusammenfassung

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.

Fragen und Antworten

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.

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. Wie lautet der Rückgabetyp für die mathematischen Funktionen von C?
  2. Welchem Variablentyp von C entspricht time_t?
  3. Worin bestehen die Unterschiede zwischen den Funktionen time() und clock()?
  4. Was macht die Funktion perror(), um einen bestehenden Fehler zu beheben?
  5. Kann man eine Funktion schreiben, die eine beliebig lange Argumentenliste ohne feste Argumente übernimmt?
  6. Welche Makros sollten Sie verwenden, wenn Sie Funktionen mit beliebig langen Argumentenlisten schreiben?
  7. Was müssen Sie machen, bevor Sie ein Array mit bsearch() durchsuchen?
  8. Wie viele Vergleiche müsste man mit bsearch() durchführen, um ein Element in einem Array mit 16 000 Elementen zu finden?
  9. Wie viele Vergleiche müsste man mit bsearch() durchführen, um ein Element in einem Array mit 10 Elementen zu finden?
  10. Wie viele Vergleiche müsste man mit bsearch() durchführen, um ein Element in einem Array mit 2 000 000 Elementen zu finden?
  11. Welche Werte muss eine Vergleichsfunktion für bsearch() und qsort() zurückliefern?
  12. Was liefert bsearch() zurück, wenn sie das gesuchte Element nicht findet?

Übungen

  1. Setzen Sie einen 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.
  2. FEHLERSUCHE: Was ist falsch an folgendem Programm?
    #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());
    }
  3. FEHLERSUCHE: Ist irgendetwas falsch an der Vergleichsfunktion?
    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:

  1. Ändern Sie Listing 17.1 so ab, dass 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.
  2. Schreiben Sie ein Programm mit einem Menü, das Optionen für verschiedene mathematische Funktionen enthält. Verwenden Sie so viele mathematische Funktionen wie möglich.
  3. Schreiben Sie unter Zuhilfenahme der heute besprochenen Zeitfunktionen eine Funktion, die das Programm für ungefähr fünf Sekunden anhält.
  4. 7. Ergänzen Sie das Programm aus Übung 4 um die Funktion assert(). Das Programm sollte eine Meldung ausgeben, wenn ein negativer Wert eingegeben wurde.
  5. Schreiben Sie ein Programm, das 30 Namen einliest und diese mit qsort sortiert. Das Programm soll die sortierten Namen ausgeben.
  6. Ändern Sie das Programm in Übung 8 so, dass das Programm nach der Eingabe von »ENDE« keine weiteren Eingaben einliest und die bis dahin eingegebenen Werte sortiert.
  7. Am Tag 14 wurde eine simple Methode zum Sortieren eines Arrays von Zeigern auf Strings vorgeführt. Schreiben Sie ein Programm, das die Zeit misst, die benötigt wird, um ein großes Array von Zeigern nach dieser Methode zu sortieren, und diese Zeit dann mit der Zeit vergleicht, die benötigt wird, um die gleiche Suche mit der Bibliotheksfunktion qsort()durchzuführen.
  8. Implementieren Sie eine Funktion, die a) eine beliebige Zahl von Strings als Argumente übernimmt, b) die Strings in sortierter Reihenfolge aneinander hängt zu einem einzigen großen String und c) einen Zeiger auf den neuen String an das aufrufende Programm zurückliefert.
12

vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


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