vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 13

Mit Bildschirm und Tastatur arbeiten

Fast jedes Programm muss Daten einlesen und ausgeben. Wie gut ein Programm diese Ein- und Ausgabe handhabt, ist oft der beste Indikator für die Zweckmäßigkeit des Programms. Die grundlegende Vorgehensweise bei der Ein- und Ausgabe haben Sie bereits kennen gelernt. Heute lernen Sie:

Streams in C

Bevor wir uns mit den Feinheiten der Ein- und Ausgabe befassen, müssen wir klären, was ein Stream ist. In C erfolgt die gesamte Ein- und Ausgabe über Streams, egal woher die Eingabe kommt oder wohin die Ausgabe geht. Wie Sie später noch sehen werden, hat dieser standardisierte Umgang mit der Ein- und Ausgabe unbestreitbar Vorzüge für den Programmierer, setzt aber natürlich voraus, dass Sie wissen, was Streams sind und wie sie arbeiten. Erst einmal jedoch soll genau definiert werden, was man unter den Begriffen Eingabe und Ausgabe versteht.

Was genau versteht man unter Programmeingabe und -ausgabe?

Weiter vorn in diesem Buch wurde bereits darauf hingewiesen, dass ein C-Programm während der Ausführung die Daten im RAM (Speicher mit wahlfreiem Zugriff) ablegt. Diese Daten haben die Form von Variablen, Strukturen und Arrays, die von dem Programm deklariert wurden. Woher aber kommen diese Daten und was kann das Programm damit machen?

Eingabequellen und Ausgabeziele werden zusammengefasst als Geräte bezeichnet. Die Tastatur ist ein Gerät, der Bildschirm ist ein Gerät und so weiter. Einige Geräte (die Tastatur) dienen nur der Eingabe, andere (der Bildschirm) nur der Ausgabe und wiederum andere (Dateien) sind für Ein- und Ausgabe geeignet (siehe Abbildung 13.1).

Abbildung 13.1:  Ein- und Ausgabe kann zwischen Ihrem Programm und einer Vielzahl von externen Geräten stattfinden.

Welches Gerät auch immer betroffen ist - sei es für Eingabe oder Ausgabe -, C führt alle Operationen mit Hilfe von Streams aus.

Was ist ein Stream?

Ein Stream (zu Deutsch »Strom«) ist eine Folge von Zeichen oder genauer gesagt eine Folge von Daten-Bytes. Eine Folge von Bytes, die in ein Programm fließt, ist ein Eingabe-Stream, und eine Folge von Bytes, die aus einem Programm herausfließt, ist ein Ausgabe-Stream. Indem man sich bei der Programmierung auf das allgemeine Konzept des Streams konzentriert, braucht man sich weniger Gedanken darüber zu machen, wohin die Daten gehen oder woher sie kommen. Der größte Vorteil von Streams ist daher, dass die Ein- und Ausgabeprogrammierung geräteunabhängig wird. Programmierer müssen keine speziellen Ein- und Ausgabefunktionen für jedes Gerät (Tastatur, Festplatte und so weiter) schreiben. Das Programm behandelt die Ein- und Ausgabe einfach als einen kontinuierlichen Strom von Byte, egal woher die Eingabe kommt oder wohin die Ausgabe geht.

In C ist jeder Stream mit einer Datei verbunden. Der Begriff Datei bezieht sich dabei nicht auf die auf Festplatte gespeicherten Dateien, sondern steht vielmehr für eine Schnittstelle, die zwischen dem Stream, mit dem sich Ihr Programm befasst, und dem eigentlichen physikalischen Gerät, das für die Ein- und Ausgabe verwendet wird, vermittelt. Als C-Anfänger brauchen Sie sich um diese Dateien kaum zu kümmern, da die C-Bibliotheksfunktionen und das Betriebssystem die Wechselwirkungen zwischen Streams, Dateien und Geräten automatisch regeln.

Vordefinierte Streams

In ANSI C gibt es drei vordefinierte Streams, die auch als Standard-E/A-Dateien bezeichnet werden und alle drei unter Linux verfügbar sind. Diese Streams werden automatisch geöffnet, wenn die Ausführung eines C-Programms beginnt, und geschlossen, wenn das Programm zu Ende ist. Der Programmierer muss keine besonderen Schritte unternehmen, um diese Streams verfügbar zu machen. Tabelle 13.1 enthält eine Übersicht über die Standard-Streams und zeigt, mit welchen Geräten sie normalerweise verbunden sind.

Name

Streams

Geräte

stdin

Standardeingabe

Tastatur

stdout

Standardausgabe

Bildschirm

stderr

Standardfehlerausgabe

Bildschirm

Tabelle 13.1: Die drei Standard-Streams.

Immer wenn Sie die Funktionen printf() oder puts() verwenden, um Text auf dem Bildschirm auszugeben, verwenden Sie den stdout-Stream. Entsprechend verwenden Sie den stdin-Stream, wenn Sie mit gets() oder scanf() Eingaben von der Tastatur einlesen. (Vielleicht erinnern Sie sich noch an Tag 9, »Zeichen und Strings«, als Sie der Funktion fgets()als drittes Argument stdin übergeben haben.)

Die Standard-Streams werden automatisch geöffnet, andere Streams jedoch - beispielsweise Streams zur Bearbeitung von Daten, die auf der Platte gespeichert sind - müssen explizit geöffnet werden. Wie man dabei vorgeht, erfahren Sie am Tag 15, »Mit Dateien arbeiten«. Der Rest der heutigen Diskussion behandelt ausschließlich die Standard-Streams.

Die Stream-Funktionen von C

In der Standardbibliothek von C gibt es eine Vielzahl von Funktionen, die für die Streameingabe und -ausgabe zuständig sind. Die meisten dieser Funktionen gibt es in zwei Ausprägungen: eine, die immer die Standard-Streams verwendet, und eine, für die der Programmierer den Stream angeben muss. Eine Liste dieser Funktionen finden Sie in Tabelle 13.2. Diese Tabelle enthält allerdings nicht alle Ein- und Ausgabefunktionen von C, auch werden nicht alle Funktionen in der Tabelle heute behandelt.

Verwendet einen der Standard-Streams

Erfordert einen Stream-Namen

Beschreibung

printf()

fprintf()

Formatierte Ausgabe

puts()

fputs()

String-Ausgabe

putchar()

putc(), fputc()

Zeichenausgabe

scanf()

fscanf()

Formatierte Eingabe

gets()

fgets()

String-Eingabe

getchar()

getc(), fgetc()

Zeicheneingabe

perror()

String-Ausgabe nur an stderr

Tabelle 13.2: Die Stream-Funktionen der Standardbibliothek für die Ein- und Ausgabe.

Um diese Funktionen verwenden zu können, müssen Sie die Header-Datei stdio.h einbinden. Die Funktionen vprintf() und vfprintf() erfordern außerdem die Datei stdargs.h. Auf einigen Nicht-Linux-Systemen benötigt man für perror() die Header- Datei stdlib.h und für vprintf() und vfprintf() die Header-Datei varargs.h. Schauen Sie in der Bibliotheksreferenz Ihres Compilers oder in Ihren Manpages nach, ob Sie weitere oder andere Header-Dateien benötigen.

Ein Beispiel

Das kleine Programm aus Listing 13.1 demonstriert die Verwendung von Streams.

Listing 13.1: Die Gleichwertigkeit von Streams.

1:  /* Beispiel für die Gleichwertigkeit von Stream-Eingabe und -Ausgabe. */
2: #include <stdio.h>
3:
4: int main(void)
5: {
6: char puffer[256];
7:
8: /* Liest eine Zeile ein und gibt sie sofort wieder aus. */
9:
10: puts(fgets(puffer, 256, stdin));
11:
12: return 0;
13: }

In Zeile 10 wird mit Hilfe der Funktion fgets() eine Textzeile von der Tastatur (stdin) eingelesen. Da fgets() einen Zeiger auf den eingelesenen String zurückliefert, kann dieser als Argument an die Funktion puts() verwendet werden, die den String auf dem Bildschirm (stdout) ausgibt. Wenn das Programm ausgeführt wird, liest es eine Textzeile vom Anwender ein und gibt diesen String dann sofort auf dem Bildschirm aus.

Was Sie tun sollten

Was nicht

Nutzen Sie die Vorteile der Standardein- und -ausgabe-Streams von C.

Vermeiden Sie es, die Standard-Streams unnötig umzubenennen oder zu ändern.

Versuchen Sie nicht, einen Eingabe- Stream wie stdin für eine Ausgabefunktion wie fprintf() zu verwenden.

Tastatureingaben einlesen

Die meisten C-Programme sind darauf angewiesen, in irgendeiner Form Daten von der Tastatur (das heißt, von stdin) einzulesen. Die zur Verfügung stehenden Eingabefunktionen kann man in eine Hierarchie von drei Ebenen einordnen: Zeicheneingabe, Zeileneingabe und formatierte Eingabe.

Zeicheneingabe

Die Zeicheneingabefunktionen lesen die Eingaben zeichenweise aus dem Stream ein. Wenn aufgerufen, liefern die Zeicheneingabefunktionen das nächste Zeichen im Stream zurück oder EOF, falls das Ende der Datei erreicht wurde oder ein Fehler aufgetreten ist. EOF ist eine symbolische Konstante, die in stdio.h als -1 definiert ist. Die Zeicheneingabefunktionen unterscheiden sich hinsichtlich der Pufferung und dem Bildschirmecho.

stdin und stdout sind unter Linux und anderen Unix-ähnlichen Betriebssystemen standardmäßig gepufferte Streams. Der Standardfehler-Stream ist ungepuffert. Es gibt zwar die Möglichkeit, das Standardverhalten von stdin und stdout zu ändern, aber das würde den Rahmen dieses Buches sprengen. Lassen Sie uns deshalb in der heutigen Lektion einmal davon ausgehen, dass stdin und stdout gepufferte Streams sind.

Die Funktion getchar()

Die Funktion getchar() liest das nächste Zeichen aus dem Stream stdin ein. Die Funktion unterstützt die gepufferte Zeicheneingabe mit Echoverhalten, und ihr Prototyp lautet:

int getchar(void);

In Listing 13.2 sehen Sie ein Beispiel für die Verwendung von getchar(). Die Funktion putchar(), die später noch besprochen wird, gibt einfach ein einzelnes Zeichen auf dem Bildschirm aus.

Listing 13.2: Die Funktion getchar().

1: /* Beispiel für die Funktion getchar(). */
2:
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: int ch;
8:
9: while ((ch = getchar()) != '\n')
10: putchar(ch);
11:
12: return 0;
13: }

Dies wurde eingegeben.
Dies wurde eingegeben.

In Zeile 9 wird die Funktion getchar() aufgerufen. Sie wartet auf den Empfang eines Zeichens von stdin. Da getchar() eine gepufferte Eingabefunktion ist, werden die Zeichen erst empfangen, wenn Sie die Eingabetaste drücken. Jede Taste, die Sie drücken, wird jedoch direkt als Echo auf dem Bildschirm ausgegeben.

Wenn Sie die Eingabetaste drücken, werden alle Zeichen einschließlich des Neue- Zeile-Zeichens vom Betriebssystem an stdin geschickt. Die Funktion getchar() liefert diese Zeichen zeichenweise zurück und weist sie jeweils ch zu.

Jedes eingelesene Zeichen wird mit dem Neue-Zeile-Zeichen '\n' verglichen und bei Nichtgleichheit mit putchar() auf den Bildschirm ausgegeben. Wenn getchar() ein Neue-Zeile-Zeichen zurückliefert, wird die while-Schleife beendet.

Die getchar()-Funktion kann auch dazu verwendet werden, ganze Textzeilen einzulesen (siehe Listing 13.3). Allerdings sind andere Eingabefunktionen für diese Aufgabe besser geeignet, wie Sie später noch sehen werden.

Listing 13.3: Mit der getchar()-Funktion eine ganze Textzeile einlesen.

1: /* Mit getchar() Strings einlesen. */
2:
3: #include <stdio.h>
4:
5: #define MAX 80
6:
7: int main(void)
8: {
9: char ch, puffer[MAX+1];
10: int x = 0;
11:
12: while ((ch = getchar()) != '\n' && x < MAX)
13: puffer[x++] = ch;
14:
15: puffer[x] = '\0';
16:
17: printf("%s\n", puffer);
18:
19: return 0;
20: }

Dies ist ein String
Dies ist ein String

Dieses Programm ist dem Programm in Listing 13.2 hinsichtlich der Verwendung von getchar() sehr ähnlich. Die Schleife wurde um eine weitere Bedingung erweitert. Dieses Mal akzeptiert die while-Schleife so lange Zeichen von getchar(), bis die Funktion entweder auf ein Neue-Zeile-Zeichen stößt oder 80 Zeichen eingelesen wurden. Die einzelnen Zeichen werden in dem Array puffer abgelegt. Nachdem die Zeichen eingelesen sind, hängt Zeile 15 ein Nullzeichen an das Ende des Arrays, so dass die printf()-Funktion in Zeile 17 den eingegebenen String ausgeben kann.

Warum wurde puffer in Zeile 9 mit einer Größe von MAX+1 und nicht einfach mit MAX deklariert? Wenn Sie den Puffer mit einer Größe von MAX+1 deklarieren, kann der String 80 Zeichen lang sein plus dem abschließenden Nullzeichen. Vergessen Sie nicht, am Ende Ihrer Strings Platz für das abschließenden Nullzeichen einzuplanen!

Die Funktionen getc() und fgetc()

Die Zeicheneingabefunktionen getc() und fgetc() arbeiten nicht automatisch mit stdin. Statt dessen kann das Programm selbst den zu verwendenden Eingabe-Stream angeben. Im Übrigen entspricht die Funktion getchar() unter Linux und den meisten anderen Unix-ähnlichen Systemen dem Aufruf getc(stdin). Die Funktionen getc() und fgetc() werden hauptsächlich dazu verwendet, Zeichen aus Dateien zu lesen. Doch mehr dazu am Tag 15.

Was Sie tun sollten

Was nicht

Machen Sie sich den Unterschied zwischen Eingaben mit automatischem Bildschirmecho und ohne automatisches Bildschirmecho klar.

Machen Sie sich den Unterschied zwischen gepufferter und ungepufferter Eingabe klar.

Verwenden Sie keine Nicht-ANSI- Funktionen, wenn Ihnen an der Portierbarkeit Ihres Codes gelegen ist.

Ein Zeichen mit ungetc() »zurückstellen«

Was bedeutet es, ein Zeichen »zurückzustellen«? Lassen Sie mich dies anhand eines Beispiels erläutern. Angenommen Ihr Programm liest Zeichen von einem Eingabe- Stream und kann das Ende der Eingabe nur feststellen, indem es ein Zeichen mehr als nötig einliest. Wenn beispielsweise nur Ziffern eingelesen werden sollen, wissen Sie, dass das Ende der Eingabe erreicht ist, wenn Sie beim Einlesen auf die erste Nichtziffer treffen. Es kann aber gut sein, dass gerade dieses Zeichen ein wichtiger Bestandteil der nachfolgenden Daten ist und leider durch das Einlesen aus dem Eingabe-Stream entfernt wurde. Heißt das, es ist für immer verloren? Nein. Es kann dem Eingabe-Stream wieder zugeführt werden, so dass es bei der nächsten Einleseoperation für diesen Stream als Erstes ausgelesen wird.

Um ein Zeichen »zurückzustellen«, rufen Sie die Bibliotheksfunktion ungetc() auf. Ihr Prototyp lautet:

int ungetc(int ch, FILE *fp);

Das Argument ch ist das Zeichen, das zurückgegeben werden soll. Das Argument *fp gibt den Stream an, dem das Zeichen zugeführt werden soll. Dabei kann es sich um einen beliebigen Eingabe-Stream handeln. Im Moment begnügen wir uns damit, als zweites Argument stdin zu übergeben: ungetc(ch, stdin);. Die Notation FILE *fp wird für Streams verwendet, die mit Dateien verbunden sind (mehr dazu am Tag 15).

Zwischen den einzelnen Einleseoperationen können Sie jeweils nur ein Zeichen zurückstellen. EOF lässt sich überhaupt nicht zurückstellen. Die Funktion ungetc() liefert im Erfolgsfall ch zurück oder EOF, wenn das Zeichen dem Stream nicht wieder zugeführt werden kann.

Zeileneingabe

Die Zeileneingabefunktionen lesen ganze Zeilen aus einem Eingabe-Stream ein - d.h. sie lesen alle Zeichen bis zum nächsten Neue-Zeile-Zeichen. In der Standardbibliothek stehen zwei Funktionen für das Einlesen von ganzen Zeilen zur Verfügung: gets() und fgets().

Die Funktion gets()

Die Funktion gets() wurde Ihnen bereits am Tag 9 vorgestellt, einschließlich aller Gefahren, die mit ihrer Verwendung verbunden sind. Mehr gibt es zu dieser Funktion nicht zu sagen.

Die Funktion fgets()

Die Bibliotheksfunktion fgets() ähnelt stark der Funktion gets(), und zwar insofern, als sie ebenfalls eine Textzeile aus einem Eingabe-Stream ausliest. Die Funktion fgets() ist allerdings wesentlich vielseitiger einsetzbar, weil der Programmierer den zu verwendenden Eingabe-Stream sowie die maximale Anzahl der einzulesenden Zeichen selbst angeben kann. fgets() wird häufig für das Einlesen von Text aus Dateien verwendet (wird am Tag 15 besprochen). Um die Funktion für das Einlesen aus stdin zu verwenden, müssen Sie stdin als Eingabe-Stream angeben. Der Prototyp von fgets() lautet:

char *fgets(char *str, int n, FILE *fp);

Der letzte Parameter, FILE *fp, dient zur Angabe des Eingabe-Streams. Geben Sie einfach den Standardeingabe-Stream stdin an, wie Sie es bereits seit seiner Einführung am Tag 9 gemacht haben.

Der Zeiger str verweist auf den Speicherplatz, an dem der Eingabestring abgelegt werden soll. Das Argument n gibt die maximale Anzahl der einzulesenden Zeichen an. Die Funktion fgets() liest so lange Zeichen aus dem Eingabe-Stream ein, bis sie auf ein Neue-Zeile-Zeichen trifft oder n-1 Zeichen eingelesen worden sind. Das Neue- Zeile-Zeichen wird in den String mit aufgenommen, der mit einem \0 abgeschlossen und gespeichert wird. fgets() liefert einen Zeiger vom Typ char zurück. Im Erfolgsfall weist dieser Zeiger auf die Adresse, an der der Eingabestring gespeichert ist; falls ein Fehler aufgetreten ist, wird ein NULL-Zeiger zurückgeliefert.

Beachten Sie, dass fgets() nicht zwangsweise eine ganze Textzeile einliest (wobei wir unter einer Textzeile eine Folge von Zeichen verstehen, die mit einem Neue-Zeile- Zeichen endet). Sie kann weniger als eine ganze Zeile einlesen, wenn die Zeile mehr als n-1 Zeichen enthält. Wenn Sie die Funktion zusammen mit stdin verwenden, kehrt die Programmausführung allerdings nicht automatisch nach n-1 Zeichen aus fgets() zurück, sondern erst dann, wenn der Anwender die Eingabetaste drückt. Es werden jedoch trotzdem nur die ersten n-1 Zeichen in dem String gespeichert. Listing 13.4 veranschaulicht die Verwendung von fgets().

Listing 13.4: Die Funktion fgets() für Tastatureingaben.

1:  /* Beispiel für die Funktion fgets(). */
2:
3: #include <stdio.h>
4:
5: #define MAXLAEN 10
6:
7: int main(void)
8: {
9: char puffer[MAXLAEN];
10:
11: puts("Geben Sie ganze Textzeilen ein. Ende mit Eingabetaste.");
12:
13: while (1)
14: {
15: fgets(puffer, MAXLAEN, stdin);
16:
17: if (puffer[0] == '\n')
18: break;
19:
20: puts(puffer);
21: }
22: return 0;
23: }

Geben Sie ganze Textzeilen ein. Ende mit der Eingabetaste.
Rosen sind rot
Rosen sin
d rot
Veilchen sind blau
Veilchen
sind blau

Programmierung in C
Programmi
erung in
C

Für Leute wie Dich!
Für Leute
wie Dich
!

Die fgets()-Funktion wird in Zeile 15 aufgerufen. Wenn Sie das Programm ausführen, geben Sie Zeilen ein, die länger oder kürzer sind, als in MAXLAEN definiert, und beobachten Sie was passiert. Wenn eine Zeile eingegeben wird, die länger als MAXLAEN ist, werden beim ersten Aufruf von fgets() die ersten MAXLAEN-1 Zeichen gelesen. Die restlichen Zeichen verbleiben im Tastaturpuffer und werden beim nächsten Aufruf von fgets() oder einer anderen Funktion, die aus stdin liest, ausgelesen. Das Programm wird verlassen, wenn eine Leerzeile eingegeben wird (Zeilen 17 und 18).

Formatierte Eingabe

Die bisher beschriebenen Eingabefunktionen haben einfach ein oder mehrere Zeichen aus einem Eingabe-Stream ausgelesen und irgendwo im Speicher abgelegt. Die Eingabe wurde weder interpretiert noch formatiert, und außerdem wissen Sie immer noch nicht, wie man numerische Variablen einliest. Wie würden Sie zum Beispiel den Wert 12.86 von der Tastatur einlesen und ihn einer Variablen vom Typ float zuweisen? Dazu stehen Ihnen die Funktion scanf() und fscanf() zur Verfügung. scanf() haben Sie bereits am Tag 6, »Grundlagen der Ein- und Ausgabe«, kennen gelernt. In diesem Abschnitt werden wir uns intensiver mit scanf() beschäftigen.

Die beiden Funktionen scanf() und fscanf() sind fast identisch. scanf() verwendet immer stdin, während Sie bei fscanf() den Eingabe-Stream angeben können. Dieser Abschnitt behandelt vornehmlich scanf(). fscanf() wird im Allgemeinen für das Einlesen aus Dateien verwendet und am Tag 15 besprochen.

Die Argumente der Funktion scanf()

Die Funktion scanf() kann eine beliebige Zahl von Argumenten übernehmen, erwartet allerdings mindestens zwei Argumente. Das erste Argument ist stets ein Formatstring, der scanf() mit Hilfe spezieller Zeichen mitteilt, wie die Eingabe zu interpretieren ist. Das zweite und alle weiteren Argumente sind die Adressen der Variablen, in denen die Eingabedaten abgespeichert werden. Sehen Sie dazu folgendes Beispiel:

scanf("%d", &x);

Das erste Argument, "%d", ist der Formatstring. Hier teilt %d der Funktion scanf() mit, nach einem vorzeichenbehafteten Integer-Wert Ausschau zu halten. Das zweite Argument verwendet den Adressoperator (&), damit scanf()den Eingabewert in der Variablen x speichern kann. Schauen wir uns den Formatstring jetzt einmal genauer an.

Der Formatstring von scanf() kann folgende Elemente enthalten:

Der einzige obligatorische Teil des Formatstrings sind die Konvertierungsspezifizierer. Jeder Konvertierungsspezifizierer beginnt mit einem %-Zeichen, gefolgt von optionalen und obligatorischen Komponenten, die in einer bestimmten Reihenfolge stehen müsen. Die scanf()-Funktion wendet die Konvertierungsspezifizierer im Formatstring der Reihe nach auf die Eingabefelder an. Ein Eingabefeld ist eine Folge von Nicht-Whitespace-Zeichen, das endet, wenn in der Eingabe das nächste Whitespace-Zeichen folgt oder die im Konvertierungsspezifizierer angegebene Feldlänge erreicht ist (falls der Spezifizierer eine Angabe für die Feldlänge enthält). Zu den Komponenten der Konvertierungsspezifizierer gehören:

Typ

Argument

Bedeutung des Typs

d

int *

Eine Dezimalzahl.

i

int *

Ein Integer in Dezimal-, Oktal- (mit führender 0) oder Hexadezimalnotation (mit führendem 0X oder 0x).

o

int *

Ein Integer in Oktalnotation mit oder ohne führende 0.

u

unsigned int *

Eine vorzeichenlose Dezimalzahl.

x

int *

Ein hexadezimaler Integer mit oder ohne führendes 0X oder 0x.

c

char *

Ein oder mehr Zeichen werden gelesen und hintereinander an der Speicherstelle abgelegt, die durch das Argument zu dem Spezifizierer vorgegeben ist. Es wird kein abschließendes \0 hinzugefügt. Wenn kein Argument für die Feldlänge angegeben wurde, wird nur ein Zeichen gelesen. Bei Angabe der Feldlänge wird die vorgegebene Anzahl an Zeichen, einschließlich Whitespace-Zeichen (soweit vorhanden), gelesen.

s

char *

Es wird ein String von Nicht-Whitespaces in die angegebene Speicherstelle eingelesen und ein abschließendes \0 hinzugefügt.

e,f,g

float *

Eine Fließkommazahl. Zahlen können in Dezimalschreibweise oder wissenschaftlicher Notation eingegeben werden.

[...]

char *

Ein String. Nur die Zeichen, die innerhalb der eckigen Klammern aufgelistet werden, werden akzeptiert. Die Eingabe endet, sobald scanf() auf ein nicht übereinstimmendes Zeichen trifft, die angegebene Feldlänge erreicht ist oder die Eingabetaste gedrückt wurde. Um das ]-Zeichen zu akzeptieren, geben Sie es als Erstes an: []...]. Am Ende des Strings wird \0 hinzugefügt.

[^...]

char *

Das Gleiche wie [...], außer dass nur Zeichen akzeptiert werden, die nicht in eckigen Klammern stehen.

%

-

Literal %. Liest das %-Zeichen. Es erfolgt keine Zuweisung.

Tabelle 13.3: Zeichen für die Typspezifizierer, wie sie in den scanf()-Konvertierungsspezifizierern verwendet werden.

Bevor wir uns einige Beispiele zu scanf() anschauen, möchte ich Ihnen noch die Genauigkeitsmodifizierer etwas näher vorstellen:

Genauigkeitsmodifizierer

Bedeutung

h

Vor den Typspezifizierern d, i, o, u oder x gibt der Modifizierer h an, dass das Argument ein Zeiger auf den Typ short und nicht int ist.

l

Vor den Typspezifizierern d, i, o, u oder x gibt der Modifizierer l an, dass das Argument ein Zeiger auf den Typ long ist. Vor den Typspezifizierern e, f oder g gibt der Modifizierer l an, dass das Argument ein Zeiger auf den Typ double ist.

L

Vor den Typspezifizierern e, f oder g gibt der Modifizierer L an, dass das Argument ein Zeiger auf den Typ long double ist.

Tabelle 13.4: Genauigkeitsmodifizierer.

Handhabung übrig gebliebener Zeichen

Die Eingabe für scanf() ist gepuffert. Erst wenn der Anwender die Eingabetaste drückt, schickt stdin die Zeichen. Dann kommt die ganze Zeichenkette von stdin bei scanf() an und wird Zeichen für Zeichen verarbeitet. Die Funktion scanf() gibt die Programmausführung erst wieder ab, wenn sie so viele Zeichen empfangen hat, wie gemäß den Angaben im Formatstring benötigt werden. Werden mehr Zeichen als benötigt von stdin geschickt, verarbeitet scanf() nur so viele Zeichen, wie im Formatstring angegeben. Etwaige übrig gebliebene, nicht benötigte Zeichen verbleiben in stdin. Diese Zeichen können Probleme verursachen. Betrachten wir die Funktionsweise von scanf() einmal näher, um zu sehen, wo Probleme bestehen.

Nehmen wir an, scanf() wird mit den folgenden Argumenten aufgerufen: scanf("%d %d", &x, &y), d.h. scanf() erwartet zwei Dezimalwerte. Nachdem der Anwender als Antwort eine Zeile über die Tastatur eingegeben hat, gibt es drei Möglichkeiten:

Es ist diese dritte Situation - die übrig gebliebenen Zeichen -, die Probleme verursachen kann. Solange Ihr Programm läuft, bleiben diese Zeichen in stdin - bis das Programm erneut Eingaben aus stdin ausliest. Dann werden die in stdin verbliebenen Zeichen als Erstes ausgelesen, sogar noch vor der Eingabe, die der Anwender diesmal vornimmt. Es sollte klar sein, dass es dabei zu Fehlern kommen kann. Beispielsweise fordert der folgende Code den Anwender auf, erst einen Integer und dann einen String einzugeben:

puts("Geben Sie Ihr Alter ein.");
scanf("%d", &alter);
puts("Geben Sie Ihren Vornamen ein.");
scanf("%s", name);

Angenommen der Anwender ist übergenau und gibt als Antwort auf die erste Eingabeaufforderung 29.00 ein und drückt dann die Eingabetaste. Der erste Aufruf an scanf() hält Ausschau nach einem Integer. Deshalb liest er die Zeichen 29 von stdin ein und weist diesen Wert der Variablen alter zu. Die Zeichen .00 verbleiben in stdin. Der nächste Aufruf an scanf() hält Ausschau nach einem String. Auf der Suche nach einer Eingabe in stdin findet er dort .00. Folglich wird der String .00 der Variablen name zugewiesen.

Wie können Sie solche Fehler vermeiden? Eine Lösung wäre, dass alle, die Ihre Programme ausführen, nie einen Fehler machen - allerdings ist das äußerst unwahrscheinlich.

Eine bessere Lösung wäre es, sicherzustellen, dass keine Zeichen in stdin sind, wenn man den Anwender um eine Eingabe bittet. Das lässt sich mit einem Aufruf von fgets() bewerkstelligen - einer Funktion, die alle verbliebenen Zeichen von stdin bis einschließlich dem Zeilenende einliest. Statt fgets() direkt aufzurufen, können Sie diesen Aufruf auch in einer separaten Funktion mit dem anschaulichen Namen tastatur_loeschen() unterbringen - wie in Listing 13.5 zu sehen.

Listing 13.5: Übrig gebliebene Zeichen aus stdin löschen, um Fehler zu vermeiden.

1: /* Übrig gebliebene Zeichen in stdin löschen. */
2:
3: #include <stdio.h>
4:
5: void tastatur_loeschen(void);
6:
7: int main(void)
8: {
9: int alter;
10: char name[20];
11:
12: /* Fragt nach dem Alter des Anwenders. */
13:
14: puts("Geben Sie Ihr Alter ein.");
15: scanf("%d", &alter);
16:
17: /* Löscht alle übrig gebliebenen Zeichen in stdin. */
18:
19: tastatur_loeschen();
20:
21: /* Fragt nach dem Namen des Anwenders. */
22:
23: puts("Geben Sie Ihren Vornamen ein.");
24: scanf("%s", name);
25: /* Gibt die Daten aus. */
26:
27: printf("Ihr Alter ist %d.\n", alter);
28: printf("Ihr Name lautet %s.\n", name);
29:
30: return 0;
31: }
32:
33: void tastatur_loeschen(void)
34:
35: /* Löscht alle in stdin verbliebenen Zeichen. */
36: {
37: char muell[80];
38: fgets(muell,80,stdin);
39: }

Geben Sie Ihr Alter ein.
29 und keinen Tag älter!
Geben Sie Ihren Vornamen ein.
Bradley
Ihr Alter ist 29.
Ihr Name lautet Bradley.

Wenn Sie das Programm aus Listing 13.5 ausführen, sollten Sie einige zusätzliche Zeichen eingeben, wenn Sie nach Ihrem Alter gefragt werden. Vergewissern Sie sich, dass das Programm diese Zeichen ignoriert und Sie korrekt nach Ihrem Namen fragt. Ändern Sie anschließend das Programm, indem Sie den Aufruf von tastatur_loeschen() entfernen, und starten Sie es dann erneut. Alle zusätzlichen Zeichen, die zusammen mit Ihrem Alter eingegeben wurden, werden jetzt name zugewiesen.

Übrig gebliebene Zeichen mit fflush() entfernen

Es gibt noch einen zweiten Weg, die zu viel eingegebenen Zeichen zu löschen. Mit der Funktion fflush() können die Daten in einem Stream - auch im Standardeingabe- Stream - gelöscht werden. fflush() wird im Allgemeinen zusammen mit Dateien verwendet (die am Tag 15 behandelt werden). Sie können die Funktion jedoch auch zur Vereinfachung des Listings 13.5 verwenden. Listing 13.6 verwendet fflush() anstelle der Funktion tastatur_loeschen(), die in Listing 13.5 benutzt wurde.

Listing 13.6: Übrig gebliebene Zeichen mit fflush() aus stdin löschen.

1:  /* Löscht alle übrig gebliebenen Zeichen in stdin */
2: /* mit Hilfe der Funktion fflush(). */
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: int alter;
8: char name[20];
9:
10: /* Fragt nach dem Alter des Anwenders. */
11: puts("Geben Sie Ihr Alter ein.");
12: scanf("%d", &alter);
13:
14: /* Löscht alle in stdin verbliebenen Zeichen. */
15: fflush(stdin);
16:
17: /* Fragt nach dem Namen des Anwenders. */
18: puts("Geben Sie Ihren Vornamen ein.");
19: scanf("%s", name);
20:
21: /* Gibt die Daten aus. */
22: printf("Ihr Alter ist %d.\n", alter);
23: printf("Ihr Name lautet %s.\n", name);
24:
25: return 0;
26: }

Geben Sie Ihr Alter ein.
29 und keinen Tag älter!
Geben Sie Ihren Vornamen ein.
Bradley
Ihr Alter ist 29.
Ihr Name lautet Bradley.

Wie Sie in Zeile 15 sehen können, wird hier die Funktion fflush() verwendet. Der Prototyp für fflush() lautet:

int fflush( FILE *stream);

stream ist der Stream, dessen Inhalt gelöscht werden soll. In Listing 13.6 wird der Standardeingabe-Stream stdin als Wert für stream übergeben.

Beispiele für scanf()

Der beste Weg, sich mit der Funktionsweise von scanf() vertraut zu machen, ist die praktische Anwendung. scanf() ist eine mächtige Funktion, die manchmal auch ziemlich verwirrend sein kann. Experimentieren Sie mit der Funktion und schauen Sie, was passiert. Listing 13.7 demonstriert einige der eher ungewöhnlichen Einsatzmöglichkeiten von scanf(). Sie sollten dieses Programm kompilieren und ausführen und dann zur Übung kleine Änderungen an den Formatstrings von scanf() vornehmen.

Listing 13.7: Einige Beispiele für den Einsatz von scanf() zur Tastatureingabe.

1:  /* Beispiele für den Einsatz von scanf(). */
2:
3: #include <stdio.h>
4:
5:
6:
7: int main(void)
8: {
9: int i1, i2;
10: long l1;
11:
12: double d1;
13: char puffer1[80], puffer2[80];
14:
15: /* Mit dem Modifizierer l long-Integer und double-Werte einlesen */
16:
17: puts("Geben Sie einen Integer und eine Fließkommazahl ein.");
18: scanf("%ld %lf", &l1, &d1);
19: printf("\nIhre Eingabe lautete %ld und %f.\n",l1, d1);
20: puts("Der Formatstring von scanf() verwendete den Modifizierer l,");
21: puts("um die Eingabe in long- und double-Werten zu speichern.\n");
22:
23: fflush(stdin);
24:
25: /* Aufsplittung der Eingabe durch Angabe von Feldlängen. */
26:
27: puts("Geben Sie einen Integer aus 5 Ziffern ein (z.B. 54321).");
28: scanf("%2d%3d", &i1, &i2);
29:
30: printf("\nIhre Eingabe lautete %d und %d.\n", i1, i2);
31: puts("Der Feldlängenspezifizierer in dem Formatstring von scanf()");
32: puts("splittete Ihre Eingabe in zwei Werte auf.\n");
33:
34: fflush(stdin);
35:
36: /* Verwendet ein ausgeschlossenes Leerzeichen, um eine */
37: /* Eingabezeile beim Leerzeichen in zwei Strings aufzuteilen. */
38:
39: puts("Geben Sie Vor- u. Nachnamen getrennt durch Leerzeichen ein.");
40: scanf("%[^ ]%s", puffer1, puffer2);
41: printf("\nIhr Vorname lautet %s\n", puffer1);
42: printf("Ihr Nachname lautet %s\n", puffer2);
43: puts("[^ ] in dem Formatstring von scanf() hat durch Ausschließen");
44: puts("des Leerzeichens die Aufsplittung der Eingabe bewirkt.");
45:
46: return 0;
47: }

Geben Sie einen Integer und eine Fließkommazahl ein.
123 45.6789

Ihre Eingabe lautete 123 und 45.678900.
Der Formatstring von scanf() verwendete den Modifizierer l,
um die Eingabe in long- und double-Werten zu speichern.

Geben Sie einen Integer aus 5 Ziffern ein (z.B. 54321).
54321

Ihre Eingabe lautete 54 und 321.
Der Feldlängenspezifizierer in dem Formatstring von scanf()
splittete Ihre Eingabe in zwei Werte auf.

Geben Sie Vor- u. Nachnamen getrennt durch Leerzeichen ein.
Gayle Johnson

Ihr Vorname lautet Gayle
Ihr Nachname lautet Johnson
[^ ] in dem Formatstring von scanf() hat durch Ausschließen
des Leerzeichens die Aufsplittung der Eingabe bewirkt.

Dieses Listing beginnt mit der Definition einiger Variablen für die Dateneingabe (Zeilen 9 bis 13). Anschließend führt Sie das Programm schrittweise durch die Eingabe verschiedener Daten. Die Zeilen 17 bis 21 fordern Sie auf, einen Integer vom Typ long und eine Fließkommazahl vom Typ double einzugeben, die dann ausgegeben werden. Zeile 23 ruft die Funktion fflush() auf, um unerwünschte Zeichen aus dem Standardeingabe-Stream zu löschen. Die Zeilen 27 und 28 lesen den nächsten Wert ein, einen Integer aus fünf Ziffern. Da in dem zugehörigen scanf()-Aufruf Längenspezifizierer verwendet wurden, wird der fünfteilige Integer in zwei Integer getrennt - einen, der aus zwei Ziffern besteht, und einen weiteren, der aus drei Ziffern besteht. Zeile 34 ruft erneut fflush() auf, um den Tastaturpuffer zu löschen. Das letzte Beispiel in den Zeilen 36 bis 44 verwendet ein Ausschlusszeichen. In Zeile 40 wird scanf() durch den Konvertierungsspezifizierer %[^ ] aufgefordert, einen String einzulesen, aber bei Leerzeichen anzuhalten. Dies stellt eine weitere Möglichkeit dar, die Eingabe aufzusplitten.

Nehmen Sie sich die Zeit, das Listing abzuwandeln und es mit anderen Eingaben auszuprobieren, und begutachten Sie die erzielten Ergebnisse.

Mit der Funktion scanf() kann man sich praktisch in jeder Situation, in der Benutzereingaben benötigt werden, behelfen. Insbesondere beim Einlesen von Zahlenwerten verwendet man scanf(), während man Strings einfacher mit fgets() einliest. Gelegentlich ist es aber auch lohnenswert, eigene Eingabefunktionen zu schreiben.

Was Sie tun sollten

Was nicht

Verwenden Sie den Längenspezifizierer, wenn Sie mit scanf() Strings einlesen. So verhindern Sie, dass mehr Zeichen eingelesen werden, als in den zur Verfügung stehenden Speicher passen.

Verwenden Sie die scanf()-Funktionen anstelle der fscanf()-Funktionen, wenn Sie mit der Standardeingabedatei (stdin) arbeiten.

Vermeiden Sie gets().

Vergessen Sie nicht, den Eingabe-Stream auf übrig gebliebene Zeichen zu checken.

Bildschirmausgabe

Die Funktionen für die Bildschirmausgabe fallen in die gleichen drei allgemeinen Kategorien wie die Eingabefunktionen: Zeichenausgabe, Zeilenausgabe und formatierte Ausgabe. Einige davon kennen Sie bereits von früheren Kapiteln. In diesem Abschnitt werden alle Funktionen ausführlich behandelt.

Zeichenausgabe mit putchar(), putc() und fputc()

Die Zeichenausgabefunktionen der C-Bibliothek schicken ein einzelnes Zeichen an einen Stream. Die Funktion putchar() sendet die Ausgabe an stdout (normalerweise der Bildschirm). Die Funktionen fputc() und putc() senden ihre Ausgabe an den Stream, der Ihnen als Argument übergeben wird.

Die Funktion putchar()

Der Prototyp für putchar(), der in stdio.h deklariert ist, lautet:

int putchar(int c);

Diese Funktion schreibt das Zeichen, das in c gespeichert ist, in den Standardausgabe- Stream stdout. Auch wenn der Prototyp ein Argument vom Typ int angibt, übergeben Sie putchar() ein Argument vom Typ char. Sie können ihr aber auch eines vom Typ int übergeben, solange der Wert des Arguments einem Zeichen entspricht (das heißt im Bereich von 0 bis 255 liegt). Die Funktion liefert das Zeichen zurück, das gerade geschrieben wurde, oder EOF, wenn ein Fehler aufgetreten ist.

Ein Beispiel für putchar() konnten Sie schon in Listing 13.2 sehen. Listing 13.8 gibt die Zeichen mit den ASCII-Werten zwischen 14 und 127 aus.

Listing 13.8: Die Funktion putchar().

1: /* Beispiel für putchar(). */
2:
3: #include <stdio.h>
4: int main(void)
5: {
6: int count;
7:
8: for (count = 14; count < 128; )
9: putchar(count++);
10:
11: return 0;
12: }

Sie können mit der putchar()-Funktion ebenso Strings ausgeben (Listing 13.9), auch wenn andere Funktionen für diesen Zweck besser geeignet sind.

Listing 13.9: Einen String mit putchar() ausgeben.

1: /* Mit putchar() Strings ausgeben. */
2:
3: #include <stdio.h>
4:
5: #define MAXSTRING 80
6:
7: char nachricht[] = "Ausgegeben mit putchar().";
8: int main(void)
9: {
10: int count;
11:
12: for (count = 0; count < MAXSTRING; count++)
13: {
14:
15: /* Sucht das Ende des Strings. Ist es gefunden, wird ein */
16: /* Neue-Zeile-Zeichen geschrieben und die Schleife verlassen. */
17:
18: if (nachricht[count] == '\0')
19: {
20: putchar('\n');
21: break;
22: }
23: else
24:
25: /* Wird kein Stringende gefunden, nächstes Zeichen schreiben. */
26:
27: putchar(nachricht[count]);
28: }
29: return 0;
30: }

Ausgegeben mit putchar().

Die Funktionen putc() und fputc()

Diese beiden Funktionen führen die gleiche Aufgabe aus - sie senden jeweils ein Zeichen an einen angegebenen Stream. putc() ist eine Makro-Implementierung von fputc(). Am Tag 20, »Compiler für Fortgeschrittene«, erfahren Sie mehr über Makros. Im Moment halten Sie sich am besten an fputc(). Ihr Prototyp lautet:

int fputc(int c, FILE *fp);

Der Parameter FILE *fp mag Sie vielleicht etwas verwirren. Über das Argument zu diesem Parameter übergeben Sie fputc() den Ausgabe-Stream (mehr dazu am Tag 15). Wenn Sie stdout als Stream übergeben, verhält sich fputc() genauso wie putchar(). Demzufolge sind die beiden folgenden Anweisungen bedeutungsgleich:

putchar('x');
fputc('x', stdout);

Stringausgabe mit puts() und fputs()

Ihre Programme werden wesentlich häufiger Strings als einfache Zeichen auf dem Bildschirm ausgeben. Strings können mit einer der Bibliotheksfunktionen puts() oder mit fputs() ausgeben werden. Die Funktion fputs() entspricht puts(), erlaubt aber die Angabe des Ausgabe-Streams. Der Prototyp für puts() lautet:

int puts(char *cp);

*cp ist ein Zeiger auf das erste Zeichen des Strings, den Sie ausgeben wollen. Die Funktion puts() gibt den ganzen String (ohne das abschließende Nullzeichen) aus und schließt die Ausgabe mit einem Neue-Zeile-Zeichen ab. Im Erfolgsfall liefert puts() einen positiven Wert zurück, im Falle eines Fehlers EOF. (Zur Erinnerung: EOF ist eine symbolische Konstante mit dem Wert -1, die in stdio.h definiert ist.)

Die puts()-Funktion kann für die Ausgabe eines beliebigen Strings benutzt werden, wie das Beispiel in Listing 13.10 zeigt.

Listing 13.10: Mit der Funktion puts() Strings ausgeben.

1: /* Beispiel für puts(). */
2:
3: #include <stdio.h>
4:
5: /* Deklariert und initialisiert ein Array von Zeigern. */
6:
7: char *nachrichten[5] = { "Dies", "ist", "eine", "kurze", "Nachricht." };
8:
9: int main(void)
10: {
11: int x;
12:
13: for (x=0; x<5; x++)
14: puts(nachrichten[x]);
15:
16: puts("Und dies ist das Ende!");
17:
18: return 0;
19: }

Dies
ist
eine
kurze
Nachricht.
Und dies ist das Ende!

In dem Listing wird ein Array von Zeigern deklariert - eine Konstruktion, die bisher noch nicht behandelt wurde, aber zu der wir morgen kommen werden. Die Zeilen 13 und 14 geben die Strings, die in dem nachrichten-Array gespeichert sind, der Reihe nach aus.

Formatierte Ausgabe mit printf() und fprintf()

Bis jetzt konnte man mit den Ausgabefunktionen nur Zeichen und Strings ausgeben. Wie aber steht es mit Zahlen? Um Zahlen auszugeben, müssen Sie die C- Bibliotheksfunktionen für die formatierte Ausgabe, printf() und fprintf(), verwenden. (Dass man mit diesen Funktionen natürlich auch Strings und Zeichen ausgeben kann, muss wohl nicht mehr erwähnt werden.) Offiziell haben Sie printf() bereits am Tag 6 kennen gelernt und seitdem fast in jedem Kapitel verwendet. Dieser Abschnitt soll die noch fehlenden Details nachtragen.

Die beiden Funktionen printf() und fprintf() sind praktisch identisch, bis auf den kleinen Unterschied, dass printf() die Ausgabe immer an stdout schickt, während man fprintf() den gewünschten Ausgabe-Stream als Argument übergibt. fprintf() wird in der Regel für das Schreiben in Dateien verwendet. Darauf werden wir in Kapitel 15 eingehen.

Die printf()-Funktion übernimmt eine beliebige Anzahl an Argumenten, mindestens jedoch eines. Dieses Argument ist der Formatstring, der printf() mitteilt, wie die Ausgabe formatiert werden soll. Die optionalen Argumente sind die Variablen und Ausdrücke, deren Werte Sie ausgeben wollen. Betrachten wir einige einfache Beispiele, die Ihnen ein Gefühl für die Verwendung von printf() geben sollen:

Betrachten wir den Formatstring von printf() etwas eingehender. Er kann folgende Elemente haben:

Der Formatstring des dritten Beispiels lautete: %d plus %d gleich %d. In diesem Fall sind die drei %d Konvertierungsbefehle und der Rest des Strings, einschließlich der Leerzeichen, literale Zeichen, die unverändert ausgegeben werden.

Jetzt sind wir soweit, dass wir den Konvertierungsbefehl zerlegen können. Die folgende Zeile gibt Ihnen eine Übersicht über die einzelnen Komponenten des Befehls, die anschließend erklärt werden. Die Komponenten in eckigen Klammern sind optional.

%[flag][feld_laenge][.[genauigkeit]][l]konvertierungszeichen

Dabei ist konvertierungszeichen der einzige erforderliche Teil eines Konvertierungsbefehls (abgesehen von %). Tabelle 13.5 bietet eine Übersicht über die Konvertierungszeichen und ihre Bedeutung.

Konvertierungszeichen

Bedeutung

d, i

Gibt einen vorzeichenbehafteten Integer in Dezimalschreibweise aus.

u

Gibt einen vorzeichenlosen Integer in Dezimalschreibweise aus.

o

Gibt einen Integer in vorzeichenloser Oktalschreibweise aus.

x, X

Gibt einen Integer in vorzeichenloser Hexadezimalschreibweise aus. Verwendet x für die Ausgabe in Kleinbuchstaben und X für Großbuchstaben.

c

Gibt ein einzelnes Zeichen aus (das Argument enthält den ASCII-Code des Zeichens).

e, E

Gibt einen float- oder double-Wert in wissenschaftlicher Notation aus (zum Beispiel wird 123.45 als 1.234500e+002 ausgegeben). Rechts des Dezimalpunktes werden sechs Ziffern angezeigt, es sei denn, es wird mit dem Spezifizierer f eine andere Genauigkeit angegeben. Verwenden Sie e oder E, um die Groß- und Kleinschreibung der Ausgabe zu steuern.

f

Gibt einen float- oder double-Wert in Dezimalschreibweise aus (zum Beispiel wird 123.45 als 123.450000 ausgegeben). Rechts des Dezimalpunktes werden sechs Ziffern angezeigt, es sei denn, es wird eine andere Genaugikeit angegeben.

g, G

Verwendet das Format e, E oder f. Das Format e oder E wird verwendet, wenn der Exponent kleiner als -3 oder größer als die Genauigkeit ist (standardmäßig 6). Ansonsten wird das Format f verwendet. Nullen am Ende werden abgeschnitten.

n

Nichts wird angezeigt. Das Argument, das einem n-Konvertierungsbefehl entspricht, ist ein Zeiger auf den Typ int. Die Funktion printf() weist dieser Variablen die Zahl der bis dahin ausgegebenen Zeichen zu.

s

Gibt einen String aus. Das Argument ist ein Zeiger auf char. Es wird eine Folge von Zeichen ausgegeben, bis ein Nullzeichen auftritt oder die von der Genauigkeit angegebene Zahl an Zeichen (standardmäßig 32767) ausgegeben wurde. Das abschließende Nullzeichen wird nicht ausgegeben.

%

Gibt das %-Zeichen aus.

Tabelle 13.5: Die Konvertierungszeichen von printf() und fprintf().

Vor das Konvertierungszeichen können Sie den Modifizierer l setzen. Wenn Sie den Modifizierer auf die Konvertierungszeichen o, u, x, X, i, d und b anwenden, gibt er an, dass das Argument statt vom Typ int vom Typ long ist. Für die Konvertierungszeichen e, E, f, g oder G gibt der Modifizierer an, dass das Argument vom Typ double ist. Vor allen anderen Konvertierungszeichen wird der l-Modifizierer ignoriert.

Der Genauigkeitsspezifizierer besteht aus einem Dezimalpunkt (.), der entweder allein steht oder von einer Zahl gefolgt wird. Ein Genauigkeitsspezifizierer kann nur zusammen mit den Konvertierungszeichen e, E, f, g, G und s verwendet werden. Er gibt die Anzahl der Ziffern an, die rechts vom Dezimalpunkt ausgegeben werden sollen. Zusammen mit s gibt der Genauigkeitsspezifizierer an, wie viele Zeichen ausgegeben werden sollen. Der Dezimalpunkt allein bedeutet eine Genauigkeit von 0.

Der Feldlängen-Spezifizierer legt die minimale Anzahl der auszugebenden Zeichen fest. Er kann folgende Formen annehmen:

Wenn keine Feldlänge angegeben wird oder die angegebene Feldlänge kürzer als die Ausgabe ist, wird das Ausgabefeld gerade so lang wie nötig.

Der letzte optionale Teil des Formatstrings von printf() ist das Flag, das direkt auf das %-Zeichen folgt. Es gibt genau vier Flags:

-

Bewirkt, dass die Ausgabe im Feld linksbündig anstatt rechtsbündig (der Standard) erfolgt.

+

Bewirkt, dass vorzeichenbehaftete Zahlen immer mit einem führenden + oder - ausgegeben werden.

' '

Ein Leerzeichen bewirkt, dass vor positiven Zahlen ein Leerzeichen ausgegeben wird.

#

Dieses Flag gilt nur für die Konvertierungszeichen x, X und o. Es gibt an, dass Zahlen ungleich Null mit einem führenden 0X oder 0x (für x und X) oder einer führenden 0 (für o) ausgegeben werden.

Bei Aufruf von printf() können Sie den Formatstring als Stringliteral in doppelten Anführungszeichen, aber auch als im RAM gespeicherten String mit abschließendem Nullzeichen übergeben. In letzterem Fall übergeben Sie printf() einfach einen Zeiger auf den String. So entspricht zum Beispiel die Anweisung

char *fmt = "Die Antwort lautet %f.";
printf(fmt, x);

der Anweisung

printf("Die Antwort lautet %f.", x);

Wie bereits am Tag 6 erklärt, kann der Formatstring von printf() Escape-Sequenzen enthalten. In Tabelle 13.6 sind die gängigsten Escape-Sequenzen aufgeführt. Wenn Sie zum Beispiel die Sequenz für »Neue Zeile« (\n) in den Formatstring mit aufnehmen, wird die nachfolgende Ausgabe auf dem Bildschirm in eine neue Zeile gesetzt.

Sequenz

Bedeutung

\a

Beep (akustisches Signal)

\b

Backspace

\n

Neue Zeile

\t

Horizontaler Tabulator

\\

Backslash

\?

Fragezeichen

\'

Einfaches Anführungszeichen

\"

Doppeltes Anführungszeichen

Tabelle 13.6: Die gängigsten Escape-Sequenzen.

Die Funktion printf() ist etwas kompliziert. Den Umgang mit printf() lernen Sie am besten, indem Sie Beispiele betrachten und dann selbst mit der Funktion experimentieren. In Listing 13.11 finden Sie viele Möglichkeiten für den Einsatz von printf().

Listing 13.11: Einige Möglichkeiten für den Einsatz von printf().

1: /* Beispiele für printf(). */
2:
3: #include <stdio.h>
4:
5: char *m1 = "Binaer";
6: char *m2 = "Dezimal";
7: char *m3 = "Oktal";
8: char *m4 = "Hexadezimal";
9:
10: int main(void)
11: {
12: float d1 = 10000.123;
13: int n, f;
14:
15:
16: puts("Eine Zahl mit unterschiedlichen Feldlängen ausgeben.\n");
17:
18: printf("%5f\n", d1);
19: printf("%10f\n", d1);
20: printf("%15f\n", d1);
21: printf("%20f\n", d1);
22: printf("%25f\n", d1);
23:
24: puts("\n Weiter mit der Eingabetaste...");
25: fflush(stdin);
26: getchar();
27:
28: puts("\nVerwendet den Feldlängenspezifizierer *, um");
29: puts("die Feldlänge aus der Argumentenliste zu übernehmen.\n");
30:
31: for (n=5;n<=25; n+=5)
32: printf("%*f\n", n, d1);
33:
34: puts("\n Weiter mit der Eingabetaste...");
35: fflush(stdin);
36: getchar();
37:
38: puts("\nNimmt führende Nullen mit auf.\n");
39:
40: printf("%05f\n", d1);
41: printf("%010f\n", d1);
42: printf("%015f\n", d1);
43: printf("%020f\n", d1);
44: printf("%025f\n", d1);
45:
46: puts("\n Weiter mit der Eingabetaste...");
47: fflush(stdin);
48: getchar();
49:
50: puts("\nAnzeige in oktaler, dezimaler und hexadezimaler Notation.");
51: puts("# stellt oktalen und HEX-Ausgaben 0 oder 0X voran.");
52: puts("- richtet Werte in Feldern linksbündig aus.");
53: puts("Zuerst Spaltenüberschriften ausgeben.\n");
54:
55: printf("%-15s%-15s%-15s", m2, m3, m4);
56:
57: for (n = 1;n< 20; n++)
58: printf("\n%-15d%-#15o%-#15X", n, n, n);
59:
60: puts("\n\n Weiter mit der Eingabetaste...");
61: fflush(stdin);
62: getchar();
63:
64: puts("\n\nMit dem Konvertierungsbefehl %n Zeichen zählen.\n");
65:
66: printf("%s%s%s%s%n", m1, m2, m3, m4, &n);
67:
68: printf("\n\nDer letzte printf()-Aufruf gab %d Zeichen aus.\n", n);
69:
70: return 0;
71: }

Eine Zahl mit unterschiedlichen Feldlängen ausgeben.

10000.123047
10000.123047
10000.123047
10000.123047
10000.123047

Weiter mit der Eingabetaste...


Verwendet den Feldlängenspezifizierer *, um
die Feldlänge aus der Argumentenliste zu übernehmen.

10000.123047
10000.123047
10000.123047
10000.123047
10000.123047

Weiter mit der Eingabetaste...


Nimmt führende Nullen mit auf.

10000.123047
10000.123047
00010000.123047
0000000010000.123047
000000000000010000.123047

Weiter mit der Eingabetaste...


Anzeige in oktaler, dezimaler und hexadezimaler Notation.
# stellt oktalen und HEX-Ausgaben 0 oder 0X voran.
- richtet Werte in Feldern linksbündig aus.
Zuerst Spaltenüberschriften ausgeben.

Dezimal Oktal Hexadezimal
1 01 0X1
2 02 0X2
3 03 0X3
4 04 0X4
5 05 0X5
6 06 0X6
7 07 0X7
8 010 0X8
9 011 0X9
10 012 0XA
11 013 0XB
12 014 0XC
13 015 0XD
14 016 0XE
15 017 0XF
16 020 0X10
17 021 0X11
18 022 0X12
19 023 0X13

Weiter mit der Eingabetaste...

Mit dem Konvertierungsbefehl %n Zeichen zählen.

BinaerDezimalOktalHexadezimal

Der letzte printf()-Aufruf gab 29 Zeichen aus.

Ein- und Ausgabe umleiten

Wenn Sie mit stdin und stdout arbeiten, können Sie von einem besonderen Feature des Betriebssystems, der so genannten Umleitung, Gebrauch machen. Die Umleitung ermöglicht Ihnen Folgendes:

Für die Umleitung müssen Sie keinen Code in Ihren Programmen aufsetzen. Sie geben die Umleitung in der Befehlszeile an, wenn Sie das Programm ausführen. Unter Linux (und den meisten anderen Unix-ähnlichen Betriebssystemen) lauten die Symbole für Dateiumleitung < und >. Für das Umleiten der Ausgabe in den stdin- Stream eines anderen Programms (Piping) wird das Symbol | verwendet. Doch betrachten wir zuerst die Umleitung für Dateien.

Erinnern Sie sich noch an Ihr erstes C-Programm, hallo.c? Wir benutzten damals die Bibliotheksfunktion printf(), um die Nachricht Hallo, Welt auf dem Bildschirm auszugeben. Jetzt wissen Sie, dass printf() die Ausgabe an stdout sendet, was bedeutet, dass die Ausgabe umgeleitet werden kann. Wenn Sie das Programm über die Befehlszeile starten, setzen Sie direkt hinter den Programmnamen das >-Symbol und den Namen des neuen Ziels:

hallo > ziel

Wenn Sie also hallo > hallo.txt eingeben, wird die Ausgabe in eine Datei namens hallo.txt geschrieben.

Seien Sie vorsichtig, wenn Sie die Ausgabe in eine Datei umleiten. Wenn die Datei bereits existiert, wird die alte Kopie gelöscht und durch eine neue Datei ersetzt. Existiert die Datei nicht, wird sie erzeugt. Für die Umleitung der Ausgabe in eine Datei können Sie auch das Symbol >> verwenden. In diesem Fall wird die Programmausgabe an die Zieldatei angehängt. Listing 13.12 zeigt ein Beispiel für die Umleitung.

Listing 13.12: Umleitung von Ein- und Ausgabe.

1: /* Beispiel für die Umleitung von stdin und stdout. */
2:
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: char puffer[80];
8:
9: fgets(puffer,80,stdin);
10: printf("Die Eingabe lautete: %s\n", puffer);
11: return 0;
12: }

Dieses Programm übernimmt von stdin eine Eingabezeile und sendet sie an stdout, wobei der Eingabezeile die Worte Die Eingabe lautete: vorangestellt werden. Führen Sie das Programm nach dem Kompilieren und Linken ohne Umleitung aus, indem Sie (in der Annahme, das Programm heißt list1312) in der Befehlszeile ./list1312 eingeben. Wenn Sie dann »Ich kann Linux mit C programmieren« eingeben, gibt das Programm Folgendes auf dem Bildschirm aus:

Die Eingabe lautete: Ich kann Linux mit C programmieren

Wenn Sie das Programm mit ./list1312 > test.txt starten und den gleichen Text eingeben, bleibt der Bildschirm leer. Statt dessen wird im aktuellen Verzeichnis eine Datei namens test.txt angelegt. Wenn Sie sich den Inhalt der Datei mit Hilfe des cat- Befehls anzeigen lassen,

cat test.txt

sehen Sie, dass die Datei die Zeile »Die Eingabe lautete: Ich kann Linux mit C programmieren« enthält.

Führen Sie das Programm noch einmal aus und leiten Sie die Ausgabe diesmal mit dem >>-Symbol an test.txt um. Statt dass die Datei ersetzt wird, wird die neue Ausgabe an das Ende von test.txt angehängt.

Eingaben umleiten

Kommen wir jetzt zu dem Umleiten von Eingaben. Dazu brauchen Sie zuerst eine Quelldatei. Verwenden Sie Ihren Editor, um eine Datei namens eingabe.txt zu erzeugen, die als einzige Zeile »William Shakespeare« enthalten soll. Starten Sie jetzt das Programm aus Listing 13.12 wie folgt:

./list1312 < eingabe.txt

Das Programm wartet nicht, bis Sie eine Eingabe über die Tastatur vornehmen, sondern zeigt direkt die folgende Nachricht auf dem Bildschirm an:

Die Eingabe lautete: William Shakespeare

Der Stream stdin wurde zu der Datei eingabe.txt umgeleitet, so dass der Aufruf von fgets() eine Textzeile aus der Datei statt von der Tastatur einliest.

Sie können Ein- und Ausgabe gleichzeitig umleiten. Versuchen Sie, das Programm mit dem folgenden Befehl auszuführen, um stdin zu der Datei eingabe.txt und stdout in die Datei muell.txt umzuleiten:

./list1312 < eingabe.txt > muell.txt

Die Umleitung von stdin und stdout kann in bestimmten Situationen sehr nützlich sein. Ein Sortierprogramm kann dank der Umleitung sowohl Tastatureingaben als auch den Inhalt einer Datei sortieren. Ein weiteres Beispiel wäre ein Mailinglisten- Programm, das seine Adressen alternativ auf den Bildschirm ausgeben, zur Etikettenerstellung an den Drucker schicken oder in einer Datei speichern kann.

Piping zwischen Programmen

Kommen wir jetzt zum Piping von Daten aus dem stdout-Stream eines Programms in den stdin-Stream eines anderen Programms. Wir bedienen uns dabei immer noch des Programms in Listing 13.12. Geben Sie folgende Befehlszeile ein:

./list1312 | ./list1312

Das Programm wartet auf Ihre Eingabe. Wenn Sie jetzt Hallo Ihr da! eingeben, erhalten Sie folgende Ausgabe:

Die Eingabe lautete: Die Eingabe lautete: Hallo Ihr da!.

Beachten Sie das zweimalige Auftreten von »Die Eingabe lautete:«. Folgendes ist passiert: Das erste Programm aus der Befehlszeile schickt den String »Die Eingabe lautete: Hallo Ihr da!« an seinen stdout-Stream, der mit dem stdin des zweiten Programms aus der Befehlszeile verknüpft ist. Das zweite Programm erhält den vollen String »Die Eingabe lautete: Hallo Ihr da!« als Eingabe und gibt deshalb »Die Eingabe lautete: » gefolgt von dem String aus, den es als Eingabe erhalten hat.

Pipes ermöglichen es Ihnen, Befehle und Programme miteinander zu verbinden, wobei jedes Programm aus seinem eigenen stdin-Stream liest und zu seinem eigenen stdout-Stream schreibt. Und nur die Ausgabe des letzten Programms wird dann tatsächlich auf dem Bildschirm ausgegeben.

Merken Sie sich, dass das Umleiten und Piping von stdin und stdout Features des Betriebssystems und nicht von C sind. Sie sind ein weiterer Beleg für die Flexibilität des Stream-Konzepts.

Einsatzmöglichkeiten von fprintf()

Wie bereits erwähnt, ist die Bibliotheksfunktion fprintf() - bis auf das zusätzliche Argument für den Ausgabe-Stream - identisch mit printf(). fprintf() wird hauptsächlich zusammen mit Dateien eingesetzt, worauf am Tag 15 noch näher eingegangen wird. Es gibt jedoch noch zwei andere Einsatzbereiche, die hier erläutert werden sollen.

stderr verwenden

Einer der vordefinierten Streams von C ist stderr (Standardfehlerausgabe). Die Fehlermeldungen eines Programms werden üblicherweise an den Stream stderr und nicht an stdout geschickt. Warum?

Wie Sie gelernt haben, kann die Ausgabe an stdout an ein anderes Ziel als den Bildschirm umgeleitet werden. Wenn stdout umgeleitet wird, fallen dem Anwender unter Umständen eventuell vorhandene Fehlermeldungen, die das Programm an stdout ausgibt, nicht auf. Unter Linux und anderen Unix-ähnlichen Systemen kann stderr ebenfalls umgeleitet werden, doch ist das nicht allgemein üblich. Indem sie die Fehlermeldungen an stderr umleiten, können Sie sicherstellen, dass der Anwender sie immer zu sehen bekommt. Dazu benötigen Sie fprintf():

fprintf(stderr, "Ein Fehler ist aufgetreten.");

Sie können eine Funktion schreiben, die Fehlermeldungen behandelt, und dann anstatt fprintf() diese Funktion aufrufen, wenn ein Fehler auftritt.

fehlermeldung("Ein Fehler ist aufgetreten.");

void fehlermeldung(char *msg)
{
fprintf(stderr, msg);
}

Wenn Sie Ihre eigene Funktion verwenden, anstatt fprintf() direkt aufzurufen, erzielen Sie eine höhere Flexibilität (einer der Vorteile der strukturierten Programmierung). Wenn Sie zum Beispiel unter bestimmten Umständen die Fehlermeldungen eines Programms auf dem Drucker ausgeben oder in eine Datei umleiten wollen, müssen Sie nur die Funktion fehlermeldung() ändern - und nicht unzählige fprintf()-Aufrufe.

Was Sie tun sollten

Was nicht

Verwenden Sie fprintf() für Programme, die Ausgaben an stdout, stderr oder beliebige andere Streams senden.

Verwenden Sie fprintf() mit stderr, um Fehlermeldungen auf dem Bildschirm auszugeben.

Schreiben Sie Funktionen wie fehlermeldung(), um Ihren Code strukturierter und leichter wartbar zu machen.

Verwenden Sie stderr nicht für Ausgaben, bei denen es sich weder um Fehlermeldungen noch um Warnungen handelt.

Zusammenfassung

Heute war ein langer Tag, vollgestopft mit Informationen zur Programmein- und - ausgabe. Sie haben gelernt, dass C Streams verwendet, die die gesamte Ein- und Ausgabe als eine Folge von Bytes behandeln. Drei dieser Streams sind in C vordefiniert:

stdin

Die Tastatur

stdout

Der Bildschirm

stderr

Der Bildschirm

Die Eingabe von der Tastatur kommt über den Stream stdin. Mit den Funktionen aus der C-Standardbibliothek können Sie die Tastatureingaben zeichenweise, zeilenweise oder als formatierte Zahlen und Strings übernehmen. Die Zeicheneingabe kann gepuffert oder ungepuffert sein und direkt auf dem Bildschirm angezeigt werden oder nicht.

Die Ausgabe auf dem Bildschirm erfolgt normalerweise über den Stream stdout. Wie die Eingabe kann auch die Ausgabe zeichenweise, zeilenweise oder als formatierte Zahlen und Strings erfolgen.

Wenn Sie stdin und stdout verwenden, können Sie die Programmein- und -ausgabe umleiten. Die Eingabe kann dann statt von der Tastatur von einer Datei kommen und die Ausgabe kann, statt auf dem Bildschirm zu erfolgen, an den Drucker oder an eine Datei gesendet werden.

Abschließend haben Sie erfahren, warum Fehlermeldungen statt an stdout an einen eigenen Stream namens stderr gesendet werden. Da stderr normalerweise mit dem Bildschirm verbunden ist, können Sie sicher sein, dass Sie alle Fehlermeldungen sehen, auch wenn die Programmausgabe umgeleitet wird.

Fragen und Antworten

Frage:
Was passiert, wenn ich versuche, eine Eingabe von einem Ausgabe-Stream zu erhalten?

Antwort:
Sie können ein solches C-Programm schreiben, aber es wird nicht funktionieren. Wenn Sie zum Beispiel versuchen, stderr mit fscanf() zu verwenden, lässt sich das Programm in eine ausführbare Datei kompilieren, aber stderr ist nicht fähig, Eingaben zu senden. Deshalb funktioniert Ihr Programm nicht wie vorgesehen.

Frage:
Was passiert, wenn ich einen der Standard-Streams umdefiniere?

Antwort:
Dies kann zu Problemen im weiteren Verlauf des Programms führen. Wenn Sie einen Stream umdefinieren, müssen Sie dies wieder rückgängig machen, wenn Sie ihn später im gleichen Programm noch einmal benötigen. Viele der in diesem Kapitel beschriebenen Funktionen verwenden die Standard- Streams. Sie alle verwenden die gleichen Streams. Wenn Sie also den Stream an einer Stelle im Programm ändern, bedeutet dies, dass Sie ihn auch für alle anderen Funktionen ändern. Probieren Sie es selbst einmal aus. Weisen Sie in einem der heute vorgestellten Programme stdout den Stream stderr zu und schauen Sie was passiert.

Frage:
Warum verwendet man nicht immer fprintf() anstelle von printf()? Oder fscanf() anstelle von scanf()?

Antwort:
Wenn Sie die Standardausgabe-/-eingabe-Streams verwenden, sollten Sie printf() und scanf() verwenden. Wenn Sie mit diesen einfacheren Funktionen arbeiten, brauchen Sie sich nicht um die zu verwendenden Streams zu kümmern.

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. Was ist ein Stream und wozu benötigt ein C-Programm Streams?
  2. Sind die nachfolgend aufgeführten Geräte Eingabe- oder Ausgabegeräte?
  3. a. Drucker
  4. b Tastatur
  5. c. Modem
  6. d. Monitor
  7. e. Laufwerk
  8. Geben Sie die drei vordefinierten Streams an sowie die Geräte, mit denen sie verbunden sind.
  9. Welche Streams verwenden die folgenden Funktionen?
  10. a. printf()
  11. b. puts()
  12. c. scanf()
  13. d. fgets()
  14. e. fprintf()
  15. Worin besteht der Unterschied zwischen gepufferter und ungepufferter Zeicheneingabe über stdin?
  16. Können Sie mit ungetc() mehr als ein Zeichen gleichzeitig »zurückstellen«?
  17. Wie markieren die Zeileneingabefunktionen von C das Ende der Zeile?
  18. Welche der folgenden Formatstrings enthalten gültige Typspezifizierer?
  19. a. "%d"
  20. b. "%4d"
  21. c. "%3i%c"
  22. d. "%q%d"
  23. e. "%%%I"
  24. f. "%9ld"
  25. Was ist der Unterschied zwischen stderr und stdout?

Übungen

  1. Schreiben Sie eine Anweisung, die »Hallo Welt« auf dem Bildschirm ausgibt.
  2. Realisieren Sie die Aufgabe aus Übung 1 mit zwei anderen C-Funktionen.
  3. Schreiben Sie eine Anweisung, die »Hallo Standardfehlerausgabe« an stderr ausgibt.
  4. Schreiben Sie eine Anweisung, die einen String von 30 Zeichen oder kürzer einliest. Trifft die Anweisung auf ein Sternchen, soll der String abgeschnitten werden.
  5. Schreiben Sie eine einfache Anweisung, die Folgendes ausgibt:
    Hans fragte, "Was ist ein Backslash?"
    Grete sagte, "Ein '\'-Zeichen"

Wegen der vielen denkbaren Lösungen gibt es zu den nachfolgenden Übungen keine Lösungen. Versuchen Sie, die Übungen trotzdem zu lösen.

  1. OHNE LÖSUNG: Schreiben Sie ein Programm, das via Umleitung den Inhalt einer Datei einliest und zählt, wie oft jeder Buchstabe in der Datei vorkommt. Als Ergebnis soll das Programm eine Statistik auf den Bildschirm ausgeben.


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


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