Programmieren in C Die C-Standardbibliothek: Datei Ein- und Ausgabe Prof. Dr. Nikolaus Wulff
Das Dateisystem In C erfolgt die gesamte Ein- und Ausgabe durch Lesen oder Schreiben von Strömen (stream). Periphere Geräten werden immer mit der selben einheitlichen Schnittstelle angesprochen. Konsole stdout und die Tastatur stdin werden in C als Dateien modelliert, die beim Programmstart bereits geöffnet worden sind. Beim Programmstart kann diese Vorbelegung übersteuert werden: progamm.exe < eingabe.txt > ausgabe.txt Prof. Dr. Nikolaus Wulff 2
FILE und <stdio.h> FILE ist als Struktur in <stdio.h> deklariert: #ifndef _FILE_DEFINED struct _iobuf { int _cnt; /* Anzahl Zeichen */ char *_ptr; /* Zeiger auf Zeichen */ int _flag; /* Art des Dateizugriff */ char *_base; /* Zeiger auf Puffer */ int _file; /* File Deskriptor */... ; typedef struct _iobuf FILE; #define _FILE_DEFINED extern FILE _iob[]; #define stdin (&_iob[0]) #define stdout (&_iob[1]) #define stderr (&_iob[2]) Prof. Dr. Nikolaus Wulff 3
Datei Funktions Makros Viele häufig benötigte Funktion zur Ein- und Ausgabe entpuppen sich als einfache Makros: #define getc(_stream) (--(_stream)->_cnt >= 0 \? 0xff & *(_stream)->_ptr++ : _filbuf(_stream)) #define putc(_c,_stream) (--(_stream)->_cnt >= 0 \? 0xff & (*(_stream)->_ptr++ = \ (char)(_c)) : _flsbuf((_c),(_stream))) #define getchar() #define putchar(_c) getc(stdin) putc((_c),stdout) Wie die Deklarationen exakt aussehen hängt vom verwendeten Compiler und Betriebssystem ab. Daumenregel: interne Bezeichner mit _XXX nicht verwenden, diese sind meist nicht portabel. Prof. Dr. Nikolaus Wulff 4
Datei Funktionen Wichtige IO-Funktionen sind: fclose, fopen Stream schließen oder öffnen feof Testet ob Stream zu Ende ist ferror Testet Stream auf Fehler fflush Leert Puffer eines Streams fgetc, fputc Liest/schreibt ein Zeichen fgets, fputs Liest/schreibt eine Zeichenkette fscanf, fprintf Liest/schreibt formatierte Daten fread, fwrite Unformatiertes Lesen/Schreiben printf/scanf sind nur Spezialfälle von fprintf/fscanf. Prof. Dr. Nikolaus Wulff 5
Datei Zeichenweise einlesen int c; FILE *stream = fopen("test.txt","r"); while(!feof(stream)) { c = fgetc(stream); printf("%c",c); fclose(stream); Das obige Codefragment liest eine Datei test.txt. Die Datei wird mit dem read-only Parameter r im Lesemodus geöffnet. Schreiben ist w... Am Ende wird die Datei wieder geschlossen, d.h. die Resourcen werden an das Betriebssystem zurückgegeben. Prof. Dr. Nikolaus Wulff 6
Datei Zeilenweise einlesen #define MAX_LEN 256 void readfile() { char* line = malloc(sizeof(char)*max_len); FILE *stream = fopen("test.txt","rt"); while(!feof(stream)) { fgets(line, MAX_LEN, stream); printf("%s",line); fclose(stream); free(line); fgets liest Textdateien zeilenweise ein. Der notwendige Puffer, in dem die Zeile von fgets kopiert wird, wurde zuvor mit malloc bereitgestellt. rt read text weist das Öffnen einer Textdatei an. Prof. Dr. Nikolaus Wulff 7
Formatierte Datei einlesen void readfile() { float x; int i; char string[] = "interner Puffer gross genug"; FILE *stream = fopen("test.txt","r"); while(!feof(stream)) { fscanf(stream, "%d %s %f",&i, string, &x); printf("%s %d %f \n",string, i, x); fclose(stream); Dateien mit fester Formatierung lassen sich mit fscanf einlesen. fscanf benötigt die Adressen der zu lesenden Variablen, deshalb &x und &i! Prof. Dr. Nikolaus Wulff 8
Zeichenkette schreiben FILE *stream; char *string = "Hallo World"; stream = fopen("stext.txt","wt"); fputs(string,stream); fclose(stream); fputs ist das Gegenstück zu fgets fputs schreibt eine Zeichenkette in eine Textdatei. Prof. Dr. Nikolaus Wulff 9
Formatiertes Schreiben FILE *stream; int i = 2; float x = 3.14; char* s = "hallo"; stream = fopen("stext.txt","wt"); fprintf(stream,"%d %s %f",i,s,x); fclose(stream); fprinft schreibt in Textdateien. Alle Formatanweisungen von printf gelten analog. Prof. Dr. Nikolaus Wulff 10
Unformatiertes Schreiben typedef struct { char name[40]; char surname[40]; int age; Person; void writepersons(file *stream, int len, Person persons[]) { /** write array size */ fwrite(&len,sizeof(int),1,stream); /** write array at once */ fwrite(persons,sizeof(person),len,stream); fwrite schreibt unformatierte Daten. Stream im Modus wb für write binary öffnen. Bei Arrays erst die Feldlänge len ausgeben, dann geht später das Lesen einfacher... Prof. Dr. Nikolaus Wulff 11
Unformatiertes Lesen Person* readpersons(file *stream, int *len) { Person *persons; /** read array size */ fread(len,sizeof(int),1,stream); /** allocate array */ persons = malloc(*len*sizeof(person)); assert(persons!= NULL); /** read array at once */ fread(persons,sizeof(person),*len,stream); return persons; fread ist das Gegenstück zu fwrite. Zum Lesen wird zunächst die Feldlänge eingelesen Dann Speicher alloziert und die Daten gelesen. Achtung: len wurde als Zeiger übergeben... Prof. Dr. Nikolaus Wulff 12
Weitere IO-Funktionen Neben printf / scanf und fprintf / fscanf gibt es Methoden, die direkt Speicheradressen verwenden: sscanf sprintf Liest formatiert von einer Zeichenkette Schreibt formatiert eine Zeichenkette Zusätzlich gibt es noch analoge Schreibmethoden für variable Argumentlisten aus <stdarg.h>: vprintf, vsprintf, vfprintf Prof. Dr. Nikolaus Wulff 13
Komfortable Debug Messages #define MAXSTRING 512 int debugenabled = 1; FILE *out = stdout; void debug(char* file, int line, const char* fmt,...) { int len; va_list args; char message[maxstring]; char *msg = message; if (debugenabled) { sprintf(msg,"debug %s [%d]: ",file, line); len = strlen(message); msg += len; va_start(args, fmt); vsnprintf(msg, MAXSTRING - len, fmt, args); va_end(args); fprintf (out,"%s",message); Kombinierte Anwendung von sprintf und vsnprintf, um formatierte Debug Nachrichten variabler Länge auszugeben. Prof. Dr. Nikolaus Wulff 14
Zusammenfassung In <stdio.h> sind viele stream -basierte IO Funktionen definiert, die ein einheitliches Schreiben und Lesen ermöglichen. Hierbei ist es dann unerheblich ob dies die Tastatur, die Shell, eine Datei oder Speicheradresse ist. Auf den ersten Blick verwirrend, aber eigentlich genial. Es handelt sich um Quellen und Senken, die durch Ströme verbunden sind... Die Beschäftigung mit dem Prinzip und der Logik dieser Methoden ist sicherlich lohnend, da sie in jeder Programmiersprache immer wieder vorkommen. Prof. Dr. Nikolaus Wulff 15