forked from pittlerf/ckurs2017
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dateinverarbeitung.tex
296 lines (282 loc) · 11.9 KB
/
dateinverarbeitung.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
\section{Ein- und Ausgabe}\label{sec:ein_ausgabe}
Ein- und Ausgabe in Dateien, oder allgemein Ein- und Ausgabe auf Geräten ist nicht Teil von \texttt{C} selbst.
Aber die Standardbibliothek stellt diese Funktionalität zur Verfügung.
Sie basiert auf dem Konzept von sogenannten \emph{streams}.
Der Name kommt daher, dass Ein- und Ausgabe immer ein serieller Prozess ist.
D.h., in Einheiten bestimmter kleinster Elemente werden die Daten sukzessive eingelesen oder ausgegeben.
Man kann es sich also als einen Datenstrom vorstellen.
Ein stream in \verb|stdio.h| ist immer vom Typ \verb|FILE*|.
Die Syntax ist die gleiche, wie bei jedem Datentyp
\begin{lstlisting}
FILE * mystream;
\end{lstlisting}
\index{\texttt{FILE}}\index{\texttt{stdio.h}}
\subsection{Standard Ein- und Ausgabe}
Es gibt einige vordefinierte streams, so genannte standard streams:
\begin{enumerate}
\item \verb|stdin| : Standardeingabe \index{\texttt{stdin}}
\item \texttt{stdout} : Standardausgabe \index{\texttt{stdout}}
\item \texttt{stderr} : Standardfehler \index{\texttt{stderr}}
\end{enumerate}
Standard Ein- und Ausgabe haben wir schon implizit mit \texttt{scanf} und \texttt{printf} benutzt.\index{\texttt{scanf}}\index{\texttt{printf}}
Funktionen, die ebenfalls direkt auf \verb|stdin| und \texttt{stdout} arbeiten, sind \verb|getchar| and \verb|putchar|.\index{\texttt{putchar}}\index{\texttt{getchar}}
Sie lesen bzw. schreiben genau ein Zeichen von \verb|stdin| bzw. nach \texttt{stdout}.
Ihre Deklaration in \verb|stdio.h| sieht wie folgt aus:
\begin{lstlisting}
int getchar();
int putchar(int c);
\end{lstlisting}
In \verb|putchar| wird ein impliziter \emph{cast} von \verb|c| nach \verb|unsigned char| durchgeführt.\index{cast!implizit}
\verb|getchar| liest ein Zeichen als \verb|unsigned char| vom \verb|stdin| und führt dann einen impliziten \emph{cast} nach \verb|int| durch.
Damit kann man beispielsweise ein Programm schreiben, dass alle über die Standardeingabe eingegenen Zeichen direkt wieder auf die Standardausgabe \texttt{stdout} ausgibt:
\begin{lstlisting}
#include <stdio.h>
#include <stdlib.h>
int main()
{
int c;
while ((c = getchar()) != EOF)
{
putchar(c);
}
return (0);
}
\end{lstlisting}
Wiederum wird solange eingelesen, bis das Sonderzeichen \verb|EOF| gefunden wird.\index{\texttt{EOF}}
\subsection{Ausgabe in Dateien}
Wenn wir direkt aus einer Datei lesen wollen, so müssen wir einen \emph{stream} bekanntmachen, der mit der Datei verbunden ist.
Dies geht mit Hilfe der Funktion \verb|fopen|.\index{\texttt{fopen}}
Man spricht vom Öffnen der Datei.
Die Definition von \texttt{fopen} sieht wie folgt aus:
\begin{lstlisting}
FILE *fopen(const char *filename, char mode);
\end{lstlisting}
Dabei wird mit \texttt{filename} der Name der Datei als string übergeben.
\texttt{mode} gibt an, für welchen Zweck die Datei geöffnet werden soll:
\begin{itemize}
\itemsep0.5ex
\item \texttt{"w"}: Datei zum Schreiben öffnen. Wenn sie schon existiert, wird sie überschrieben. Wenn sie nicht existiert, wird die Datei erzeugt.
\item \texttt{"r"}: Öffnen einer Datei ausschließlich zum lesen. Der \emph{stream} zeigt auf den Anfang der Datei.
\item \texttt{"{}a"}: Öffnen einer Datei zum Schreiben. Wenn sie schon existiert, wird am Ende der Datei hinzugefügt.
\item \texttt{"w+"}: Öffnen zum Schreiben und Lesen. Wenn die Datei schon existiert, wird sie überschrieben. Der \emph{stream} zeigt auf den Anfang der Datei.
\item \texttt{"r+"}: Öffnen einer Datei zum Schreiben und Lesen. Der \emph{stream} zeigt auf den Anfang der Datei.
\item \texttt{"{}a+"}: Öffnen einer Datei zum Lesen und Hinzufügen. Zum Lesen zeigt der \emph{stream} auf den Anfang der Datei. Geschriebenes wird immer hinzugefügt.
\end{itemize}
Eine Datei, die mit \texttt{fopen} geöffnet wurde, muss mit der Funktion \texttt{fclose} wieder geschlossen werden, die folgende Definition hat:\index{\texttt{fclose}}
\begin{lstlisting}
int fclose(FILE *stream);
\end{lstlisting}
Der Rückgabewert ist \texttt{0}, wenn die Datei erfolgreich geschlossen werden konnte und \texttt{EOF}, wenn nicht.
Nur, wenn \texttt{fclose} aufgerufen wurde ist sichergestellt, dass die Daten auch in die Datei geschrieben wurden.
\subsubsection{Formatierte Ein- und Ausgabe} \label{subsec:FormattedInAndOutput}
Hat man einmal eine Datei geöffnet, muss man sich entscheiden, wie man sie ausliest oder in sie schreibt.
Wir führen hier zunächst die sogenannte formatierte Ein- und Ausgabe ein.
Dabei wird jedes gelesene Byte als ein ASCII Zeichen interpretiert.
Wir haben diese Art von Ein- und Ausgabe bereits mit \verb|printf| und \verb|scanf| kennengelertn.
Die Verallgemeinerung von \verb|scanf| für beliebige \emph{streams} ist \verb|fscanf|\index{\texttt{prinft}}\index{\texttt{scanf}}\index{\texttt{fscanf}}
\begin{lstlisting}
int fscanf(FILE *stream, char *format, ...);
\end{lstlisting}
\verb|fscanf| bekommt als ersten Parameter den \emph{stream} übergeben.
\verb|format| ist eine Zeichekette, wie wir sie schon von \verb|scanf| kennen.
Daher kennen wir auch schon, dass man Variablen über spezielle Zeichfolgen, die mit \% beginnen, aus dem \emph{stream} zuweisen kann.
Dabei stehen unter anderem die folgenden speziellen Zeichen zur Verfügung
\begin{center}
\begin{tabular}{lr}
\hline
\texttt{Platzhalter} & Bedeutung \\\hline
\texttt{\%d} & \texttt{int} \\
\texttt{\%ld} & \texttt{long int} \\
\texttt{\%ud} & \texttt{unsigned int} \\
\texttt{\%f,\%e} & \texttt{float} \\
\texttt{\%lf,\%le} & \texttt{double} \\
\texttt{\%c} & \texttt{char} \\
\texttt{\%s} & eine Zeichenkette\\
\hline
\end{tabular}
\end{center}
Alle anderen Zeichen im Formatierungstext werden eingelesen, aber nicht gespeichert.
Nehmen wir an, wir müssen die Zahlen aus folgender ASCII Datei einlesen:
\begin{verbatim}
1212
1222
999 12212
888
\end{verbatim}
Dies kann mit folgendem Code gemacht werden
\begin{lstlisting}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (argc > 2)
{ // wurde ein Dateiname uebergeben?
fprintf(stderr, "Usage: ./read_int_ascii filename\n");
return (-3);
}
FILE *in = fopen(argv[1], "r"); // Oeffne die Datei
if (in == NULL)
{ // Ueberpruefe auf Fehler
fprintf(stderr, "Error opening the file\n");
return (-2);
}
printf("Lese aus der Datei %s\n", argv[1]);
int d;
while (fscanf(in, "%d", &d) == 1)
{ // lese ein int nach dem anderen ein
printf("%d\n", d);
}
if (fclose(in) != 0)
{ // Schliesse die Datei wieder
printf("Error closing the File\n");
return (-3);
}
return (0);
}
\end{lstlisting}
Es fällt auf, dass wir als Formatierungszeichenkette lediglich \verb|%d| verwenden, und keine Leerzeichen.
Der Grund dafür ist, dass Leerzeichen (und Zeilenumbrüche, Tabs etc.) als Trennzeichen interpretiert werden.
Wie viele Leerzeichen und Zeilenumbrüche zwischen den Zahlen eingefügt sind, spielt für die formatierte Eingabe keine Rolle.
Die Ausgabe des Programms sieht dann wie folgt aus:
\begin{verbatim}
Lese aus der Datei numbers.txt
1212
1222
999
12212
88
\end{verbatim}
Die formatierte Ausgabe funktioniert ähnlich, nur mit der Funktion \verb|fprintf|
\begin{lstlisting}
int fprintf(FILE *stream, char *format, ...);
\end{lstlisting}
Die Spezifizierungszeichen sind die gleichen wie für \verb|fscanf|.
Mit der Ausname von \verb|%lf,%le|, die für \verb|fprintf| nicht zur Verfügung stehen.
\verb|%e| gibt eine Fließkommazahl auf \verb|double| Genauigkeit gerundet im wissenschaftlichen Format aus, \verb|%f| ebenfalls auf \verb|double| Genauigkeit gerundet als Dezimalzahl.
Weiterhin kann man die Anzahl von Stellen etc beeinflussen, zum Beispiel
\begin{lstlisting}
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *out;
out = fopen("quadrats.txt", "w"); // Oeffne die Datei zum Schreiben
if (out == NULL)
{
fprintf(stderr, "Error opening the file\n");
return (-1);
}
for (int i = 1; i <= 10; ++i)
{
fprintf(out, "%.6d\n", i * i); // Schreibe i^2 in die Datei
}
if (fclose(out) != 0)
{ // Schliese die Datei wieder
printf("Error in closing the File\n");
return (-2);
}
return (0);
}
\end{lstlisting}
Nach dem Aufruf enthält die Datei \verb|quadrats.txt| folgendes
\begin{verbatim}
000001
000004
000009
000016
000025
000036
000049
000064
000081
000100
\end{verbatim}
Die Spezifizierung \verb|%.6d| bedeutet, dass die ganze Zahl mit sechs Stellen ausgegeben werden soll.
Dementsprechend werden führende Nullen hinzugefügt.
\subsubsection{Nichtformatierte Ein- und Ausgabe}
Formatierte Ausgabe ist sinnvoll, wenn die Dateien für Menschen lesbar sein sollen und wenn nicht viele Daten gespeichert werden müssen.
Für den Fall von vielen Daten sollte man allerdings auf nichtformatierte Ausgabe zurückgreifen (manchmal auch als binäre Ausgabe bezeichnet).
Dies kann man in C beispielsweise mit den Funktionen \texttt{fread} und \texttt{fwrite} bewerkstelligen:\index{\texttt{fread}}\index{\texttt{fwrite}}
\begin{lstlisting}
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
\end{lstlisting}
Dabei werden \verb|count*size| bytes direkt gelesen und im Speicherbereich abgelegt, auf den \verb|ptr| zeigt, bzw. \verb|count*size| aus dem Speicherbereich direkt geschrieben, ohne, dass dazwischen eine Konvertierung und Interpretation stattfinden würde.
Beide Funktionen liefern die Anzahl der gelesen bzw. geschriebenen Elemente der Größe \verb|size| zurück.
Um den Erfolg der Operationen zu testen, sollte man also prüfen, ob der Rückgabewert dem Wert der Variablen \verb|count| entspricht.
Um mehrfach aus einer Datei lesen zu können, kann man mit\index{\texttt{rewind}}\index{\texttt{fseek}}
\begin{lstlisting}
void rewind(FILE *stream)
\end{lstlisting}
zum Anfang der Datei zurückkehren, oder mit
\begin{lstlisting}
int fseek(FILE *stream, long int offset, int whence)
\end{lstlisting}
zu einem bestimmten Punkt in der Datei gehen.
Bei letzterer Funktion wir der \verb|offset| in Bytes relativ zu \verb|whence| gesetzt.
\verb|whence| kann auf \verb|SEEK_SET|, \verb|SEEK_CUR| or \verb|SEEK_END| gesetzt werden, was Anfang, momentane Position oder Ende in der Datei bedeutet.
\index{\texttt{SEEK\_SET}}\index{\texttt{SEEK\_CUR}}\index{\texttt{SEEK\_END}}
Im folgenden ein Beispiel:
\begin{myexampleprogram}{Beispiel: Summe der Quadrate}
\begin{lstlisting}
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *outin;
int *squares;
const int n = 10;
// Oeffne die Datei
outin = fopen("temporarystorage", "w+");
if (outin == NULL)
{
fprintf(stderr, "Error in opening the file\n");
return (-1);
}
// Speicher fuer squares reservieren
squares = (int *)malloc(sizeof(int) * n);
if (squares == NULL)
{
fprintf(stderr, "Error in allocating memory\n");
return (-2);
}
// Berechne Quadrate und speichere sie in squares
for (int i = 0; i < n; ++i)
{
squares[i] = (i + 1) * (i + 1);
}
// Schreibe squares in die Datei
if (fwrite((void *)squares, sizeof(int), n, outin) != n)
{
printf("Error in writing\n");
return (-3);
}
for (int i = 0; i < n; ++i)
{
squares[i] = 0;
}
rewind(outin); // Geh wieder zum Anfang der Datei
// Lese die Datei wieder ein
if (fread((void *)squares, sizeof(int), n, outin) != n)
{
printf("Error in read\n");
return (-4);
}
for (int i = 0; i < n; ++i)
{
printf("%d\n", squares[i]);
}
// Datei wieder schliessen
if (fclose(outin) != 0)
{
printf("Error in closing file\n");
return (-5);
}
return (0);
}
\end{lstlisting}
\end{myexampleprogram}
Die Datei wird zum Schreiben und Lesen geöffnet.
Dabei wird eine eventuell existierende Datei gleichen Namens überschrieben.
Danach werden die Quadrate von $i=1,...,n$ zuerst in die Datei geschrieben.
Anschließend wird die Datei wieder von Anfang gelesen und die eingelesenen Zahlen auf die Standardausgabe ausgegeben.