14.7    Funktionen zur formatierten Ein-/Ausgabe

Zum formatierten Einlesen und Schreiben von Daten stehen Ihnen verschiedene scanf()- und printf()-Funktionen in der Header-Datei stdio.h zur Verfügung. Beide Funktionsfamilien bieten sogenannte Umwandlungsvorgaben des Format-Strings an, mit denen Sie das Datenformat steuern können.

14.7.1    Funktionen zur formatierten Ausgabe

Die einfache printf()-Funktion haben Sie ja schon des Öfteren in diesem Buch verwendet. Neben dieser Version gibt es noch einige andere Versionen, die alle auf recht ähnliche Weise den Format-String verwenden können. Folgende printf()-Versionen stehen Ihnen zur Verfügung:

int printf( const char* restrict format, ... );

Die grundlegende printf()-Funktion schreibt den String format auf die Standardausgabe stdout. Die Version mit Stream hat folgende Syntax:

int fprintf( FILE* restrict fp,
const char* restrict format, ... );

Damit schreiben Sie den String format auf den Ausgabe-Stream fp. printf(format) entspricht fprintf (stdout, format). Es kann sehr hilfreich sein, etwas formatiert in eine Datei zu schreiben. Des Weiteren gibt es auch zwei String-Versionen. Die Syntax dazu ist:

int sprintf( char * restrict buf,
const char * restrict format, ... );
int snprintf( char * restrict buf,
size_t n,
const char * restrict format, ... );

Hiermit schreiben Sie format in einen String mit der Anfangsadresse von buf. Die Version snprintf() begrenzt die Zeichen, die in buf geschrieben werden, zudem noch auf n Bytes. Dies stellt also die sicherere Alternative gegenüber sprintf() dar. Des Weiteren gibt es noch Versionen all der eben erwähnten printf()-Varianten wie vprintf(), vfprintf(), vsprintf() und vsnprintf() mit dem Präfix v für Parameter aus einer variablen Parameterliste, worauf in diesem Buch allerdings nicht näher eingegangen wird.

Die drei Punkte von printf()

Die drei Punkte in den printf()-Anweisungen (auch Ellipse genannt) stehen für weitere optionale Argumente, die hier neben dem festen Argument format verwendet werden können. Der Trick ist hier, dass Ellipsen (da sie ja keine konkrete Typangabe besitzen) von Typ void* sind und daher nicht auf Korrektheit geprüft werden. Deshalb kann printf() zusammen mit den Formatbezeichnern auch beliebige Parameter entgegennehmen. Es gibt in C spezielle Funktionsbibliotheken, mit denen variable Argumentlisten verarbeitet werden können. Dies ist aber nicht Bestandteil eines Grundkurses.

14.7.2    Umwandlungsvorgaben für die printf-Familie

Bei den Datentypen haben Sie die Umwandlungsvorgaben zu den einzelnen Typen näher kennengelernt, die mit dem Zeichen % beginnen und mit einem Buchstaben, dem Konvertierungs-Spezifizierer, enden. Sie beziehen sich dann auf das nachfolgende Argument. %d steht beispielsweise für den Spezifizierer des Integer-Wertes (int). Folglich wird in der Argumentenliste auch ein solcher Typ zur Konvertierung im Format-String passen. Ein Beispiel wäre:

int val = 12345;
char str[] = "Hallo Welt";
printf("Der Wert ist %d, und der String lautet %s", val, str);

Neben den einfachen Umwandlungszeichen für den entsprechenden Typ zur Konvertierung im Format-String gibt es noch einige Möglichkeiten mehr, um den Format-String zu formatieren. Folgende Syntax soll hier für die anschließende Beschreibung verwendet werden:

%[F][W][G][L]U

Die einzelnen Teile in den eckigen Klammern können optional zu U verwendet werden. Die Bedeutung der einzelnen Buchstaben ist:

Regel für eine Konvertierungs-Spezifikation

Die Syntax einer Konvertierungs-Spezifikation, die mit dem Zeichen % beginnt, endet immer mit einem Konvertierungs-Spezifizierer, also dem Buchstaben (Umwandlungszeichen) für den entsprechenden Typ.

14.7.3    Weite und Feldbreite

Die wohl am häufigsten verwendete Konvertierungs-Spezifikation dürfte die Feldbreite sein. Sie kann bei jedem Umwandlungstyp verwendet werden. Damit können Sie festlegen, wie viele Zeichen zur Ausgabe mindestens verwendet werden dürfen. Zur Angabe der Feldbreite wird entweder eine Ganzzahl oder ein Stern (*) verwendet. Mit dem Sternchen können Sie die Feldbreite variabel halten und müssen dafür ein zusätzliches Argument in der Argumentenliste vom Typ int angeben, dessen Wert als Feldbreite verwendet wird.

Standardmäßig erfolgt die Ausgabe rechtsbündig, wobei eine zu große Feldbreite mit Leerzeichen auf der linken Seite aufgefüllt wird. Für eine linksbündige Ausrichtung setzen Sie vor der Feldbreite das Minuszeichen (ein Flag). Wenn Sie eine zu kleine Feldbreite angeben, bewirkt dies bei der Ausgabe nicht, dass die Zahlen beschnitten bzw. weniger Zeichen ausgegeben werden. Sie werden dennoch komplett ausgegeben, d. h., die Angabe wird ignoriert. Hierzu folgt ein einfaches Listing, mit dem die Wirkung der Feldbreite demonstriert wird:

00  // Kapitel14/feldbreite.c
01 #include <stdio.h>
02 #include <stdlib.h>

03 int main (void) {
04 char text[] = "Tiefstand";
05 int breite = 20;
06 printf("|01234567890123456789|\n");
07 printf("|%s|\n", text);
08 printf("|%20s|\n", text);
09 printf("|%-20s|\n", text);
10 printf("|%20s|\n", text+4); // Textanfang um 4 Zeichen nach vorne
11 printf("|%-20s|\n", text+4);
12 printf("|%*s|\n", breite, text);
13 return EXIT_SUCCESS;
14 }

Listing 14.5    Das Listing zeigt, wie Sie mit dem Feldbreite-Formatdeskriptor den Ausgabetext in der Konsole ausrichten können.

Das Programm gibt bei der Ausführung Folgendes aus:

|01234567890123456789|
|Tiefstand|
| Tiefstand|
|Tiefstand |
| stand|
|stand |
| Tiefstand|

14.7.4    Flags

Die sogenannten Flags stehen immer unmittelbar nach dem %-Zeichen. Mit den Flags kann ein bestimmtes Verhalten ein- oder ausgeschaltet werden. Stellen Sie sich ein Flag ruhig als einen Schalter vor, der den Wert 1 hat, wenn er eingeschaltet ist. Wenn es sinnvoll ist, können Sie durchaus mehrere Flags gleichzeitig verwenden. In Tabelle 14.3 finden Sie die Flags und deren Bedeutung aufgelistet.

Flag

Beschreibung

-

linksbündig justieren

+

Ausgabe des Vorzeichens. + wird bei positiven Werten vorangestellt, – bei negativen Werten.

Leerzeichen

Ist ein Argument kein Vorzeichen, wird ein Leerzeichen ausgegeben.

0

Bei numerischer Ausgabe wird mit Nullen bis zur angegebenen Feldbreite gefüllt.

#

Die Wirkung des Zeichens # hängt vom verwendeten Format ab. Wenn Sie z. B. einen Wert über %x als Hexadezimal ausgeben, wird bei %#x dem Wert ein 0x vorangestellt. Bei e, E oder f wird der Wert mit einem Dezimalpunkt ausgegeben, auch wenn keine Nachkommastelle existiert.

Tabelle 14.3    Flags für die Formatierungsanweisung

Zum besseren Verständnis der einzelnen Flags soll auch hierzu wieder ein einfaches Listing mit deren Anwendung gezeigt werden:

00  // Kapitel14/printf_flags_1.c
01 #include <stdio.h>
02 #include <stdlib.h>

03 int main (void) {
04 int val = 123456789;
05 printf("|012345678901|\n");
06 printf("|%d|\n", val);
07 printf("|%+12d|\n", val);
08 printf("|%-12d|\n", val);
09 printf("|%012d|\n", val);
10 printf("|%-#12o|\n", val);
11 printf("|%#x|\n", val);
12 printf("|%#012X|\n", val);
13 return EXIT_SUCCESS;
14 }

Listing 14.6    printf_flags.c zeigt, wie Sie in printf() Flags einsetzen können.

Das Programm gibt bei der Ausführung Folgendes aus:

|012345678901|
|123456789|
| +123456789|
|123456789 |
|000123456789|
|0726746425 |
|0x75bcd15|
|0X00075BCD15|

14.7.5    Genauigkeitsangaben für Zahlen bei printf()

Geben Sie einen Punkt an, gefolgt von einer Ganzzahl, können Sie die Genauigkeitsangabe der Nachkommastelle von Gleitpunktzahlen beeinflussen oder auch einen String »beschneiden«. Natürlich wird hierbei nicht der Wert selbst verändert, sondern die Angaben beziehen sich immer nur auf die formatierte Ausgabe. Bei Ganzzahlen und Strings wirkt sich eine kleinere Genauigkeitsangabe nicht auf die Ausgabe aus. Hierzu folgt wieder ein kleines Beispiel, das Ihnen die Genauigkeitsangabe etwas näherbringt:

00  // Kapitel14/printf_flags_2.c
01 #include <stdio.h>
02 #include <stdlib.h>

03 int main (void) {
04 float fval = 3.14132;
05 char text[] = "Tiefstand";
06 printf("|%08.2f|\n", fval);
07 printf("|%-8.0f|\n", fval);
08 printf("|%8.4s|\n", text);
09 printf("|%-8.4s|\n", text);
10 return EXIT_SUCCESS;
11 }

Das Programm gibt bei der Ausführung Folgendes aus:

|00003.14|
|3 |
| Tief|
|Tief |

Umwandlungszeichen

Zwar haben Sie die Umwandlungszeichen bereits mit den Datentypen näher kennengelernt, aber trotzdem sollen sie hier zur besseren Übersicht nochmals alle aufgelistet werden.

Zeichen

Typ

Bedeutung

%c

char, int

Einzelnes Zeichen. Für die numerische Ausgabe von signed char werden %hhi und unsigned char %hhu verwendet.

%lc

wchar_t

einzelnes Breitzeichen bei entsprechender w-Funktion

%s

Zeiger auf char

ein String (char-Array)

%p

Ausgabe des Zeigerwertes (Adresse)

%d, %i

int

vorzeichenbehaftete dezimale Ganzzahl

%u

unsigned int

vorzeichenlose Ganzzahl

%o

unsigned int

oktale Darstellung einer vorzeichenlosen Ganzzahl

%x

unsigned int

hexadezimale Darstellung einer vorzeichenlosen Ganzzahl mit den Buchstaben a, b, c, d, e, f

%X

unsigned int

hexadezimale Darstellung einer vorzeichenlosen Ganzzahl mit den Buchstaben A, B, C, D, E, F

%lld, %lli

long long

vorzeichenbehaftete Ganzzahl

%llu

unsigned  long long

vorzeichenlose Ganzzahl

%llx, %llX

unsigned  long long

hexadezimale Darstellung einer vorzeichenlosen Ganzzahl mit den Buchstaben a, b, c, d, e, f bzw. A, B, C, D, E, F

%f, %lf (für scanf)

double

dezimale Gleitpunktzahl in Form von dd.dddd

%e, %E

double

exponentiale Darstellung der Gleitpunktzahl in Form von d.dde+-dd bzw. d.ddE+-dd. Der Exponent enthält mindestens zwei Ziffern.

%g, %G

double

Gleitpunkt- oder Exponentialdarstellung, abhängig davon, was kürzer ist.

%a, %A

double

Exponentialdarstellung der Gleitpunktzahl in hexadezimaler Form.

%Lf

long double

Gleitpunktdarstellung, die für Gleitpunktzahlen mit einer erweiterten Genauigkeit verwendet wird.

Tabelle 14.4    Umwandlungszeichen für die einzelnen Typen

Neben den Umwandlungsvorgaben gibt es sogenannte Argumenttyp-Modifikationen. Folgende Typen gibt es, und folgende Auswirkungen haben sie auf die damit verwendeten Umwandlungszeichen:

Modifikation

Auswirkung

h

Die Umwandlungszeichen d, i, o, u, x, X werden als short int-bzw. unsigned short int-Wert behandelt (beispielsweise %hd).

l

Die Umwandlungszeichen d, i, o, u, x, X werden als long int- bzw. unsigned long int-Wert behandelt. Wird hingegen e, f oder g verwendet, werden die Umwandlungszeichen als double-Wert behandelt (beispielsweise %lf).

ll

Die Umwandlungszeichen d, i, o, u, x, X werden als long long int- bzw. unsigned long long int-Wert behandelt (seit C99).

L

Die Umwandlungszeichen e, E, f, g, G werden als long double-Wert behandelt. Die Umwandlungszeichen d, i, o, u, x, X werden hingegen als long long-Wert behandelt (beispielsweise %Ld).

hh

Wie h, nur dass die Umwandlungszeichen d, i, o, u, x, X als signed char- bzw. unsigned char-Wert behandelt werden (beispielsweise %hhd) (seit C99).

j

Die Umwandlungszeichen d, i, o, u, x, X werden als intmax_t- bzw. uintmax_t-Wert behandelt (seit C99).

t

Die Umwandlungszeichen d, i, o, u, x, X werden als ptrdiff_t-Wert behandelt (seit C99).

z

Die Umwandlungszeichen d, i, o, u, x, X werden als size_t-Wert behandelt (seit C99).

Tabelle 14.5    Argumenttyp-Modifikationen

14.7.6    Funktionen für die formatierte Eingabe

Wie bei der printf()-Familie für die Ausgabe, gibt es auch bei der scanf()-Familie für die Eingabe mehrere Funktionen. Alle Funktionen können Sie auf eine recht ähnlich Weise verwenden, wie Sie es von scanf() her bereits kennen. Nur die Angaben der Quelle und der Argumentübergabe sind dabei anders. Es folgt nun ein Überblick über die einzelnen scanf()-Varianten:

int scanf( const char* restrict format, ... );

Damit lesen Sie Daten von der Standardeingabe stdin formatiert ein. Natürlich gibt es wieder ein Stream-orientiertes Gegenstück. Die Syntax hierzu lautet:

int fscanf( FILE* restrict fp,
const char * restrict format, ... );

Die Funktion entspricht der einfachen scanf()-Funktion, nur wird hierbei vom Eingabe-Stream fp eingelesen. fscanf(stdin, format) hat somit denselben Effekt wie die einfache scanf()-Funktion. Auch eine String-orientierte Version ist vorhanden. Die Syntax hierzu:

int sscanf( const char * restrict src,
const char * restrict format, ... );

Damit erfolgt die Eingabe eines Strings. Es stehen Ihnen außerdem die Versionen vscanf(), vfscanf() und vsscanf() zur Verfügung, bei denen die Eingabe aus stdin(), einer Datei bzw. aus einem String kommt und für Parameter aus einer variablen Parameterliste verwendet wird. Auch hierauf wird nicht mehr näher eingegangen.

14.7.7    Umwandlungsvorgaben für die scanf()-Familie

Die gängigen Umwandlungszeichen zu scanf() finden Sie in Tabelle 14.4 im vorangehenden Abschnitt zur printf()-Familie. Zwar sind die Umwandlungszeichen von printf() und scanf() recht ähnlich, aber nicht (!) identisch. Sie müssen beispielsweise darauf achten, dass Sie für ein double die Umwandlungsvorgabe %lf bei scanf() verwenden. Bei printf() wird hingegen %lf nicht immer unterstützt.

Sie können den Format-String mit unterschiedlichen scanf()-Funktionen formatieren. Es gilt folgende allgemeine Syntax:

%[W][L][S]U

Die einzelnen Teile in den eckigen Klammern können optional zu U verwendet werden. Die Bedeutung der einzelnen Buchstaben ist:

Die Konvertierungs-Spezifikationen von W (für Weite, Feldbreite), L (für Längenangabe) und U (für Umwandlungszeichen) haben Sie bereits ausführlich in Abschnitt 14.7.2, »Umwandlungsvorgaben für die printf-Familie«, kennengelernt. Die Bedeutung ist meistens recht ähnlich, nur bezieht sich diese hier auf die Eingabe statt auf die Ausgabe.

14.7.8    Suchmengenkonvertierung

Zusätzlich bieten die scanf-Funktionen beim Einlesen von Strings sogenannte Suchmengenkonvertierungen an. Sie können anstelle des Umwandlungszeichens s (für Strings, char-Arrays) verwendet werden.

Suche

Es wird eingelesen, …

%[bezeichner]

… bis ein Zeichen vorkommt, das nicht in der Liste bezeichner steht.

%[^bezeichner]

… bis ein Zeichen vorkommt, das in der Liste bezeichner steht.

Tabelle 14.6    Suchmengenkonvertierung für die scanf-Funktionen

Es folgt ein kurzer Codeausschnitt hierzu:

01  char str[255], str2[255];
02 printf("Nur Zahlen eingeben : ");
03 if( scanf("%254[0-9]", str) != 1 ) {
04 fprintf(stderr, "Fehler bei der Eingabe\n");
05 return EXIT_FAILURE;
06 }
07 dump_buffer(stdin);
08 printf("Keine Zahlen eingeben : ");
09 if( scanf("%254[^0-9]", str2) != 1 ) {
10 fprintf(stderr, "Fehler bei der Eingabe\n");
11 return EXIT_FAILURE;
12 }

Wenn Sie in Zeile (03) etwas eingeben, werden so lange Zeichen eingelesen, bis zum ersten Mal ein Zeichen etwas anderes ist als eine Ziffer von 0 bis 9. Verwenden Sie denselben Ausdruck zusammen mit dem Caret-Zeichen (^), werden umgekehrt so lange Zeichen eingelesen, bis ein Zeichen zwischen 0 bis 9 liegt (siehe Zeile (09)). Wenn Sie das letzte Beispiel ausprobieren, werden Sie feststellen, dass scanf() in Zeile (09) nicht wie üblich beim Auftreten eines Whitespace-Zeichens (also Leerzeichen, Tabs oder Zeilenumbrüchen) abgebrochen wird. Zusätzlich wurde hier auch die Feldbreite mit 254 in den Zeilen (03) und (09) verwendet, damit kein Pufferüberlauf stattfindet. Dabei muss allerdings angemerkt werden, dass sich die weiteren Zeichen darüber hinaus im Eingabepuffer des Programms befinden, wenn mehr als 254 Zeichen eingegeben wurden. Dies sollten Sie wissen, wenn Sie beispielsweise vorhaben sollten, gleich darauf ein weiteres scanf() aufzurufen.

Weitere mögliche Beispiele zur Suchmengenkonvertierung:

%[A-Z]    // Großbuchstaben von A bis Z
%[a-fm-z] // Kleinbuchstaben a bis f und m bis z
%[a-fA-F] // Groß- und Kleinbuchstaben a-f A-F (Hexadezimal)

Bei der Verwendung eines Bindestrichs müssen Sie darauf achten, dass die Zeichen lexikalisch nach der ASCII-Tabelle verwendet werden. Es kann also nicht C-A, sondern es muss immer A-C verwendet werden. Natürlich setzt die Verwendung von A-Z auch wieder voraus, dass die ASCII-Code-Tabelle für die Kodierung von Zeichen verwendet wird, was der Standard ja nicht vorgibt.