C-Tutorial (C oder C++, Vorwort, Installation, Kapitel 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)

Ein- und Ausgabe

3.1. Daten mit printf() ausgeben und formatieren

Die Datenein- und -ausgabe gehört zu den wichtigsten Dingen während des Programmablaufs. Ein Programm macht für viele Benutzer - zumindest auf den ersten Blick - keinen Sinn, wenn sie nicht über den aktuellen Ablauf informiert werden. Viele Benutzer wollen erfahren, welche Tätigkeit das Programm im Augenblick ausführt. Sehr wichtig ist hierbei auch die Eingabe, denn immerhin soll das Programm individuell auf die Wünsche des Benutzers reagieren können, diese verarbeiten und ausführen.

Allerdings ist hier wieder Information notwendig, denn der Benutzer ist nur dann imstande, die benötigten Daten zu liefern, wenn er darüber informiert wurde, was er eingeben soll. Der Eingabe geht also oft eine Ausgabe durch das Programm voran. Mit dieser, der Datenausgabe, wollen wir uns in diesem Kapitel beschäftigen.

Im Normalfall verwendet man zur Ausgabe von Texten die Funktion printf(), die uns die Standardbibliothek zur Verfügung stellt. Dazu muss die Headerdatei stdio.h inkludiert werden. Die einfachste Form der Ausgabe sieht dabei folgendermaßen aus:

printf (Text);

Ein Beispiel, das Sie bereits kennen:

printf ("hello, world!");

Es ist allerdings auch möglich - und oft erwünscht-, Werte auszugeben, die Variablen und Konstanten speichern. Das nächste Beispiel kennen Sie aus Kapitel 2.4., wo die int-Variable i ausgegeben wurde:

printf ("Wert von i: %d\n", i);

Die genaue Erklärung dafür bin ich im letzten Kapitel noch schuldig geblieben. Wie Sie sehen, übergeben wir der Funktion printf() hier zwei Parameter, einen String (eine Zeichenkette; also der Text, der zwischen den Anführungszeichen steht) und zusätzlich die Variable i, deren Wert wir ausgeben wollen. Die Zeichenkette bezeichnet man im Zusammenhang mit printf() als Formatstring. Dieser enthält den vielleicht etwas verwirrenden Text "%d".

Das %-Zeichen signalisiert, dass an dieser Stelle ein Wert ausgegeben werden soll. Dass hier das %-Zeichen zum ersten Mal vorkommt, bedeutet, dass der Wert der ersten nach dem String stehenden Variable ausgegeben wird. Das ist in diesem Fall die Variable i. Das d signalisiert, von welchem Typ die Variable ist. d steht für eine Ganzzahl. Hier soll also der Wert einer Ganzzahl ausgegeben werden.

Um es genauer zu machen: Das d signalisiert nur, dass die Variable als eine int-Variable behandelt werden soll. Von welchem Typ die Variable tatsächlich ist, spielt keine Rolle. So kann es zu ungewollten Ausgaben kommen, wenn die Variable von einem anderen Typ als int ist. Dann wird sie nämlich entsprechend umgewandelt, was gewollt oder ungewollt sein kann. Sie werden allerdings auch sehen, wie sich das sinnvoll einsetzen lässt, etwa bei der Umwandlung von Dezimal- zu Hexadezimalzahlen, Oktalzahlen und umgekehrt.

Es lassen sich also mehrere Variablen auf einmal ausgeben. Etwa das Ergebnis einer simplen Addition:

printf ("%d + %d ergibt %d.", x, y, ergebnis);

Angenommen, die Variable x hätte den Wert 10 und y 15. ergebnis speichert das Ergebnis der Addition beider Zahlen, also 25. Im obigen Beispiel würde also 10 + 15 ergibt 25. ausgegeben werden.

Farbig untermalt, was zusammen gehört:

printf ("%d + %d ergibt %d.", x, y, ergebnis);

Das erste %d gehört also zu x, das zweite zu y und das dritte zu ergebnis. Es ist auch möglich, die Werte direkt anzugeben (statt des Variablenbezeichners):

printf ("%d + %d ergibt %d.", 10, 15, 10+15);

Bevor der Text ausgegeben wird, werden die Ausdrücke (10+15 ist ein Ausdruck) nach dem String ausgewertet. 10+15 wird berechnet und das Ergebnis von printf() an der entsprechenden Stelle ausgegeben.

Außer %d existieren noch weitere Formatelemente:

Formatelement Typ Ausgabe
%d int Dezimalzahl
%u unsigned int Natürliche (Dezimal-)Zahl (pos. Zahl)
%x, %X unsigned int Hexadezimalzahl
%o unsigned int Oktalzahl
%c char Einzelnes Zeichen (ASCII-Zeichen)
%s char * Zeichenkette (String)
%f float, double Gleitkommazahl
%e, %E float, double Gleitkommazahl in Exponential-Darstellung
%g, %G float, double Normale od. Exponential-Darstellung (was günstiger ist)

Das Formatelement %u erlaubt es, positive Ganzzahlen auszugeben, die größer sind als der Maximalbereich von int. Probieren Sie das nächste Beispiel aus. In diesem wird versucht, mit %u eine negative Zahl auszugeben.

#include <stdio.h>
 
int main()
{
  int a = -12345;
  printf ("Wert der Variable a als positive Zahl behandelt: %u.", a);
 
  return 0;
}

Überrascht vom Ergebnis? Der Grund dafür ist, dass beim Abspeichern einer negativen Zahl von dieser das Zweier-Komplement (Bits werden invertiert und 1 addiert) gebildet wird. Die umgewandelte Zahl sehen Sie als Ergebnis.

Um den Absolutbetrag (= der Wert ohne Vorzeichen, also 12345) von -12345 darzustellen, müssten Sie von dieser wieder das Zweier-Komplement bilden. Einfacher geht es, wenn Sie den negativen Wert mit (-1) multiplizieren, oder besser, Sie verwenden die Funktion abs() wie das folgende Beispiel zeigt:

#include <stdio.h>
#include <stdlib.h>    /* wird fuer abs() benoetigt */
 
int main()
{
  int a = -12345;
  printf ("Absolutwert der Variable a: %d.", abs(a));    /* so funktioniert es! */
 
  return 0;
}

Kurz gesagt: %u erwartet einen Wert vom Typ unsigned int, wie Sie der oberen Tabelle entnehmen können. Dass im Beispiel zu abs() das Formatelement %d (und nicht %u) angegeben wurde, liegt daran, dass abs() einen Wert vom Typ int (und nicht unsigned int) zurückliefert (Rückgabewert). %u funktioniert hier aber genauso. Näheres zu Rückgabewerten erfahren Sie in Kapitel 10 über Funktionen.

Nun möchten wir die positive Zahl 12345 in Dezimal-, Hexadezimal- und Oktal-Darstellung ausgeben:

#include <stdio.h>
 
int main()
{
  int a = 12345;
 
  printf ("Dezimal: %u.\n", a);
  printf ("Hexadezimal: %X.\n", a);
  printf ("Oktal: %o.\n\n", a);
 
  return 0;
}

%X sorgt dafür, dass der Wert in hexadezimaler Schreibweise dargestellt wird. %x ist ebenso möglich, dann werden die Buchstaben A bis F klein dargestellt, also a bis f. Zur oktalen Darstellung verwenden wir %o. Auf \n werde ich später bei den Escape-Sequenzen noch einmal zurückkommen. Soviel sei gesagt: \n sorgt für einen Zeilenumbruch.

Sie können Werte auch direkt in Hexadezimal- und Oktalschreibweise angeben. Das ist besonders in der hardwarenahen Programmierung wichtig.

i = 0xA;   /* weist i den Hexadezimal-Wert A zu; entspricht 10 dezimal */

i = 012;   /* weist i den Oktal-Wert 12 zu; entspricht 10 dezimal */

Um Werte in Hexadezimalform anzugeben, wird in C ein 0x (Null + x) vor die Zahl gestellt. Um Oktalzahlen anzugeben, schreibt man eine 0 (Null) vor den Zahlenwert.

Wie es der Name printf (= print formatted) schon andeutet, lassen sich Texte formatiert ausgeben. So ist es mit printf() möglich, Breite und Ausrichtung des Textes zu bestimmen. Es kann etwa festgelegt werden, dass für die Ausgabe einer Zahl eine Breite von zehn Zeichen reserviert werden sollen.

Wird für die Ausgabe doch mehr Platz benötigt, so wird die Zahl aber nicht abgeschnitten. Neben der Angabe einer Breite ist es auch möglich, die Ausrichtung zu bestimmen. Also, soll der Text links- oder rechtsbündig ausgerichtet werden. Das folgende Beispiel demonstriert diese Funktion. Beachten Sie, dass im Textmodus pro Zeile 80 Zeichen angezeigt werden können, und insgesamt 25 Zeilen zur Verfügung stehen.

#include <stdio.h>
 
int main()
{
  printf ("%-5d%75d", 1, 1000);
  printf ("%80s","Dieser Text wird rechtsbuendig angezeigt.");
 
  return 0;
}

Die erste printf()-Anweisung sorgt für die Ausgabe zweier Zahlen sowie für einen Zeilenumbruch. Das erste Formatelement %-5d gibt mit einer Breite von 5 Zeichen eine Ganzzahl linksbündig aus. Für die 5 Zeichen Breite sorgt die 5, die zwischen dem %-Zeichen und dem d steht. Das Minus-Zeichen (bzw. der Bindestrich -; im ASCII-Code besteht zwischen beiden kein Unterschied) verursacht die linksbündige Ausgabe, das der 5 vorangestellt ist.

Das zweite Formatelement %75d macht Ähnliches. Es gibt eine Zahl rechtsbündig (da kein Minus vor der Zahl) auf 75 Zeichen Breite aus. Die zweite printf()-Anweisung sorgt für die Ausgabe eines Textes, worauf das Formatelement %s hinweist. Zwischen dem Prozentzeichen und dem s steht die Zahl 80, die die Breite angibt. Da hier kein Minus vorangestellt ist, erfolgt die Ausgabe rechtsbündig (das ist Standard).

Wichtig ist in diesem Beispiel, dass beide Zahlen in Summe 80 und somit die Gesamtbreite des Bildschirms ergeben. Immerhin ist der Zweck ja, den Text so weit rechtsbündig und linksbündig wie möglich auszugeben. Die rechtsbündige Ausgabe am Bildschirmrand wäre bei einer Summe < 80 nicht möglich. Für die erste Zahl benötigt man 1 Zeichen Breite, für die zweite 4 Zeichen. Das heißt, es wäre für die erste Zahl jede Breitenangabe >= 1 möglich (ich habe willkürlich 5 gewählt). Die zweite Zahl muss somit 80 abzüglich der ersten Zahl sein, in diesem Fall also 75.

Wenn Sie das obere Beispiel in einem Konsolenfenster unter Linux testen, müssen Sie womöglich vorher erst die Fenstergröße anpassen. Im KDE 3 geht das im Konsolenmenü über Einstellungen->Größe und den Auswahlpunkt 80x25 (IBM PC). Die voreingestellte Größe liegt meist höher.

Einige Dinge können in der Praxis noch ganz nützlich sein: Anstelle eines Minuszeichens kann auch ein Plus (+) angegeben werden. Das hat zur Folge, dass eine positive Zahl immer mit Vorzeichen ausgegeben wird (also +10 statt 10). Wird ein Leerzeichen anstelle eines Plus- oder Minuszeichens angegeben, dann wird, wenn kein Vorzeichen nötig und üblich ist (also bei positiven Zahlen), ein Leerzeichen bei Ganzzahlen ausgegeben. Gibt man eine Null (0) an, so werden alle nicht benötigten Zeichen der eingestellten Breite von vorne weg mit Nullen aufgefüllt.

Weniger gebräuchlich ist die Angabe der Raute #. Hier wird bei der Ausgabe einer Hexadezimalzahl ein 0x vorangestellt, bei Oktalzahlen eine 0 (Null) und bei Fließkommazahlen wird der Dezimalpunkt auch dann ausgegeben, wenn sich die Fließkommazahl auch als Ganzzahl ohne Rundung darstellen ließe (etwa 3.0). Werden hierbei die Formatelemente %g bzw. %G gewählt, werden auch die überflüssigen Nachkommastellen ausgegeben, ansonsten nicht.

Ein wichtiger Punkt fehlt noch: Die Ausgabe von Fließkommazahlen. Zuvor noch ein kurzer Einschub. Sie können Fließkommazahlen auch in Exponentialschreibweise angeben:

x = 1.3456e2;     /* weist den Wert 134,56 zu */
y = 9.342464E4;   /* weist den Wert 93424,64 zu */

Der Exponent zur Basis 10 steht nach dem e (bzw. E). 1.3456e2 ist mathematisch angeschrieben 1,3456 * 102.
1,3456 wird also mit 10 hoch 2 (= 1 mit 2 Nullen, also 100) multipliziert. Das Dezimalkomma wird hier 2 Stellen nach rechts verschoben (weil eine Multiplikation).

Um Fließkommazahlen auszugeben, stehen die Formatelemente %f, %g (bzw. %G) und %e (bzw. %E) zur Verfügung. Das nächste Beispiel zeigt, wie Fließkommazahlen ausgegeben werden können:

#include <stdio.h>
 
int main()
{
  float x = 0.123e1;   /* entspricht 1,23 */
  float y = 12.3;
 
  printf ("Dezimal - Exponentialschreibweise\n");
  printf ("%-7f - %23e\n\n", x, y);
 
  return 0;
}

Bei genauem Hinsehen lässt sich erkennen, dass das Beispiel nichts Neues enthält. Den Variablen x und y werden zwei Werte zugewiesen. Davon wurde eine Zahl in Exponentialschreibweise angegeben (Zuweisung von x). Interessant ist die zweite printf()-Anweisung. Wir nutzen die Längenangabe von %f und %e aus, um die Zahlen exakt unter den Worten Dezimal und Exponentialschreibweise zu positionieren. Das gelingt nicht ganz, da standardmäßig 6 Nachkommastellen angezeigt werden. Mit einer Vorkommastelle und dem Dezimalpunkt geht es sich um 1 Zeichen nicht aus.

Die Anzahl der Nachkommastellen lässt sich beeinflussen, wie das folgende Beispiel zeigt:

#include <stdio.h>
 
int main()
{
  float x = 0.123e1;   /* entspricht 1,23 */
  float y = 12.3;
 
  printf ("Dezimal - Exponentialschreibweise\n");
  printf ("%-7.5f - %23.5e\n\n", x, y);    /* je 5 Nachkommastellen */
 
  return 0;
}

Um die Anzahl der Dezimalstellen festzulegen, wird nach der Längenangabe ein Punkt . und die Anzahl der Dezimalstellen angegeben. Die Längenangabe kann auch weggelassen werden. Zum Beispiel: %.3f = Fließkommazahl mit 3 Nachkommastellen.

3.2. Escape-Sequenzen

Bereits mehrmals kam \n vor, das, wie gesagt, einen Zeilenumbruch erzeugt. Eine solche Zeichenfolge, die mit einem Backslash \ beginnt, bezeichnet man als Escape-Sequenz. Die nächste Tabelle zeigt alle möglichen Escape-Sequenzen:

Escape-Sequenz Beschreibung
\a Bell (Standardton); auch Escape-Sequenz \007 möglich
\b Backspace (letztes Zeichen löschen)
\f Seitenvorschub (formfeed)
\n New Line (neue Zeile)
\r Wagenrücklauf (an den Anfang der Zeile)
\t Tabulator (Sprung um einen Standardwert)
\v Vertikaler Tabulator (Sprung um einen Standardwert)
\' Hochkomma ausgeben
\" Anführungszeichen ausgeben
\\ Backslash (verkehrter Schrägstrich) ausgeben
\ooo Oktalzahl mit max. 3 Stellen ausgeben, z.B. oktal \077 entspricht dezimal 63.
Ersetzen Sie o durch eine Oktalzahl.
Ausgegeben wird das entsprechende ASCII-Zeichen.
\xhh Hexadezimalzahl mit max. 2 Stellen ausgeben.
Ersetzen Sie h durch eine Hexadezimalzahl.
Ausgegeben wird das entsprechende ASCII-Zeichen.

Ein String besteht - wie bereits erwähnt - aus mehreren Zeichen. Die Escape-Sequenz wird hierbei einfach durch ein entsprechendes ASCII-Zeichen ersetzt. Das nächste Beispiel zeigt die Verwendung mehrerer Escape-Sequenzen:

#include <stdio.h>
 
int main()
{
  printf ("Sie hören einen Ton ...\a\n");
  printf ("Dem folgenden \"xyz\" wird das letzte Zeichen entfernt und mit einem \"!\" überschrieben: xyz\b!\n");
  printf ("XXXZurück an den Anfang der Seite und 'XXX' überschreiben.\r...\n");
  printf ("\t\t2 Tabulatorsprünge; der Backslash: \\ \n");
 
  return 0;
} 

3.3. Daten von der Tastatur mit scanf() einlesen

Betrachten wir nun die Standardfunktion zur Eingabe: scanf(). Mit scanf() können Sie Daten von der Tastatur während des Programmablaufs einlesen. Es handelt sich also um eine interaktive Form der Eingabe. Unter Linux und UNIX-Systemen ist bei rein textbasierten Programmen eine andere Form der Eingabe vielfach üblich: Werte werden dabei als Kommandozeilenparameter angegeben und so an das Programm übergeben. Während das Programm läuft, ist dann keine weitere Eingabe mehr nötig. Wie Sie Kommandozeilenparameter verarbeiten, zeige ich Ihnen in Kapitel 13. Zunächst aber zu scanf():

scanf (Formatstring, Variable[,...]);

scanf() ist printf() sehr ähnlich. Ein kleines Beispiel:

scanf ("%d", &i);

Eingaben mit scanf() werden zeilenweise gepuffert. Das heißt, eine Eingabe lässt sich mit der Backspace-Taste korrigieren und wird erst dann abgeschlossen, wenn Enter gedrückt wird. Im eben genannten Beispiel wird eine Ganzzahl (%d) in die Variable i eingelesen. Auf den ersten Blick etwas verwirrend wirkt der &-Operator vor der Variable.

Merken Sie sich einstweil nur, dass Sie bei scanf() dieses Zeichen vor jede Variable stellen müssen. Im Kapitel zu Zeigern werden Sie sehen, weshalb das so ist. (Hinweis für Programmierer mit etwas Erfahrung im Umgang mit Zeigern: scanf() erwartet als Parameter einen Zeiger, da Variablen by Reference übergeben werden. Um die Adresse der Variable zu erhalten, wird der &-Operator benötigt.)

Es lassen sich auch mehrere Werte mit einer scanf()-Anweisung einlesen. Zum Beispiel:

scanf ("%d %f %d", &i, &x, &j);

Der Benutzer muss hierbei die einzelnen Eingaben durch (ein) Leerzeichen oder durch einen Zeilenumbruch trennen. Gibt der Benutzer etwa "3 10.2 4534" ein, so wird 3 in i, 10.2 in x und 4534 in j gespeichert.

Achten Sie bei der Verwendung von scanf() darauf, die richtigen Formatelemente im Formatstring anzugeben!

Wie bereits erwähnt, erfolgt die Eingabe gepuffert. Es wird aus dem Eingabepuffer gelesen und Eingaben sind erst dann zu Ende, wenn Enter (entspricht \n) gedrückt wird. Wenn Sie nun mehrere Werte hintereinander einlesen, kann es passieren, dass von der vorigen Eingabe noch Daten im Eingabepuffer sind, und die nächste Eingabe einfach übersprungen wird. Meistens bleibt ein \n im Puffer zurück. Wir müssen uns also von den Daten im Puffer trennen.

Ein häufig gegangener Weg ist, den Eingabestrom (standard input stream) stdin mit fflush(stdin) zu löschen. Das Problem: Das Verhalten von fflush(stdin) ist nicht standardisiert! Das heißt: Kann funktionieren, kann nicht funktionieren. In der Regel funktioniert es unter Windows, unter Linux nicht.

Testen Sie einmal das nächste Beispiel. Zweck des Beispiels ist es, zwei Ganzzahlen einzulesen. Voraussetzung ist für die erste Zahl, dass diese max. 3 Stellen hat. Die Zahl darf also nicht größer als 999 sein. Testen Sie das Beispiel einmal mit der ersten Zahl kleiner 999 und einmal mit ein einer größer 999. Welche zweite Zahl Sie wählen, ist egal.

#include <stdio.h>
 
int main()
{
  int zahl1 = 0, zahl2 = 0;
 
  printf ("Geben Sie die erste Zahl ein: ");
  scanf ("%3d", &zahl1);
 
  printf ("Geben Sie nun die zweite Zahl ein: ");
  scanf ("%d", &zahl2);
 
  printf ("\nDie eingegebenen Zahlen waren:\n");
  printf ("zahl1: %d\nzahl2: %d\n", zahl1, zahl2);
 
  return 0;
}

Geben Sie etwa einmal als erste Zahl 1234 ein. Das Formatelement %3d sorgt dafür, dass nur die ersten 3 Zeichen akzeptiert werden. Auch hier ist es also möglich, eine Längenangabe zu machen. Die Möglichkeit, hier noch eine zweite Eingabe zu tätigen, bekommen Sie nicht, da Folgendes passiert: scanf() liest aus dem Eingabepuffer dank des entsprechenden Formatelements nur 3 Zeichen ein. Der Rest, hier die Zahl 4 aus 1234, bleibt im Eingabepuffer. scanf() liest immer aus dem Eingabepuffer. Ist der Eingabepuffer nicht leer, wird dessen Inhalt verwendet. 4 steht im Eingabepuffer, wird als Eingabe des Benutzers angesehen und in zahl2 gespeichert.

Wenn Sie allerdings einer Zahl kleiner oder gleich 999 eingeben, funktioniert alles bestens. Testen Sie das Beispiel mit fflush(stdin):

#include <stdio.h>
 
int main()
{
  int zahl1 = 0, zahl2 = 0;
 
  printf ("Geben Sie die erste Zahl ein: ");
  scanf ("%3d", &zahl1);
 
  fflush(stdin);   /* Eingabepuffer loeschen; bzw. zumindest versuchen .. */
 
  printf ("Geben Sie nun die zweite Zahl ein: ");
  scanf ("%d", &zahl2);
 
  printf ("\nDie eingegebenen Zahlen waren:\n");
  printf ("zahl1: %d\nzahl2: %d\n", zahl1, zahl2);
 
  return 0;
}

Dieses Beispiel kann funktionieren, kann aber auch nicht funktionieren, je nachdem, welchen Compiler und welches Betriebssystem Sie verwenden. Das ist aber natürlich kein Ansatz, um plattformunabhängigen Code zu schreiben.

Eine Lösung für dieses Problem finden Sie hier und in Kapitel 13.1. Dazu fehlt Ihnen zum jetzigen Zeitpunkt aber noch das nötige Vorwissen (v.a. über Schleifen und Ausdrücke), weswegen ich den Lösungsansatz hier noch nicht näher vorstelle.

Im Laufe des Tutorials werden Sie noch sehr häufig mit der Ein- und Ausgabe konfrontiert. Die nötige Vertrautheit im Umgang mit printf() und scanf() kommt mit der Zeit von selbst.

Vorheriges Kapitel Nächstes Kapitel