Die im letzten Abschnitt vorgestellten Files besitzen Komponenten eines beliebigen skalaren oder zusammengesetzten Typs. Dadurch können effizient und kompakt alle Werte der Komponenten auf einem Hintergrundspeicher dargestellt werden. Jedoch werden die Werte in codierter Form (als Bytefolgen) gespeichert. Diese Codierung der Daten ist eine für den Menschen oder Programme in anderen Programmiersprachen ungeeignete Darstellungsform. Da zur Ein- und Ausgabe bevorzugt Zeichenfolgen, wie
Dies ist ein Beispiel für einen Text, der sich über drei Zeilen erstreckt.
verwendet werden, spielen Files mit dem Komponententyp CHAR eine besondere Rolle. In Pascal existiert deshalb ein vordefinierter Typbezeichner:
TYPE TEXT = FILE OF CHAR;
Files des Typs TEXT bestehen also aus einer (beliebig) langen Folge von Zeichen. Sie kennen bereits zwei Filevariablen, die den Typ TEXT besitzen. Es sind dies die im Programmkopf genannten Namen INPUT und OUTPUT, welche folgendermaßen vordefiniert sind:
VAR INPUT, OUTPUT: TEXT;
In Abschnitt 2.2 wurde erklärt, daß INPUT und OUTPUT die Standardeingabe von der Tastatur bzw. die Standardausgabe an den Bildschirm symbolisieren. Eingaben von der Tastatur und Ausgaben an den Bildschirm stellen nichts anderes als Folgen von Zeichen des Typs CHAR dar, die nur sequentiell lesend oder schreibend bearbeitet werden können.
Aber auch alle anderen Peripheriegeräte am C-128 kommunizieren mit dem Computer über Zeichenfolgen, die vom Drucker gedruckt, auf der Floppy gespeichert, und von einem Modem eingelesen werden. Zwar könnte man eine zeichenweise Aus- und Eingabe auch auf Textfiles mit den Standardfileoperationen PUT und GET vornehmen, jedoch existieren einige spezielle Prozeduren und Funktionen in Pascal, mit denen komplette Zeichenfolgen bearbeitet werden können:
In Abschnitt 2.5 wurde nämlich nur eine Kurzform der Prozeduren READ(LN) und WRITE(LN) vorgestellt, in der alle Ausgaben am Bildschirm und alle Eingaben von der Tastatur erfolgen. In der allgemeinen Form dieser Prozeduren muß als erster Parameter in der Parameterliste ein File vom Typ TEXT (also FILE OF CHAR) genannt werden, auf dem die auszugebenden Daten zu speichern sind, oder von dem die Eingabedaten stammen:
WRITE(Filevariable, Ausdruck, Ausdruck, ..., Ausdruck) WRITELN(Filevariable) WRITELN(Filevariable, Ausdruck, Ausdruck, ...., Ausdruck)
Die obigen Prozeduren speichern Zeichenfolgen in dem durch Filevariable spezifizierten File. Die Ausdrücke können wie in Abschnitt 2.5 beschrieben um Formatierungsparameter erweitert werden. Mit der Prozedur WRITELN können Sie den ausgegebenen Text (wie es ebenfalls in Abschnitt 2.5 für den Bildschirm erläutert wurde) in Zeilen gliedern.
PROGRAM TEXTFILE(INPUT,OUTPUT); VAR F: TEXT; S: STRING; BEGIN OPEN(F,8,3, 'TEXT,SEQ,WRITE'); WRITELN(F,'Dies ist ein Beispiel für'); WRITELN(F,'einen Text, der sich'); WRITELN(F,'über drei Zeilen erstreckt.'); CLOSE(F); OPEN(F,8,3, 'TEXT,SEQ,READ'); WHILE NOT EOF(F) DO BEGIN READLN(F,S); WRITELN(S); END; CLOSE(F); END.
Bild 81: Zeilenstrukturen in Textfiles
Das Ende einer Zeile wird in Files (und am Bildschirm) mit einem Steuerzeichen markiert. In Pascal 2.0 ist dies das Zeichen CHR(13) (carriage return im ASCII). Jedoch müssen Sie sich nicht um solche Realisierungsdetails kümmern, da spezielle Funktionen und Prozeduren zur Behandlung der Zeilenstruktur in Textfiles existieren.
Ist der erste Parameter einer WRITE-Anweisung keine Filevariable, so wird das Standardfile OUTPUT zur Ausgabe benutzt:
WRITELN('^':50 , 'ERROR', ERRNUM:3) entspricht WRITELN(OUTPUT,'^':50, 'ERROR', ERRNUM:3)
Wie alle anderen Files auch, müssen Files mit dem Komponententyp CHAR vor Schreiboperationen mit RESET (OPEN in Pascal 2.0) zum Schreiben eröffnet werden. In dem in Bild 82 angegebenen Programm wird wieder das Muster für eine zeilenweise Ausgabe verwendet, wie es Ihnen bereits in Abschnitt 2.5 und bei der Ausgabe von Matrizen in Abschnitt 2.9.3 begegnet ist.
Das Programm gibt eine ASCII-Tabelle (also eine Liste aller darstellbaren Zeichen in der Reihenfolge ihrer Codierung) auf einem Drucker aus. Die Variable I gibt die momentan ausgebene Zeile an. In der inneren For-Anweisung für die Variable J wird der ASCII-Code mit der Schrittweite 15 berechnet. Jede Zeile wird mit WRITELN(D) beendet. Bei der Ausgabe werden die Zeichen mit den Codes zwischen 0 und 31 sowie 127 und 159 nicht gedruckt, da sie am Drucker nur Steuerfunktionen (Schriftartwechsel, Papiervorschub etc.) besitzen.
PROGRAM ASCII (INPUT,OUTPUT); VAR I,J: 0..15; D : TEXT; (* Filevariable zur Ausgabe *) BEGIN OPEN(D,4,0); (* Eröffne den Druckerkanal *) FOR I:= 0 TO 15 DO BEGIN FOR J:= 0 TO 15 DO IF J IN [0,1,8,9] THEN (* Steuerzeichen auslassen *) WRITE(D,' ':3) ELSE WRITE(D,' ',CHR(I+16*J),' '); WRITELN(D); END; CLOSE(D) END.
Bild 82: Ausgabe einer ASCII-Tabelle
Sollten Sie keinen Drucker besitzen, so können Sie die Ausgabe auch auf einem anderen Peripheriegerät vornehmen. Dazu müssen Sie nur im OPEN-Befehl den Geräteparameter (4 für Druckerausgabe) ändern. So besitzt z.B. der Bildschirm die Geräteadresse 3. In diesem Buch können natürlich nicht alle Eigenheiten des Betriebssystems des C-128 besprochen werden. Solche Informationen finden Sie einerseits im BASIC-Handbuch und andererseits bei den Bedienungsanleitungen der Geräte (Drucker, Floppy). Dort wird auch die Bedeutung der Sekundäradresse (0) für jedes Gerät erläutert.
OPEN(D,4,7) Beim Drucker MPS-802 Ausgabe in Kleinschrift OPEN(D,3,0) Ausgabe auf den Bildschirm OPEN(D,1,2,'ASCII') Ausgabe auf ein Kassetten-File mit dem Namen ASCII OPEN(D,8,3,'ASCII,SEQ,WRITE')
Die letzte Angabe erzeugt eine sequentielle Datei mit dem Namen »ASCII« auf der Diskette mit der Geräteadresse 8. Solche Textdateien können Sie übrigens mit dem Editor des Pascal-Systems lesen und schreiben (primary-commands INPUT und OUTPUT s. 4.2.10 und 4.2.11).
Eine sehr nützliche Möglichkeit von Pascal 2.0 besteht darin, die Standardausgabe (OUTPUT) auf ein anderes Gerät als den Bildschirm umzuleiten. Das folgende kleine Programm druckt eine gedämpfte Schwingung auf dem Bildschirm. Bei Bedarf können Sie die Ausgabe jedoch mit dem Prozeduraufruf OPEN(OUTPUT,4,0) auf den Drucker umlenken. Um wieder zur Bildschirmausgabe zurückzuschalten genügt der Prozeduraufruf CLOSE(OUTPUT).
PROGRAM SIMPLECURVE (INPUT,OUTPUT); CONST D = 0.00624; S = 32; H = 34; C = 6.28318; (* 2 * PI *) LIM = 32; VAR X,Y: REAL; I : INTEGER; BEGIN WRITE('Ausgabe auf den Drucker? N' #157); READLN(C); IF C<>'N' THEN OPEN(OUTPUT,4,0); (* Umleitung auf den Drucker *) FOR I:= 0 TO LIM DO BEGIN X:= D*I; Y:= EXP(-X)* SIN(C*X); N:= ROUND(S*Y) + H; WRITE('*':N); END; CLOSE(OUTPUT); (* OUTPUT wieder zum Bildschirm *) END.
Bild 83: Umleitung der Standardausgabe
Natürlich gelten analog die Prozeduren READ und READLN nicht nur für das Standard-Eingabetextfile INPUT. Die allgemeine Syntax der Eingabeoperationen lautet nämlich:
READ(Filevariable, Variable, Variable, ..., Variable) READLN(Filevariable) READLN(Filevariable, Variable, Variable, ..., Variable)
Die Eingabe erfolgt wie in Abschnitt 2.5 zeilenweise. Damit lassen sich also Werte der Typen CHAR, INTEGER, REAL und STRING einlesen. Die Regeln bezüglich der Syntax der eingelesenen Zahlen wurden bereits in Abschnitt 2.5 beschrieben. Wird bei den Prozeduren als erster Parameter keine Filevariable angegeben, so erfolgt die Eingabe vom Standard-Eingabemedium INPUT:
READ(A,X,S)entspricht READ(INPUT,A,X,S)
Sowohl bei der Eingabe von der Tastatur als auch bei jedem anderen Textfile besteht die Möglichkeit zu prüfen, ob bei der Eingabe das Ende einer Zeile erreicht wurde. Die Funktion
EOLN(Filevariable)
(end of line, Zeilenende) liefert ein boolesches Ergebnis. Wurde beim Einlesen einer Zahl oder eines Zeichens vom File Filevariable das Zeilenende erreicht, so ist EOLN(Filevariable) = TRUE.
PROGRAM EOLTEST(INPUT,OUTPUT); VAR I,ZAHL: INTEGER; BEGIN WRITELN('Geben Sie eine Folge von 5 Zahlen '); WRITELN('in verschiedenen Zeilen ein:'); FOR I:= 1 TO 5 DO BEGIN READ(ZAHL); IF EOLN(INPUT) THEN WRITE('Zeilenende nach', ZAHL); END; END.
Bild 84: Erkennen von Zeilenwechseln
In Bild 84 wurde die Funktion EOLN(INPUT) verwendet, um das Ende einer Eingabezeile beim File INPUT (Benutzer betätigt die RETURN-Taste) zu erkennen. Wie bei READ(LN) kann auch bei EOLN die Angabe INPUT entfallen, so daß man in Bild 84 die If-Anweisung abkürzen könnte:
IF EOLN THEN WRITE('Zeilenende nach', ZAHL);
Die Prozedur READLN(F) läßt sich formal durch folgende Anweisungsfolge definieren (die Variable CH ist vom Typ CHAR):
READLN(F) entspricht WHILE NOT EOLN(F) DO READ(F,CH)
Das folgende Programm (Bild 85) demonstriert die Eingabe von Files mit READ. Das sequentielle File mit dem Namen »DATA« auf der Diskette beinhaltet in jeder Zeile mehrere reelle Zahlen. Die Zeilensumme über diese Zahlen soll am Bildschirm angezeigt werden. Hat das File folgenden Inhalt
3.142 22 -0.345 0.33 1 2 3 4 3 -8 -8 -8
soll also die Ausgabe
25.127 10 3 -24
erzeugt werden. Da die Länge einer Zeile und die Länge des Files nicht a priori bekannt sind, werden While- und Repeat-Anweisungen mit den Funktionen EOLN(DATEN) und EOF(DATEN) als Abbruchkriterien verwendet.
PROGRAM ZEILENSUMME (INPUT, OUTPUT); VAR DATEN: TEXT; PROCEDURE ADD(VAR F: TEXT); (* ADDIERE DIE WERTE IN DEN ZEILEN VON F *) (* DRUCKE DIE SUMMEN *) VAR R, SIGMA: REAL; BEGIN WHILE NOT EOF(F) DO BEGIN SIGMA:=0.0; REPEAT READ(F,R); SIGMA:= SIGMA + R; UNTIL EOLN(F); WRITE(SIGMA:10:3); END; END; (* ADD *) BEGIN OPEN(DATEN, 8, 3, 'DATA,SEQ,READ'); ADD(DATEN); WRITELN; CLOSE(DATEN) END.
Bild 85: Schleifen mit EOLN und EOF
Bemerkenswert ist vielleicht noch die Übergabe von Filevariablen als Parameter an Funktionen und Prozeduren, da in diesem Fall Files nur als Variablenparameter übergeben werden dürfen. Die Prozedur ADD hätte also nicht folgendermaßen vereinbart werden dürfen:
PROCEDURE ADD(F: TEXT); FALSCH !
Das Programm in Bild 86 zeigt abschließend, wie man eine Textdatei zeichenweise bearbeitet und gleichzeitig ein File der gleichen Zeilenstruktur erzeugt. Es wird eine Datei gelesen und alle Kleinbuchstaben oder Grafikzeichen (mit Ordinalwerten ORD(CH) >=128) in Großbuchstaben umgewandelt.
PROGRAM KONVERT (INPUT, OUTPUT); VAR EINGABE,AUSGABE: TEXT; NAME : STRING[16]; CH : CHAR; BEGIN WRITE('FILENAMEN :'); READLN(NAME); OPEN(EINGABE, 8, 3, NAME + 'SEQ,READ'); OPEN(AUSGABE, 8, 4, NAME + '.G,SEQ,WRITE'); WHILE NOT EOF(EINGABE) DO BEGIN READ(EINGABE, CH); WHILE NOT EOLN(EINGABE) DO BEGIN IF ORD(CH)>127 THEN CH:= CHR(ORD(CH)-128); WRITE(AUSGABE,CH); READ(EINGABE,CH); END; WRITELN(AUSGABE); END; CLOSE(EINGABE); CLOSE(AUSGABE); END.
Bild 86: Umwandlung einer Datei in Großbuchstaben
Natürlich können Sie das Programm auch mit Files auf anderen Speichermedien testen, indem Sie die Parameter bei OPEN geeignet wählen.
Da jeder Aufruf der Standardprozeduren READ und WRITE neben der eigentlichen Zeichenein- und ausgabe zunächst das Peripheriegerät adressieren muß, kann man die Verarbeitungsgeschwindigkeit des Programmes dadurch erhöhen, daß man jeweils vollständige Zeilen (Strings) einliest und ausgibt und diese im Hauptspeicher bearbeitet:
PROGRAM STRINGKONVERT (INPUT, OUTPUT); VAR EINGABE,AUSGABE: TEXT; NAME : STRING[16]; I : INTEGER; LINE : STRING[255]; BEGIN WRITE('FILENAMEN :'); READLN(NAME); OPEN(EINGABE, 8, 3, NAME + 'SEQ,READ'); OPEN(AUSGABE, 8, 4, NAME + '.G,SEQ,WRITE'); WHILE NOT EOF(EINGABE) DO BEGIN READLN(EINGABE, LINE); FOR I:= 1 TO LENGTH(LINE) DO IF ORD(LINE[I])>127 THEN LINE[I]:= CHR(ORD(LINE[I])-128); WRITELN(AUSGABE, LINE); END; CLOSE(EINGABE); CLOSE(AUSGABE); END.
Bild 87: Umwandlung mit Zeilenpuffer
In aller Deutlichkeit soll nochmals auf die Unterschiede zwischen der Speicherung von Werten in Textfiles und »normalen« Files hingeweisen werden. Möchten Sie z.B. 100 Zahlen des Typs REAL speichern, so könnten Sie folgende Programmstücke verwenden:
PROGRAM WRITEREAL1 (INPUT,OUTPUT); VAR REALS: FILE OF REAL; I : INTEGER; BEGIN OPEN(REALS,8,3,'ZAHLEN,SEQ,WRITE'); FOR I:= 1 TO 100 DO BEGIN REALS^:= 1 / I; PUT(REALS) END; CLOSE(REALS) END. PROGRAM WRITEREAL2 (INPUT,OUTPUT); VAR T : TEXT; I : INTEGER; BEGIN OPEN(T,8,3,'ZAHLEN,SEQ,WRITE'); FOR I:= 1 TO 100 DO WRITE(T, 1/I:15); CLOSE(T) END.
Bild 88: Speicherung reelier Zahlen
Aufgaben
1. Erstellen Sie ein Druckprogramm, das den Inhalt eines Datenfiles, wie es zum Beispiel in Aufgabe 1 in Abschnitt 2.16 beschrieben wurde, formatiert als Liste ausgibt. Finden Sie ein möglichst allgemein verwendbares Verfahren, um jede Seite mit einem Listenkopf (Titel und Seitennummer) zu versehen.
2. Gegeben ist ein Datenfile, das die Umsätze von Vertretern im Bundesgebiet für ein Jahr enthält. Dieses File ist nach dem Feld Postleitzahl aufsteigend sortiert. Drucken Sie eine Liste, die alle Umsätze im Bundesgebiet enthält. Außerdem sollen Zwischensummen gebildet werden, aus denen die Gesamtumsätze in jedem PLZ-Bereich (also z.B. 6000-6999 , 7000-7999 etc.) hervorgehen:
PLZ | Stadt | Vertreter |
Monat
|
Umsatz
|
6000 | Frankfurt/M | Müller |
3
|
323000.00
|
6200 | Wiesbaden | Meier |
5
|
43000.00
|
Mogel |
6
|
12100000.00
|
||
6991 | Wildentierbach | Schulze |
12
|
3.00
|
--------------
|
||||
SUMME: |
426003.00
|
|||
7000 | Stuttgart | Müller |
7
|
4500.00
|
7006 | ... |
3. Eine solche Gruppenkontrolle wie in Aufgabe 2 läßt sich auch mehrstufig anwenden. Innerhalb jedes PLZ-Gebietes könnte man (bei einer entsprechenden Sortierung der Ausgangsdaten) auch eine zusätzliche Aufschlüsselung nach Monatsumsätzen vornehmen. Im Programm muß also ein Vergleich des laufenden mit dem nachfolgenden (Teil-) Schlüssel vorgenommen werden. Das generelle Schema einer zweistufigen Gruppenkontrolle ist also folgendes:
Dateien eröffnen Ersten Satz lesen While NOT Dateiende erreicht DO BEGIN Vorlauf Stufe 2 REPEAT Vorlauf Stufe 1 REPEAT Bearbeite Einzelposten Neuen Satz lesen UNTIL Wechsel 1 Gruppenabschluß UNTIL Wechsel 2 Gruppenabschluß 2 END Dateien schließen
Wechsel 1 bezeichnet im oben skizzierten Beispiel einen Wechsel des Monats, während Wechsel 2 eine Änderung des übergeordneten Gruppenkriteriums (PLZ-Bereich) bedeutet. Wechsel 1 muß natürlich auch durch ein Dateiende und Wechsel 2 hervorgerufen werden. Ein Wechsel 2 wird auch durch das Dateiende provoziert.
Der Vorlauf für eine Gruppe enthält das Löschen von Summenfeldern, den Druck von Überschriften etc., während der Gruppenabschluß z.B. den Druck einer Summenzeile unter einer Gruppe bedeutet.