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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
-Stream. Andere Funktionen sind
ungepuffert, was bedeutet, dass die Zeichen direkt, d.h. sowie die entsprechende
Taste gedrückt wird, an stdin
geschickt werden.
stdout
aus. Da stdout
dem Bildschirm zugewiesen ist, erscheint die Eingabe dort
als Echo, und der Anwender kann sehen, was er eintippt. Andere
Eingabefunktionen machen das nicht: Das Zeichen wird nur an stdin
und nicht an
stdout
geschickt.
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()
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 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 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.
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()
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 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).
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 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:
%
), die mit den Nicht-Whitespaces aus der Eingabe abgeglichen
werden.
%
-Zeichen gefolgt
von einem Sonderzeichen bestehen. Für jede Variable gibt man im
Formatierungsstring einen eigenen Konvertierungsspezifizierer an.
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. Diescanf()
-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:
*
) folgt direkt auf das %-Zeichen.
Falls vorhanden, teilt dieses Zeichen scanf()
mit, dass die Eingabe entsprechend
dem aktuellen Konvertierungsspezifizierer durchzuführen, aber das Ergebnis zu
ignorieren ist (das heißt, die Eingabe wird nicht in der Variablen gespeichert).
stdin
von scanf()
in die aktuelle
Konvertierung einbezogen werden sollen. Wenn keine Feldlänge angegeben wird,
erstreckt sich das Eingabefeld bis zum nächsten Whitespace-Zeichen.
h
, l
oder L
lauten kann. Falls vorhanden, ändert der
Genauigkeitsmodifizierer die Bedeutung des darauf folgenden Typspezifizierers
(später mehr dazu).
%
) ist der Typspezifizierer. Der Typspezifizierer besteht aus einem
oder mehreren Zeichen und teilt scanf()
mit, wie die Eingabe zu interpretieren ist.
Eine Liste dieser Zeichen finden Sie in Tabelle 13.3. In der »Argument«-Spalte
sind die entsprechenden Typen der zugehörigen Variablen angegeben. So
benötigt zum Beispiel der Typspezifizierer d
ein int*-
Argument (ein Zeiger auf
den Typ int
).
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:
Tabelle 13.4: Genauigkeitsmodifizierer.
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:
12
14
gefolgt von der Eingabetaste ein. In
diesem Fall gibt es keine Probleme. Der Funktion scanf()
ist Genüge getan, und
es verbleiben keine Zeichen in stdin
.
12
gefolgt von
der Eingabetaste ein. In diesem Fall wartet scanf()
auf die noch fehlende Eingabe.
Erst nachdem diese Eingabe nachgeholt und von scanf()
empfangen wurde, wird
das Programm fortgesetzt. Es verbleiben keine Zeichen in stdin
.
12
14
16
gefolgt von der
Eingabetaste ein. In diesem Fall liest scanf()
12
und 14
ein und kehrt dann zurück.
Die übrig gebliebenen Zeichen, 1
und 6
, verbleiben in stdin
.
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.
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.
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.
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.
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.
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().
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);
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.
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:
printf("Hallo, Welt.");
gibt die Nachricht Hallo, Welt.
auf dem
Bildschirm aus. In diesem Beispiel wird nur ein Argument, der Formatstring,
verwendet. Der Formatstring enthält in diesem Fall lediglich einen einfachen
literalen String, der auf dem Bildschirm ausgegeben werden soll.
printf("%d", i);
gibt den Wert der Integer-Variablen i
auf dem
Bildschirm aus. Der Formatstring enthält nur den Formatspezifizierer %d
, der
printf()
mitteilt, eine Dezimalzahl auszugeben. Das zweite Argument, i
, ist der
Name der Variablen, deren Wert ausgegeben werden soll.
printf("%d plus %d gleich %d", a, b, a+b);
gibt 2 plus 3
gleich 5
auf dem Bildschirm aus (unter der Voraussetzung, dass a
und b
Integer-
Variablen mit den Werten 2
und 3
sind). Hier übernimmt printf()
vier
Argumente: einen Formatstring, der literalen Text und Formatspezifizierer
enthält, sowie zwei Variablen und einen Ausdruck, deren Werte ausgegeben
werden sollen.
Betrachten wir den Formatstring von printf()
etwas eingehender. Er kann folgende
Elemente haben:
printf()
angeben, wie
die zugehörigen Werte aus der Argumentenliste ausgegeben werden sollen. Ein
Konvertierungsbefehl besteht aus einem %
gefolgt von einem oder mehreren
Zeichen.
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.
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:
*
-Zeichen. Der Wert des nächsten Arguments (der vom Typ int
sein muss)
wird als Feldlänge verwendet. Wenn zum Beispiel w
vom Typ int
mit einem Wert
von 10
ist, dann gibt die Anweisung printf("%*d", w, a);
den Wert von a
mit
einer Feldlänge von 10
aus.
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:
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);
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.
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.
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:
stdout
gesendete Ausgabe statt zum Bildschirm zu einer Datei oder dem
stdin
-Stream eines anderen Programms zu schicken
stdin
von einer Datei oder dem stdout
-Stream eines
anderen Programms anstatt von der Tastatur einzulesen
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.
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.
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
undstdout
Features des Betriebssystems und nicht von C sind. Sie sind ein weiterer Beleg für die Flexibilität des Stream-Konzepts.
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.
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.
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:
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.
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.
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.
printf()
puts()
scanf()
fgets()
fprintf()
stdin
?
ungetc()
mehr als ein Zeichen gleichzeitig »zurückstellen«?
"%d"
"%4d"
"%3i%c"
"%q%d"
"%%%I"
"%9ld"
stderr
und stdout
?
Hallo Welt
« auf dem Bildschirm ausgibt.
Hallo Standardfehlerausgabe
« an stderr
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.