Claus Schönleber

Hitchhacker´s Guide To PASCAL  [Vol. 1]

 [ zurück | weiter ]
   [ Start | Beispielprogramme Download | Home ]

Inhalt

Compiler
Programmieren
Datentypen,
Variablen,
Standardfunktionen
Logik
Verzweigung,
Strukturierung
Schleifen
Felder (Arrays)
Verteiler: CASE
Zeichenketten
(Strings)
Textdateien
Module
(Prozeduren,
Funktionen)
Anhang
(Operatoren,
abgeleitete
Funktionen)
 
 

 

 

Dateien [1]

Die bisher vorgestellten Datenstrukturen sind alle sehr praktisch, solange man es mit Problemlösungen zu tun hat, die mit einer endlichen oder absehbar großen Datenmenge arbeiten. Schwierig wird es dann, wenn man während der Programmierung nicht absehen kann, wieviel Daten man zu verarbeiten hat. Welcher Art diese Problemstellungen sind, soll hier nicht erläutert werden. Wir werden anhand einiger Beispiele ansatzweise klären, wann dieser Fall auftreten kann. Darüberhinaus sei auf weiterführende Literatur verwiesen. 

Wenn man also mit Verfahren zu tun hat, die es erfordern, daß eine unbekannte Anzahl von Daten bearbeitet werden kann, dann sind die bisher erörterten Strukturen ungeeignet. 

Eine neue Datenstruktur ist also erforderlich. Eine Aneinanderreihung der zu bearbeiteten Daten bietet sich als einfache Lösung an. Man nennt so eine Folge von Daten Sequenz

Die abstrakte Vorstellung, daß sich einzelne Daten zu einer - theoretisch - unendlichen Folge zusammenfügen lassen, ist in einem Computer natürlich nicht realisierbar, da es sich hier um einen endlichen Automaten handelt. Der Adressraum der Hardware (Arbeitsspeicher) ist begrenzt und kann somit keine unendliche Folge aufnehmen. Wir wollen also den Begriff "unendlich" auffassen als maximale Kapazität des benutzten Adressraums. Das ist trotzdem eine Verbesserung gegenüber den anderen Strukturen, da hiermit eine dynamische Speicherverwaltung möglich wird. Die anderen Strukturen sind statisch, da man vorher festlegen muß, wieviel Daten maximal bearbeitet werden sollen. Die Realisation einer Sequenz geschieht realiter in einer Datei

Sequenz
Eine Sequenz ist die Zusammenfassung von Daten beliebiger Art zu einer Datenstruktur, ohne eine maximale Obergrenze für die Anzahl der Daten festzulegen. 
Beispiele
(folgen in diesem Kapitel) 
Datei
Eine Datei oder File ist eine Sequenz, der zur eindeutigen Identifikation ein Name zugewiesen wird. 
Beispiele
(folgen in diesem Kapitel) 
Daten, die mit Hilfe dieser Struktur bearbeitet werden sollen, müssen aus einer Datei gelesen und in eine Datei geschrieben werden können. Um das bewerkstelligen zu können, sind mehrere Arbeitsgänge des Betriebssystems vor dem Zugriff und nach Beendigung des Zugriffs auf die Datei notwendig. Diese Vorbereitungen und abschließenden Arbeitsgänge heißen Öffnen und Schließen der Datei. 

Um auf Daten in einer Sequenz zugreifen zu können, kann man mehrere Methoden anwenden. In diesem Kurs wird nur eine Methode, der sequentielle Zugriff (sequential access method), besprochen. Eine Datei, auf die mit dieser Methode zugegriffen wird, heißt Sequentielle Datei

Sequentielle Dateien 

Sequentielle Datei
Eine Sequentielle Datei ist eine Datei, die entweder nur erstellt oder nur gelesen werden kann. Diese beiden Zustände nennt man Lesen und Schreiben. Eine Datei kann während einer Bearbeitungsphase (zwischen Öffnen und Schließen) nur in einem von beiden Zuständen sein.  [Andere Methoden lassen es zu, daß beide Zustände in beliebigem Wechsel stattfinden können. Das ist jedoch Thema des zweiten Kurses (Vol. 2).]
Beispiel
Textdatei (siehe später)
In der abstrakten Beschreibung von Dateien findet sich keine Vorschrift darüber, welches Speichermedium für Dateien benutzt werden müssen. So kann man die Implementation der Struktur Datei sehr flexibel halten. Dateien werden üblicherweise auf Disketten oder Festplatten angelegt, so daß man auf die begrenzte Speicherkapazität der Zentraleinheit nicht angewiesen ist. 

Die Daten, die in Dateien zusammengefaßt werden, werden in Datensätzen angeordnet. 

Datensatz
Ein Datensatz ist eine sinnvolle, von der Anwendung abhängige Zusammenfassung von Einzeldaten, die ein Objekt
beschreiben. Ein Datensatz kann, je nach Anwendung, Daten desselben Typs oder Daten unterschiedlichen Typs beinhalten. Ein
Datensatz besitzt verschiedene Datenfelder. Der Datensatz einer Textdatei ist die Zeile.
Beispiele
In einer Kundendatei stellt jeder Kunde einen Datensatz dar. In einer Lagerdatei stellt jedes gelagerte Teil einen Datensatz dar. 
Datenfeld
Ein Datenfeld ist Bestandteil eines Datensatzes. Es beinhaltet eine Eigenschaft des Objekts, das durch den vollständigen
Datensatz beschrieben wird. Datenfelder innerhalb eines Datensatzes können von unterschiedlichem Typ sein. In einem Datensatz muß mindestens ein Datenfeld definiert sein. Das Datenfeld einer Textdatei ist nicht vordefiniert. Es kann sich dabei um einzelne Zeichen, aber auch um durch definierte Trennzeichen (Separatoren, z.B. ";") getrennte Zeichenketten handeln.
Beispiel
In einer Personaldatei gibt es unter anderem meist folgende Datenfelder: Nachname, Vorname, Stadt, Adresse, Telefonnummer,... 
Um auf Datensätze zugreifen zu können, wird ein Dateizeiger verwaltet, der immer auf den aktuellen Datensatz zeigt. Der Zeiger kann bei der Sequentiellen Datei nur mit Einschränkungen bewegt werden. Beim Öffnen einer Datei wird der Dateizeiger in der Regel auf den ersten Datensatz gesetzt.

Listen wir noch einmal alle Eigenschaften einer Sequentiellen Datei auf: 

  • Der Dateizeiger kann nur in Vorwärtsrichtung und nur von einem Datensatz zu dessen Nachfolger bewegt werden. 
  • Wenn die Datei im Zustand "Lesen" ist (zum Lesen geöffnet), dann kann sich der Dateizeiger nur innerhalb einer bestehenden Datei bewegen. Er kann immer nur auf den Anfang der Datei oder auf den nächsten Datensatz positioniert werden. 
  • Wenn die Datei zum Schreiben geöffnet ist, kann der Zeiger nur hinter den letzten Datensatz positioniert werden. Der Schreibvorgang kann nur am Dateiende erfolgen. Bei strengen Implementationen kann eine Datei überhaupt nur neu erstellt oder eine bestehende nur gelesen werden. In vielen Implementationen kann aber auch an bestehende Dateien angehängt werden (append). 
  • Das Dateiende wird nach jeder Zeigerbewegung überprüft. Das Ergebnis wird in einer Systemvariablen vom Typ boolean abgespeichert. Sie prüft das Vorhandensein des Zeichens EOF (End Of File). Die Variable wird auf true gesetzt, wenn das Dateiende erreicht ist, auf false, wenn das Dateiende noch nicht erreicht ist. 
  • Die Datensätze in sequentiellen Dateien dürfen unterschiedliche Länge haben. Sie werden durch das Zeilenendezeichen EOL abgeschlossen. Unter DOS/WIndows handelt es sich um die Sequenz Strg-M Strg-J, unter Unix ist es alleine das Zeichen Strg-J.

Textdateien 

Eine besondere Form von sequentiellen Dateien sind die Textdateien. Ein Datensatz einer Textdatei besteht aus einer Zeile aus Zeichen des Zeichensatzes des Computers. Zulässig ist aber nur ein bestimmter Teil des Zeichensatzes, die sogenannten druckbaren Zeichen. Es sind allerdings ein paar Ausnahmen zugelassen; sie werden als Trennzeichen zwischen den Datensätzen und als Dateiendezeichen  benutzt.

Die Zeilen (Datensätze) einer Textdatei können unterschiedlich viele Zeichen enthalten (verschieden lang sein). Deswegen ist die
einzige Methode, mit der man sie bearbeiten kann, die sequentielle Datei. TurboPASCAL kennt nur eine echte sequentielle Datei: Die Textdatei.

Praktische Anwendung von Textdateien 

Wie legt man nun eine Textdatei an, wie liest man Daten aus ihr oder oder wie schreibt man Daten hinein? Das ermöglichen verschiedene Funktionen oder Prozeduren. Sie werden anhand der einzelnen Arbeitsgänge erläutert. 

Die Standardprozeduren, die hier verwendet werden, folgen nicht immer dem von Nikolaus Wirth definierten Standard. Insbesondere die Standardprozedur "assign" ist spezifisch für Microcomputerdialekte wie TurboPASCAL. Im Zweifelsfalle wird in diesem Kurs immer die Realisierung dieses Dialektes besprochen, da die Definitionen des PASCAL-Standards für den Alltagsbetrieb nicht ausreichen. Man muß gerade in solchen Fällen in Betracht ziehen, daß PASCAL von Herrn Wirth zunächst ausschließlich als Lehrbeispiel für moderne Programmiertechniken entwickelt wurde. Die Tatsache, daß PASCAL auch in der kommerziellen Welt eine größere Rolle spielte, war nicht wohl nicht geplant, und deswegen fehlen einige Teile im Standard, die man in der Praxis häufig benötigt. 

Neuanlegen einer Textdatei 

Um eine Textdatei anzulegen, muß man natürlich erst eine Variable dafür definieren. Denn es ist ja möglich, daß man gleich in mehreren Textdateien arbeiten will, und die müssen ja eindeutig identifizierbar sein. 

Die Variablen für (Text-)Dateien nennt man Dateivariablen (wie auch sonst!). Als Wert wird ihnen der Name zugewiesen, den die Datei auf der Platte (Diskette oder Festplatte) erhalten soll. Das Problem dabei ist, daß dafür sehr viel mehr Arbeitsgänge erforderlich sind, als bei der Zuweisung einer Konstanten an eine Variable einfachen Typs. Also entfällt die Zuweisung mit ":=". Man hat deswegen eine Standardprozedur eingerichtet, die diese Zuweisung für uns vornimmt. Sie heißt assign (zuweisen): 

assign (<dateivariable>,<string>) 
In <string> ist der Diskettendateiname in Form einer Zeichenkette (Typ STRING) untergebracht. Er kann mit allen dem Typ STRING zur Verfügung stehenden Mitteln manipuliert werden. Der Name muß den im Betriebssystem geltenden Regeln über die Bildung von Dateinamen genügen. Die <dateivariable> muß mit dem Typ "text" deklariert werden: 
VAR <dateivariable>{,<dateivariable>} : text; 
Ein Beispiel in einem PASCAL-Programmfragment: 
PROGRAM textdatei_beispiel; 
  VAR workfile : text; 
      filename : STRING [255]; 
BEGIN 
  write ('Geben Sie den Dateinamen ein: '); 
  readln (filename); 
  assign (workfile,filename); 
  .
  .
  .
END. 
Alle weiteren Manipulationen mit der Textdatei werden nur noch über die Dateivariable "workfile" abgewickelt. 

Mit der Zuweisung ist aber noch keine Datei auf der Platte eingerichtet. Das besorgt die Standardprozedur "rewrite": 

rewrite (<dateivariable>);
Diese Prozedur ist jedoch gefährlich! Sie richtet nicht nur einen Eintrag im Inhaltsverzeichnis der Platte ein, sie löscht auch den Inhalt einer eventuell vorhandenen Datei mit gleichem Namen. Nachdem "rewrite" ausgeführt wurde, ist auf jeden Fall eine Datei mit dem angegebenen Dateinamen im Inhaltsverzeichnis der Platte angelegt, bei einer schon bestehenden Datei jeglicher Inhalt gelöscht und der Dateizeiger auf den Anfang der Datei gesetzt worden. Außerdem wird im Speicher der Hardware ein sogenannter Pufferbereich eingerichtet, um den Datenfluß zur Platte zu organisieren. Es soll damit verhindert werden, daß der außerordentlich komplizierte Vorgang des Sendens auf die Platte nicht extra für einzelne Zeichen ablaufen muß. Erst wenn der Puffer voll ist, wird der gesamte Pufferinhalt auf die Platte geschrieben. 

Wir ergänzen unser Beispiel von oben: 

PROGRAM textdatei_beispiel; 
  VAR workfile : text; 
      filename : STRING [255]; 
BEGIN 
  write ('Geben Sie den Dateinamen ein: '); 
  readln (filename); 
  assign (workfile,filename); 
  rewrite (workfile); 
  .
  .
  . 
END. 
Damit ist eine Textdatei angelegt. 

Schreiben in eine Textdatei 

Um unsere Datei mit Text zu füllen, bedarf es natürlich irgendwelcher Texteingaben von uns. Für unser Demontrationsbeispiel wollen wir uns darauf beschränken, fünf Namen von der Tastatur zu fordern und sie in die Datei zu schreiben. Dies geschieht mit der gewöhnlichen "writeln"-Prozedur. Sie wird nur um die Angabe der Dateivariablen erweitert: 
writeln (<dateivariable>,<ausdruck>{,<ausdruck>}); 
Also weiter im Beispiel: 
PROGRAM textdatei_beispiel; 
  VAR workfile      : text; 
      filename,name : STRING [255]; 
      index         : integer; 
BEGIN 
  write ('Geben Sie den Dateinamen ein: '); 
  readln (filename); 
  assign (workfile,filename); 
  rewrite (workfile); 
  FOR index := 1 TO 5 DO 
  BEGIN 
    write ('Geben Sie den ',index,'. Namen ein: '); 
    readln (name); 
      (*** Jetzt wird in die Datei geschrieben! ***) 
    writeln (workfile,name); 
  END; 
  .
  .
  .
END. 
Mit "rewrite" haben wir die Datei zum Schreiben geöffnet, nach der Arbeit müssen wir sie wieder schließen. Das nicht nur aus Symmetriegründen, sondern damit der Puffer geleert wird. Wie oben schon erwähnt, wird der Pufferinhalt erst auf die Platte geschrieben, wenn er voll ist. Am Ende der Bearbeitung kann es aber vorkommen, daß er noch nicht ganz voll ist. Würde man jetzt abbrechen, stünde gar nichts oder nur die unvollständige Datei auf der Platte. Das Schließen der Datei ist also das erzwungene Leeren des Puffers nach getaner Arbeit. Danach wird noch das Inhaltsverzeichnis der Platte auf den neuesten Stand gebracht und der Puffer wieder freigegeben. Die Prozedur, die soviel tut, heißt close
PROGRAM textdatei_beispiel; 
  VAR workfile      : text; 
      filename,name : STRING [255]; 
      index         : integer; 
BEGIN 
  write ('Geben Sie den Dateinamen ein: '); 
  readln (filename); 
  assign (workfile,filename); 
  rewrite (workfile); 
  FOR index := 1 TO 5 DO 
  BEGIN 
    write ('Geben Sie den ',index,'. Namen ein: '); 
    readln (name); 
      (*** Jetzt wird in die Datei geschrieben! ***) 
    writeln (workfile,name); 
  END; 
  close (workfile); 
END. 
Dieses Programm ist schon vollständig und legt nach der Eingabe von fünf Zeichenketten eine Textdatei mit fünf Datensätzen (Zeilen) an.
Die Datei kann jetzt mit jedem Texteditor angesehen werden. 

Lesen einer Textdatei 

Wir nehmen an, daß das oben stehende Beispiel ausgeführt worden ist. Dann steht auf der Platte eine Datei mit dem von Ihnen ausgewählten Namen. Wenn jetzt wieder auf die Textdatei zugegriffen werden soll, dann brauchen wir eine weitere Standardprozedur; sie soll die Textdatei zum Lesen öffnen. Ihr Name ist "reset". Beim Öffnen geschieht wiederum einiges: Zuerst wird geprüft, ob sich die erwähnte Datei auf der Platte befindet und angesprochen werden kann; dann wird ein Puffer eingerichtet, diesmal für die umgekehrte Richtung; dann wird der Dateizeiger auf den ersten Datensatz (Zeile) der Datei positioniert. Nun kann man Zeilen lesen. Das besorgt die Standardprozedur "readln", die ebenso um die Dateivariable erweitert wird wie die "Kollegin" writeln. Bauen wir also ein Programm, um die Textdatei auszulesen und den Inhalt auf dem Bildschirm aufzulisten: 
PROGRAM textdatei_beispiel2; 
  VAR workfile      : text; 
      filename,name : STRING [255]; 
      index         : integer; 
BEGIN 
  write ('Geben Sie den Dateinamen ein: '); 
  readln (filename); 
  assign (workfile,filename); 
  reset (workfile); 
  WHILE NOT eof (workfile) DO 
  BEGIN 
    readln (workfile,name); 
    writeln (name); 
  END; 
  close (workfile); 
END. 
Ich hatte Ihnen natürlich noch etwas verschwiegen; das geschah aber nur deswegen, weil ich Ihnen das jetzt, wo Sie es sehen, besser erklären kann: 

Da man beim Lesen aus einer (Text-)Datei nicht wissen kann, wieviel Datensätze in ihr enthalten sind, kann in diesem Fall keine Zählschleife benutzt werden. Man muß also die schon vorher erwähnte Systemvariable benutzen, die das Dateiende (Zeichen EOF) anzeigt. Da ja jeder Schreib- oder Lesevorgang den Dateizeiger weiterbewegt, weiß das System immer, wo es ist. Beim Lesevorgang wird darüberhinaus bei jeder Vorwärtsbewegung geprüft, ob der nächste Datensatz nicht gerade das EOF-Zeichen ist. Tritt dieser Fall ein, ändert sich der Zustand der Systemvariablen (sie ist übrigens das Musterbeispiel eines Flags!). Damit wir nicht soviel damit zu tun haben, nehmen uns eine Standardprozedur und eine Standardfunktion die Arbeit ab: 

  • readln (): Setzt den Dateizeiger weiter und prüft auf Dateiende. Das Ergebnis wird in der Systemvariablen abgespeichert. 
  • eof (): Diese Funktion ermittelt den Wert der von readln verwalteten Systemvariablen. Das Ergebnis ist vom Typ boolean: true, wenn das Dateiende erreicht ist, false, wenn das Dateiende noch nicht erreicht ist. 
Allgemeine Form der Funktion eof (): 
eof (<dateivariable>) 
Eingabetyp ist eine Dateivariable, Ausgabetyp ist boolean. Sie kann in jedem beliebigen logischen Ausdruck verwendet werden. 

Warum benutzt man dann aber eine WHILE-Schleife und nicht REPEAT..UNTIL? Ganz einfach: Eine REPEAT..UNTIL- Schleife prüft die Abbruchbedingung erst nach dem Schleifenblock. Das bedeutet, daß auf jeden Fall zunächst versucht wird, aus der Datei einen Datensatz zu lesen und erst dann wird auf Dateiende geprüft. Ist die Datei aber leer, was oft vorkommen kann, führte das zu einer Fehlermeldung und das Programm bricht ab. Mit der WHILE-Schleife wird die Abbruchbedingung vor dem Eintritt in die Schleife geprüft, was diesen Fehler verhindert. 

Merksatz: Zum Lesen aus einer Datei benutzt man ausschließlich die WHILE-Schleife in Kombination mit der Standardfunktion "eof". 

 

 [ zurück | weiter ]
   (c) 2001 Schoenleber.com