Terminal-Ein-/Ausgabe und Zeichenketten

In diesem Kapitel soll auszugsweise auf die Routinen der Standardbibliothek zur Ein- und Ausgabe eingegangen werden. Da ANSI diese Routinen in ihrer Wirkungs- und Anwendungsweise vorgeschrieben hat, können Programme, die sich nur dieser Funktionen bedienen, leicht von einem System auf ein anderes portiert werden.

In der Headerdatei stdio.h finden sich u.a. die folgenden Prototypen. Hier stehen auch die Deklarationen für die Standarddateien stdin, stdout und stderr (Fehlerausgabekanal).

/** ... auszugsweise ... **/
extern int printf(const char *,...);
extern int scanf(const char *,...);
extern int sprintf(char *, const char *,...);
extern int sscanf(const char *, const char *,...);
extern int getchar(void);
extern char *gets(char *);
extern int putchar(int);
extern int puts(const char *);

Zeichenweise Ein- und Ausgabe

Der einfachste Mechanismus besteht darin, ein einzelnes Zeichen ein- bzw. auszugeben. Zur Eingabe eines Zeichens von Tastatur dient die Funktion getchar(). Prototyp:

int getchar(void);

getchar() liefert bei jedem Aufruf das nächste Zeichen im Eingabestrom (in der Regel stdin) oder den (ebenfalls in stdio.h definierten) Wert EOF (end of file) zur Kennzeichnung, daß der Eingabestrom geschlossen worden ist[15].

Beispiel: Das nachfolgende Programm readchar.c liest ein Zeichen von Tastatur ein und gibt es zusammen mit seiner Nummer im ASCII-Code wieder aus. Beachten Sie jedoch: die Eingabe ist gepuffert, d.h. es muß erst [Return] gedrückt werden, bevor die Zuweisung an zeichen und die Ausgabe via printf() geschehen können[16]!

#include <stdio.h>

void main(void)
{
int zeichen; /* Beachten Sie: zeichen ist int! */
zeichen=getchar();
printf("Das Zeichen ist %c [ASCII-Nr.: %d]!\n",zeichen,zeichen);

} /* end main */

Neben dem bereits erwähnten printf() dient die Funktion putchar() zur Ausgabe eines einzelnen Zeichens auf den Ausgabestrom, in der Regel die Datei stdout. Prototyp:

int putchar(int);

Die Funktion putchar() gibt das übergebene Zeichen auf den Ausgabestrom aus; gleichzeitig liefert sie das ausgegebene Zeichen oder EOF zurück - EOF dann, wenn ein Fehler aufgetreten ist.

Hinweis: Bei den meisten Compilern sind getchar() und putchar() keine Funktionen, sondern als Makros realisiert. Auf diesen zunächst nicht so relevanten Unterschied soll an dieser Stelle nicht weiter eingegangen werden.

Beispiel: Das zugegebenermaßen nicht besondes aufregende nachstehende Programm liest ein Zeichen über getchar() von Tastatur ein und gibt es mittels putchar() wieder aus.

#include <stdio.h>

void main(void)
{
int c = getchar();
putchar(c);
} /* end main */

In der Datei ctype.h stehen u.a. die folgenden Prototypen. Die isirgendwas()-Routinen prüfen, ob ein gewisser Sachverhalt vorliegt, isalpha() prüft beispielsweise, ob das übergebene Zeichen ein (amerikanischer) Buchstabe ist, isdigit() ob es ein Ziffernzeichen ist, islower() ob es ein Kleinbuchstabe ist usw. Die Funktionen tolower() und toupper() wandeln (amerikanische) Buchstaben um in Klein- bzw. Großschreibung[17].

/** ... auszugsweise ... **/
extern int isalnum(int);
extern int isalpha(int);
extern int isdigit(int);
extern int islower(int);
extern int isprint(int);
extern int ispunct(int);
extern int isspace(int);
extern int isupper(int);
extern int tolower(int);
extern int toupper(int);

Zeichenketten (Strings)

Für weitergehende Ein- und Ausgabe (z.B. mittels scanf() und printf(), was beispielhaft bereits weiter vorne vorgestellt worden ist) sind Zeichenketten (Strings) erforderlich.

Zeichenketten können entweder statisch (wie packed array of char bei Pascal) oder dynamisch mit Pointern (wie der Datentyp String bei vielen Pascal-Dialekten) vereinbart werden. Sowohl auf die Array-, als auch die Pointer-Variante wird später noch ausführlicher eingangen werden. Im Moment sollen uns die grundlegenden Deklarationen zur Anwendung in Zusammenhang mit scanf() und printf() genügen.

#include <stdio.h>
#include <string.h>

void main(void)
{
char Statisch[20];
char *Dynamisch = "Hello, world!";
/** ... **/

strcpy(Statisch,Dynamisch);
printf("%s",Statisch);
printf("%s",Dynamisch);
putchar(Statisch[0]);

/** ... **/

} /* end main */

Im obigen Beispielprogramm(auszug) strings.c wird ein statisches Array von 20 char-Speicherplätzen angelegt, die (für Pascal-Programmierer gewöhnungsbedürftig) mit 0 bis 19 adressiert werden können.

Wichtig: In C werden Zeichenketten mit einer terminierenden '\0' (ASCII-Zeichen Nr. 0) abgespeichert, die natürlich auch 1 Byte benötigt; daher "paßt" in die oben deklarierte Variable Statisch nur eine (sichtbare) Zeichenkette von maximal 19 Zeichen, wenn man nicht blaue Wunder erleben will!

Darunter wird die Variable Dynamisch durch "char *" vereinbart als Zeiger auf char (Pointer auf char). Dadurch erhält diese Variable zunächst nur den Speicherplatz, den ein Zeiger benötigt! Hier wird jedoch sofort   das geht in C generell  ein konstanter Text zugewiesen, so daß Dynamisch sofort über 14 Bytes verfügen kann. Mit der Funktion strcpy(), die in dem Headerfile string.h mit dem Prototypen char *strcpy(char *, const char *) vereinbart wurde, wird der Inhalt des zweiten Parameters in die Zeichenkette, die im ersten Parameter übergeben wird, kopiert. Der konkrete Funktionsaufruf strcpy(Statisch,Dynamisch); kopiert also den Inhalt von Dynamisch in die Variable Statisch - inklusive der terminierenden '\0'.

Unabhängig von der Deklarationsform kann bei beiden Variablen in gewohnter Weise auf die einzelnen Zeichen zugegriffen werden: Dynamisch[0] ist ein char, das hier das 'H' beinhaltet, Statisch[13] hat nach der Kopieraktion das Zeichen '\0' (ASCII-0) bekommen.

A zero terminated string


Ein-/Ausgabe-Formatierung

Die wesentlichen Standardroutinen in C zur Ein- und Ausgabe von Zeichen(ketten) und numerischen Werten sind printf() und scanf().

Die Funktion printf()

Die Ausgaberoutine printf() ist in stdio.h deklariert mit dem Prototyp

(extern) int printf(const char *,...);

die drei Punkte deuten eine variabel lange Parameterliste an. Der erste Parameter bei printf() ist der sogenannte Formatstring: das ist das Muster, wie die Ausgabe aussehen soll. printf() schreibt auf den Standardausgabestrom (stdout, in der Regel der Bildschirm).

Beispiele:

printf("Konstanter Text"); /* Formatstring=konstanter Text */
printf("Zahl: %d",i); /* %d bewirkt, daß i als int */
/* aufbereitet ausgegeben wird */
printf("%d %c %x",i,j,k); /* i wird als int, j als char, */
/* k hexadezimal aufbereitet... */

Der Formatstring enthält also zwei verschiedene Arten von Objekten: gewöhnliche Zeichen (wie das Wort "Zahl" im Beispiel b)), die direkt in die Ausgabe geschrieben werden, und Formatierungen, die jeweils die entsprechende Umwandlung und Aufbereitung des nächsten Arguments von printf() bewirken. Jede solche Umwandlungsangabe beginnt mit einem Prozentzeichen % und endet mit einem Umwandlungszeichen. Dazwischen kann, in dieser Reihenfolge, angegeben werden:

  1. ein Minuszeichen: damit wird das Argument linksbündig ausgegeben;
  2. eine positive, ganze Zahl, die eine minimale Feldbreite bestimmt; benötigt das Argument mehr Stellen als angegeben, so werden ihm diese auch gegeben, benötigt es weniger, so wird mit Blanks aufgefüllt;
  3. ein Punkt, der die Feldbreite von der Genauigkeit (precision) trennt;
  4. eine positive, ganze Zahl, die die maximale Anzahl von Zeichen festlegt, die von einer Zeichenkette ausgegeben werden sollen, oder die Anzahl Ziffern, die nach dem Dezimalpunkt bei einer Gleitkommazahl ausgegeben werden, oder die minimale Anzahl von Ziffern, die bei einem ganzzahligen Wert ausgegeben werden sollen;
  5. der Buchstabe h oder H, wenn short ausgegeben werden soll, oder der Buchstabe l oder L, wenn das Argument long ist.

Hinweis: Wenn das Zeichen nach % keines der obigen Zeichen und kein Umwandlungszeichen ist, dann ist die Wirkung von printf() undefiniert!

Nachstehend eine kurze (nicht vollständige) Übersicht über wichtige Formatierungszeichen.

Symbol   ...steht für...                     
   d     dezimale Ganzzahl, int              
   x     hexadezimale Ganzzahl, int          
   u     vorzeichenlose Ganzzahl, unsigned   
         int                                 
  hd     kurze Ganzzahl, short int           
  ld     dezimale Ganzzahl, long int         
   f     Gleitkommazahl, float               
  lf     Gleitkommazahl, double              
   c     einzelnes Zeichen, char             

Nachstehend ein kleines Beispiel zur Illustration: Zunächst das Programm, dann das Ablauflisting.

#include <stdio.h>

void main(void)
{

char *txt="Eine kleine Textzeile";
int i=123456;

printf("\n:%s:",txt);
printf("\n:%15s:",txt);
printf("\n:%-10s:",txt);
printf("\n:%15.10s:",txt);
printf("\n:%-15.10s:",txt);
printf("\n:%-10.5s:",txt);
printf("\n:%.10s:",txt);
printf("\n");
printf("\n:%20d:",i);
printf("\n:%-10d:",i);
printf("\n:%5.3d:",i);
printf("\n");
} /* end main */

Und das zugehörige Ablauflisting:

 :Eine kleine Textzeile:
 :Eine kleine Textzeile:
 :Eine kleine Textzeile:
 :     Eine klein:
 :Eine klein     :
 :Eine      :
 :Eine klein:
 :              123456:
 :123456    :
 :123456:

Die Funktion scanf()

Die Funktion scanf() ist die zu printf() analoge Einleseroutine, die ebenfalls in stdio.h deklariert ist mit dem Prototypen

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

scanf() liest Zeichen aus dem Standardeingabestrom (stdin), wobei die Verarbeitungsweise wieder über einen Formatstring kontrolliert wird. scanf() hört auf, wenn die Format-Zeichenkette vollständig abgearbeitet ist, oder aber wenn ein Eingabefeld nicht zur Umwandlungsangabe paßt. Als Funktionsresultat wird die Anzahl erfolgreich erkannter und zugewiesener Eingabefelder zurückgeliefert. Am Eingabeende wird EOF zurückgeliefert. Der nächste Aufruf von scanf() beginnt dann seine Arbeit unmittelbar nach dem zuletzt umgewandelten Zeichen.

Übersicht: Elementare scanf()-Formatstrings (Auszug)

Zeichen  Argument    Eingabe                                                  
 d, i    int*        dezimal, ganzzahlig, int                                 
   u     int*        dezimal ohne Vorzeichen, unsigned                        
   c     char*       ein einzelnes Zeichen                                    
   s     char*       eine Zeichenkette (ohne Hochkommata), das Zeichen '\0'   
                     wird von C automatisch hinzugefügt. Wichtig: Es wird     
                     allerdings nur bis zum ersten Whitespace (Leerzeichen,   
                     Tabulator etc.) eingelesen!                              
e, f, g  float*      Gleitkommazahlen, float                                  

Einige Beispiele für scanf()-Aufrufe:

int i;
float f;
char txt[80];
char c;

/* ... */
scanf("%d",&i); /* Wichtig: Adreßoperator & vor i !!! */
scanf("%f",&f); /* Denn C kennt nur call by value !!! */
scanf("%s",txt); /* Arrays sind intern Adressen, daher */
/* muß hier kein Adreßoperator genom- */
scanf("%c",&c); /* men werden! */
scanf("%c",&(txt[0])); /* Adresse von txt[0] */
scanf("%u",&i);

Warnung: Noch einmal ausdrücklich: einer der häufigsten Anfängerfehler ist die Formulierung

scanf("%d",i);

Hiermit wird nicht auf den Speicherplatz i eingelesen, sondern dorthin in den Hauptspeicher, wohin der momentane Wert von i (wenn überhaupt) gerade zeigt. Unter UNIX wird dieser Fehler normalerweise bemerkt, auf dem PC kann er beliebige Nebenwirkungen haben...

Aus Platzgründen soll es bei diesem kurzen Einblick bleiben; neben den hier vorgestellten beiden Grundroutinen sind in stdio.h natürlich noch eine ganze Reihe weiterer Routinen zum (formatierten) Einlesen aus Dateien oder Speicherbereichen vorhanden (fprintf() und fscanf(), sprintf() und sscanf()). Speziell fprintf() wird dann benötigt, wenn Ausgaben auf den Fehlerkanal stderr geleitet werden sollen, wie die folgende Skizze illustriert.

/* error1.c */
include <stdio.h>

void main(void)
{
printf("Diese Ausgabe geht auf stdout...\n");
fprintf(stderr,"Diese Ausgabe geht auf stderr...\n");

} /* end main */

Ablauflisting:

1. Bei Aufruf "error1" (stdout und stderr gehen auf den Bildschirm)

Diese Ausgabe geht auf stdout...
Diese Ausgabe geht auf stderr...

2. Bei Aufruf "error1 > anyfile"

Diese Ausgabe geht auf stderr...

(Die erste Textzeile ist in der Datei anyfile zu finden!)