5.2. Formatierte Ein- und Ausgabe

Die Funktion printf()

Die Funktion printf() wurde bisher schon mehrmals verwendet, ohne daß ihre genaue Verwendungsweise erklärt wurde. Die allgemeine Form von printf() ist:


printf( format, argument1, argument2, ... )

Der Rückgabewert von printf() ist vom Typ integer und enthält die Anzahl der Zeichen, die ausgegeben wurden; beim Auftreten eines Fehlers gibt printf() eine negative Zahl zurück.

Als Argumente akzeptiert printf() alles, was auszugebende Werte liefert: Variablen, Konstanten, Funktionsaufrufe und beliebige Ausdrücke. Unter der Kontrolle der Zeichenkette "format" nimmt printf() die Umwandlung der Argumente vor und gibt sie formatiert auf dem Standard-Ausgabegerät (Bildschirm) aus.

Die Zeichenkette "format" kennt zwei verschiedene Objekte: gewöhnliche Zeichen, die einfach auf den Bildschirm ausgegeben werden, und Umwandlungsangaben, die für die Umwandlung und Formatierung der Argumente zuständig sind. Umwandlungsangaben beginnen immer mit einem Prozentzeichen (%) und enden mit einem Umwandlungszeichen. Die einfachste Form des Format-Strings enthält keine Umwandlungsangaben und nur Text, der gedruckt werden soll, z.B.


printf("Hello world\n");

Enthält die Format-Zeichenkette Umwandlungsangaben, dann muß für jede dieser Angaben genau ein Argument zur Verfügung stehen, auf das sich die Umwandlung bezieht. Die Zuordnung der Formatierungs-Information zu den Argumenten ist dabei sehr einfach: Die erste Umwandlungsangabe bezieht sich auf das erste Argument, die zweite auf das zweite usw. Das folgende Beispiel verdeutlicht den Gebrauch von Umwandlungsangaben:


/************************************************
 * Beispiel für die Verwendung von printf()     *
 ************************************************/

#include <stdio.h>


int main(void){

char cProgC = 'C';				/* Initialisierung mit dem Wert 'C' */
int iAnzahlZeichen;				/* Anzahl gedruckter Zeichen */
int iErste = 1;

	 iAnzahlZeichen = printf("%c ist meine %d."
           Programmiersprache\n",
		 cProgC,
		 iErste);
/* In der Variable iAnzahlZeichen wird der Rückgabewert
 * von printf abgelegt (die Anzahl der gedruckten Zeichen)
 */

	 printf("printf hat %d Zeichen gedruckt\n",
	 	iAnzahlZeichen);

return 0;
}

Der erste Aufruf von printf() liefert als Rückgabewert 34, da der Zeilenvorschub selbstverständlich mitgezählt wird. Die Umwandlungsangabe %c veranlaßt die Darstellung des ersten Arguments, nämlich cProgC, als Character; %d (für vorzeichenbehaftete, dezimale Integer) bezieht sich auf iErste. Es erhebt sich natürlich die Frage, was printf() großartig umwandeln soll, wenn der ohnehin schon char-Variablen cProgC die Umwandlungsangabe %c zugeordnet wird. Dieses Zusammentreffen ist allerdings nicht notwendig: printf() ist nämlich in der Lage, bei Bedarf auch den Zahlenwert von char-Variablen auszugeben.

Diese Tatsache macht sich das folgende asciival.c zunutze, um den ASCII-Wert eines beliebigen Zeichens auszugeben:


/*************************************************
 *                  asciival.c                   *
 * Gibt den ASCII-Wert eines beliebigen Zeichens *
 * in dezimaler und hexadezimaler Form aus       *
 *************************************************/

#include <stdio.h>


int main(void){

char cKeyPressed;				/* speichert eingegebenes Zeichen */

	printf("Für welches Zeichen soll der ASCII-Wert"

		 "ermittelt werden ?\n");

	cKeyPressed = getchar();
	printf("Das Zeichen %c hat den ASCII-Wert:\n"
		"\tdezimal: %d\t\thexadezimal: %x\n",
		 cKeyPressed,
		 cKeyPressed,
		 cKeyPressed);

return 0;
}

Die Variable cKeyPressed wird dreimal in jeweils verschiedener Form ausgegeben: als character, als dezimaler und dank %x, als hexadezimaler Wert. Das mehrmals auftretende \t setzt bekanntlich Tabulatoren. Neben den bisher verwendeten verfügt C noch über eine Reihe zusätzlicher Umwandlungszeichen. Die folgende Tabelle enthält eine vollständige Aufzählung aller zulässiger Umwandlungszeichen.

Umwandlungsangabe Format
%c Character
%d Vorzeichenbehaftete dezimale Integerzahl
%i Vorzeichenbehaftete dezimale Integerzahl
%u Vorzeichenlose dezimale Zahl
%x Vorzeichenlose Hexadezimalzahl (Kleinschreibung)
%X Vorzeichenlose Hexadezimalzahl (Großschreibung)
%o Vorzeichenlose Oktalzahl
%s Zeichenkette
%f Dezimale Fließkommazahl
%e Exponentialschreibweise (mit kleinem e)
%E Exponentialschreibweise (mit großem E)
%g benutzt %e oder %f, je nachdem was kürzer ist.
%G benutzt %E oder %f, je nachdem was kürzer ist.
%p Pointer, stellt Speicheradressen im computerspezifischen Format dar.
%n Anzahl der bisher geschriebenen Zeichen
%% gibt ein Prozent-Zeichen aus.

Die Umwandlungsangaben sind nicht auf die hier aufgeführten Kombinationen aus Prozentzeichen und Umwandlungszeichen begrenzt; es besteht darüber hinaus die Möglichkeit, diese Angaben zu modifizieren. Solche modifizierende Angaben müssen zwischen % und dem Umwandlungszeichen eingefügt werden. Folgende Zeichen stehen für diesen Zweck zur Verfügung:

Folgendes Beispiel demonstriert die Verwendung verschiedener Umwandlungsangaben in Kombination mit diversen Modifizierern:


/************************************************
 * Weiteres Beispiel für die Verwendung von printf()     *
 ************************************************/

#include <stdio.h>


int main(void){

float fFloat = 32.7689;
int iInteger1 = 345, iInteger2 = 67;

	 printf("Die Summe von %d und %d ist %d\n\n",
	 	iInteger1,
		iInteger2,
		iInteger1 + iInteger2);
	/* printf() akzeptiert arithmetische Ausdrücke als
	 * Argumente
	 */

	printf("Feldbreite von 6 bewirkt bei der gleichen "

		"Addition folgende Ausgabe:\n"
		"Die Summe von %d und %d ist %6d\n\n",
		iInteger1,
		iInteger2,
		iInteger1 + iInteger2);

	 printf("Feldbreite von 1 ist zu schmal für das "
		"Resultat und wird daher überschritten:\n"
		"Die Summe von %d und %d ist %1d\n\n",
		iInteger1,
		iInteger2,
		iInteger1 + iInteger2);

	printf("Keine Festlegung der Genauigkeit:\n"
		"%d minus %f gibt %f\n\n",
		iInteger1,
		fFloat,
	 	iInteger1 - fFloat);
	/* ohne Präzisionsangabe werden Fließkommazahlen
	 * standardmaessig mit 6 Nachkommastellen dargestellt.
	 */

	printf("Reduktion der Genauigkeit führt zu Rundung\n"

		"%d minus %.3f gibt %5.2f\n\n",
		iInteger1,
		fFloat,
		iInteger1 - fFloat);
	/* Wird die Genauigkeit auf 2 Nachkommastellen
	 * reduziert, dann nimmt printf() eine Rundung vor
	 */

	 printf("Formatierung mit dem Minuszeichen:\n"
	 	"Die Summe von %d und %d ist:"
		"\n%-6d linksbündig (mit Minuszeichen)"
		"\n%6d rechtsbündig (ohne Minuszeichen)\n\n",
	 	iInteger1,
		iInteger2,
		iInteger1 + iInteger2,
		iInteger1 + iInteger2);

	printf("Beispiel für die Verwendung von %%n:\n"
		"Bisher wurden von printf() %ngedruckt: ",
		&iInteger1);
	printf("%d Zeichen\n", iInteger1);
	/* Die Bedeutung des Adreßoperators '&' wird
	 * anschließend bei der Besprechung von scanf()
	 * behandelt
	 */
return 0;
}

Das Programm produziert folgende Ausgabe:


Die Summe von 345 und 67 ist 412

Feldbreite von 6 bewirkt bei der gleichen Addition folgende Ausgabe:
Die Summe von 345 und 67 ist    412

Feldbreite von 1 ist zu schmal für das Resultat und wird daher überschritten:
Die Summe von 345 und 67 ist 412

Keine Festlegung der Genauigkeit:
345 minus 32.768902 gibt 312.231098

Reduktion der Genauigkeit führt zu Rundung
345 minus 32.769 gibt 312.23

Formatierung mit dem Minuszeichen:
Die Summe von 345 und 67 ist:
412    linksbündig (mit Minuszeichen)
   412 rechtsbündig (ohne Minuszeichen)

Beispiel für die Verwendung von %n:
Bisher wurden von printf() gedruckt: 63 Zeichen

Die Funktion scanf()

Die Funktion scanf() ist quasi der Gegenspieler von printf(); während printf() unter Verwendung einer Format-Zeichenkette die Ausgabe bestimmt, benutzt scanf() eine solche, um die Daten aus dem Eingabestrom einzelnen Variablen zuzuordnen. Dabei ist scanf() in der Lage, alle eingebauten Datentypen von C zu erkennen und zu lesen. Der Rückgabewert von scanf() ist wie auch bei printf() vom Typ integer: scanf() gibt die Anzahl der Variablen, denen erfolgreich ein Wert zugewiesen wurde, zurück. Tritt ein Fehler auf, ist der Returncode von scanf() ein negativer Wert. Der Aufruf von scanf() erfolgt in der Form


scanf( format, argument1, argument2, ... );

Der Adreßoperator &

Die Funktion scanf() erwartet als Argumente Werte für Speicherbereiche, an den die eingegebenen Daten abgelegt werden sollen. Soll also z.B. der integer-Variablen iInteger mit Hilfe von scanf() ein Wert zugewiesen werden, dann darf nicht einfach iInteger als Argument angegeben werden. Vielmehr muß der Funktion scanf() die Speicheradresse übergeben werden, der für die Variable iInteger bei der Definition reserviert wurde. Für diesen Zweck existiert in C der unäre Adreßoperator, der einer Variablen oder Konstanten voranzustellen ist; der Adreßoperator wird geschrieben als & (Ampersand).


/************************************************
 * Beispiel für den Einsatz des Adreßoperators  *
 ************************************************/

#include <stdio.h> /* wg. printf() */

int main(void){

int iInteger = 0;
const float fPi = 3.1415;

	printf("Die Variable integer befindet sich an"
		"der Adresse %p\n", &iInteger);

	printf("Die Konstante fPi befindet sich an"
		"der Adresse %p\n", &fPi);

return 0;
}

Die Ausführung dieses Programms bewirkt aufgrund der Verwendung des Adreßoperators & nicht die Ausgabe der Variablenwerte 0 und 3.1415, sondern führt zur Darstellung der jeweiligen Speicheradressen:


Die Variable integer befindet sich an der Adresse 0003BF9C
Die Konstante fPi befindet sich an der Adresse 0003BFA0

Jetzt dürfte auch klar sein, was passiert, wenn der Funktion scanf() anstatt einer Speicheradresse eine Variable übergeben wird: Mit Ausnahme von Pointern beinhalten Variablen normalerweise keine Speicheradressen, sondern alle möglichen Werte, die ihnen irgendwann zugewiesen wurden; scanf() interpretiert den Wert der Variablen als Speicheradresse und versucht, an diese Stelle des Speichers die Benutzereingabe zu schreiben. Ist die Variable noch nicht initialisiert, dann ist völlig unvorhersehbar, wo scanf() den eingelesenen Wert im Speicher ablegt. Der zufällige Wert, den jede Variable bei der Definition enthält, wird dann nämlich als Wert für die zu verwendende Speicheradresse genommen. Ein solcher Programmierfehler führt gewöhnlich zum vorzeitigen Abbruch der Anwendung, im schlimmsten Fall ist der Griff zur Reset-Taste erforderlich.

Die Formatierungs-Zeichenkette kann bei scanf() folgendes enthalten:

Beispiele für die Verwendung von scanf():


.
.
#include <stdio.h>
 /* wg. scanf() und printf() */

int main(void){

int iTag, iMonat, iJahr;

	printf("Bitte aktuelles Datum eingeben:\n");
	scanf("%d %d %d", &iTag, &iMonat, &iJahr);
.
.

Die Eingabe


25	2
1994

weist der Variablen iTag den Wert 25, iMonat den Wert 2 und iJahr 1994 zu. Das Auftreten von sogenannten "Whitespace"-Zeichen in der Eingabe beendet die Zuordnung der eingegeben Daten zu einer Variablen und bestimmt das Einlesen von Werten für die nächste. So bewirkt der Tabulator zwischen 25 und 2, daß scanf() die Eingabe für die Variable iTag als beendet ansieht und Daten für iMonat einzulesen beginnt. Das gleiche bewirkt der Zeilenvorschub zwischen 2 und 1994; statt der Eingabetaste nach 2 hätte aber genauso gut die Leertaste gedrückt werden können.

Wie schon bei getchar() bleibt ein abschließendes (␍) oder Leerzeichen im Tastaturpuffer stehen; ein nach scanf() vorkommendes getchar() bekäme keine Benutzereingabe zu sehen, sondern würde durch das verbliebene Zeichen ausgelöst. Diesen unerwünschten Effekt beseitigt auch hier ein leeres getchar() im Anschluß an scanf().


.
.
int iTag, iMonat, iJahr;

	printf("Bitte aktuelles Datum in der Form"

		tt/mm/jjjj eingeben:\n");
	 scanf("%d/%d/%d", &iTag, &iMonat, &iJahr);
.
.

Aufgrund der hier vorliegenden Formatierungs-Zeichenkette erwartet scanf() zwischen den Werten für die einzelnen Variablen einen Schrägstrich; erst beim Auftreten eines Schrägstrichs kommt die nächstfolgende Variable zum Zug. Der Schrägstrich wird zwar eingelesen, aber von scanf() in keiner Variablen abgelegt. Die Eingabe


6/12/1994

weist iTag 6, iMonat 12 und iJahr 1994 zu. Hingegen führt die Eingabe


25	2
1994

zwar dazu, daß iTag den Wert 25 erhält; iMonat und iJahr gehen jedoch wegen der ungültigen Trennzeichen leer aus.

Um anstelle des Schrägstrichs jedes beliebige Zeichen als Trenner zwischen den Eingaben für die Variablen zuzulassen, kann der Modifizierer * eingesetzt werden. Dieser bewirkt, daß zwar die Eingabe gelesen, aber keiner Variablen zugewiesen wird:


.
.
int iTag, iMonat, iJahr;

	printf("Bitte aktuelles Datum eingeben:\n");
	scanf("%d%*c%d%*c%d", &iTag, &iMonat, &iJahr);
.
.

Diese Format-Zeichenkette läßt die Eingabe


26-2-1994

genauso zu wie


26/2/1994

oder


26.2.1994

Und zwar deshalb, weil dar erste nichtnumerische Wert von %*c konsumiert wird und sich daher nicht störend als Minuszeichen oder Dezimalpunkt bemerkbar machen kann.

Bei der Angabe von maximalen Feldbreiten entfällt die Notwendigkeit, mit sogenannten "Whitespace"-Zeichen oder sonstigen Trennern die Eingabe auf die nächste Variable zu richten.


.
.
int iTag, iMonat, iJahr;

	printf("Bitte aktuelles Datum in der Form "
		"ttmmjjjj eingeben:\n");
	 scanf("%2d%2d%4d", &iTag, &iMonat, &iJahr);
.
.

Diese Format-Zeichenkette bewirkt, daß auch bei der Eingabe von


26021994

die einzelnen Variablen die ihnen zugedachten Werte erhalten. Trotzdem besteht aber bei dieser Formatierungsvariante weiterhin die Möglichkeit, zwischen den Werten für Tag, Monat und Jahr Leerzeichen, Tabulatoren oder Zeilenvorschübe einzutippen. Erst auf diese Trennzeichen folgende numerische Eingaben bewirken, daß den Variablen Werte zugewiesen werden.

Folgendes Beispiel demonstriert die Verwendung einer Auswahlliste innerhalb der Format-Zeichenkette. Alle Zeichen in den eckigen Klammern nach dem Prozentzeichen repräsentieren mögliche gültige Eingaben.


.
.

	printf("Soll das Programm beendet werden ?\n(J/N) ");
	iResult = scanf("%[jnJN]", &cKeyPressed);

	 printf("%d\n", iResult);
.
.

Es ist bei diesem Aufruf von scanf() nicht möglich, durch die Eingabe von


asfewpY

den Wert Y in die Variable cKeyPressed einzulesen; es wäre durchaus plausibel, davon auszugehen, daß scanf() alle ungültigen Zeichen verwirft und so lange von der Eingabe einliest, bis ein passendes Zeichen auftaucht. Wie aber schon erwähnt, beendet scanf() nach dem ersten ungültigen Zeichen den Einlesevorgang für das betroffene Eingabefeld. Im konkreten Fall heißt dies, daß die Variable iResult nur dann auf 1 gesetzt wird, wenn eines der vier Zeichen aus der Liste eingegeben wird. Die Liste selbst kann eine einfache Aufzählung der zulässigen Zeichen sein; es besteht aber auch die Möglichkeit, Zeichen zu definieren, die nicht eingegeben werden dürfen. Dazu muß der Liste ein ^ vorangestellt werden. Zum Beispiel schließt


iResult = scanf("%1[^yY]", &cKeyPressed);

die Zeichen Y und y als gültige Eingaben aus. Schließlich kann auch noch ein Bereich von Zeichen definiert werden, aus dem Eingaben gewählt werden können:


iResult = scanf("%[A-Z]", &cKeyPressed);

Diese Bereichsliste sorgt dafür, daß nur Großbuchstaben akzeptiert werden.

Wie sich noch herausstellen wird, ist die Verwendung von solchen Scansets besonders praktisch beim Einlesen von Zeichenketten: Auch dort bricht scanf() beim ersten unzulässigen Zeichen die Zuordnung der eingegebenen Zeichen zur Zeichenkette ab.