Home Computer Pascal Kurs Site Map counter

Jetzt lerne ich Pascal... Teil 4

Records

Records sind eine der Neuerung von Pascal, die andere Sprachen wie C übernommen haben. Records sind eigene Typen die aus einzelnen Feldern von verschiedenen Typen bestehen. Records werden in der Typen Deklaration definiert und sehen so aus:
TYPE
Personaltyp = record String [20];
    Gehalt    : Integer;
    Angestellt: Boolean;
end;

Man erkennt hier schon etwas bekanntes: Ein Record setzt sich aus eine Typ und Variablendefinition zusammen. In der ersten Zeile mit dem Schlüsselwort record wird der Typname definiert (hier: personaltyp). Es folgen dann die einzelnen Felder des Records, die genauso wie Variablen festgelegt werden : Name: Typ; Abgeschlossen wird die Deklaration mit dem Schlüsselwort end;. Man nennt das auch einen benutzerdefinierten oder zusammengesetzten Datentyp. Deklariert wird der Record in der Typendeklaration, denn er ist ja ein neuer Datentyp

Nun kann dieser neue Typ Personaltyp für eigene Variablen verwendet werden. Dies geschieht indem man in der var Sektion eine neue Variable dieses Types anlegt:

var
mitarbeiter : Personaltyp
personal : array [1..100] of Personaltyp;

Hier wird gleich ein ganzes Array (eine Firma hat ja meistens mehr als einen Mitarbeiter...) aus diesem Typ definiert.

Was kann man mit Records machen?

Zum einen kann man bei Records wie bei Variablen auf die einzelnen Felder zugreifen. Dies ist nichts neues. Man muss sich nur umgewöhnen, das dies nicht direkt geht sondern über den Umweg Rekordname.Feldname, d.h. man stellt den Recordname mit einem Punkt dem Feldnamen voraus. Um auf ein Gehaltsfeld des Arrays oben zuzugreifen wird man formulieren:
mitarbeiter.name'Lieschen Müller'
personal[11].gehalt:=3500;

Sind Records Bestandteil von Records, was vor allem bei Objekten in Delphi oft vorkommt, so wird das ganze länglich und auch unübersichtlich. Hier gibt es die WITH Anweisung die einen Record öffnet:

WITH personal[i] DO
BEGIN
gehalt:=1000;
Name:='Bernd Leitenberger';
Angestellt:=true;
END;

Die WITH Anweisung öffnet einen Record. Wie bei FOR umfasst sie nur die nächste Anweisung, man muss also in der Regel einen Block mit begin..end einfügen. Man kann nun auf die einzelnen Felder zugreifen, ohne den Record anzugeben. In C/C++ und Java wäre ich auch dafür dankbar, denn erspart Schreibarbeit und macht das Programm übersichtlicher. Doch wichtiger sind Operationen die man mit ganzen Records machen kann. Wenn Sie das obige Beispiel sich ansehen so ist klar, das in diesem Record zusammengehörende Werte sind. Jeder Angestellte hat einen Namen, ein Gehalt und ist fest angestellt oder nur freier Mitarbeiter. Verlässt nun ein Mitarbeiter ihre Firm so müssten sie bei 3 Arrays mit den einzelnen Feldern jeweils eines löschen, also 3 mal dieselbe Operation machen.

Records dagegen kann man auch als ganze Einheiten auffassen, d.h. man kann alle Felder die in einem Record enthalten sind zusammen löschen, zuweisen, kopieren. So ist der Zugriff wesentlich vereinfacht. Wenn Sie mit einem Record arbeiten, so arbeiten sie einfach mit dem Recordnamen anstatt den Feldnamen:

VAR neuer_mitarbeiter: Personaltyp;

....

personal[i]:=neuer_mitarbeiter;

...

Hier wird ein neuer Mitarbeiter aufgenommen der einfach durch eine einfache Zuweisung vorgenommen wird. Will man die Personal Liste sortieren so würde man das so machen:

for i:=1 to 99 do
for j:=i+1 to 100 do
if personal[i].name>personal[j].name then
begin
  neuer_mitarbeiter:=personal[j];
  personal[j]:=personal[i];
  personal[i]:=neuer_mitarbeiter;
end;

Bei diesem einfachen Sortieralgorithmus wird neuer_mitarbeiter als Zwischenspeicher benutzt um die Mitarbeiter jeweils auszutauschen. Noch wichtiger sind Records bei Dateien, doch dazu später mehr.

Zeiger

Pointer oder Zeiger sind etwas um was man als Anfänger einen großen Bogen macht, und den Sinn auch nicht ganz einsieht. Wenn man wie der Autor unter einem 16 Bit Turbo Pascal arbeiten gelernt hat, braucht man allerdings Zeiger wenn man größere Daten bearbeiten will, da normale Daten auf max. 64 K beschränkt waren. Zeiger haben ihren Einsatz wenn man temporär an vielen Stellen Größere Daten braucht und das Programm selbst nicht nun 5 mal den Speicherplatz anlegen soll, den man an 5 Stellen braucht - aber nie gleichzeitig. Darüber hinaus sind Zeiger wichtig für einige Grundstrukturen für effiziente Algorithmen wie Bäume und Listen.

Zeiger oder Pointer sind Variablen die als Wert eine Speicheradresse haben. Ihre Größe hängt daher vom Betriebssystem ab, unter DOS/Windows sind es immer 4 Byte. Ein Zeiger zeigt (daher die Name) auf eine Variable:

var S : string;
P : ^string;

begin
S:='Hallo Welt';
P^:=S;
WriteLn(P^);
end.
In diesem Fall zeigt P auf S. Der Inhalt von P ist die Adresse des ersten Bytes von S. Man unterscheidet von nun an zwei Dinge: Der Inhalt des Zeigers ist die Adresse eines Objektes. Ein Objekt ist eine Variable mit einem Inhalt. Der springende Punkt ist das man nun zwei Möglichkeiten hat: Zeiger sind in Pascal nicht so schwierig wie in C, wo nichts ohne sie läuft. C Programmierer bilden sich daher etwas darauf ein und reden dann von effektiver Programmierung. Doch arbeitet Pascal ohne Zeiger? Natürlich nicht, nur merken Sie nichts davon. Wenn Sie in einer Funktion einen VAR Parameter übergeben:

procedure Veraednere(VAR Wert)

um einen Wert zu ändern so übergeben Sie einen Zeiger und arbeiten mit einem Zeiger - Genauso wie in C. Nur muss dort der Programmierer selbst immer aufpassen ob er Änderungen am Zeiger macht oder an der Variable auf die der Zeiger zeigt.

Wie man Zeiger erzeugt und löscht

Anders als normale Variablen hat man bei einer Zeigervariablen nur die Adresse auf Daten, nicht jedoch den Speicherplatz für die Daten selbst. Man könnte nun dem Zeiger die Adresse einer schon existierenden Variablen zuweisen - so macht man es in C, wo Zeiger zwar häufig, aber nicht dynamisch wie in Pascal sind. Dynamisch heißt man kann zur Laufzeit Speicherplatz anfordern. Der Vorteil ist, das man zur Laufzeit nur soviel Speicher braucht wie aktuell benötigt wird. Unter 16 Bit war dies essentiell. Wozu dies? Nun stellen Sie sich vor, sie schreiben einen Texteditor, Sie wissen nie genau wie groß die Datei ist, die sie bearbeiten. Sie können aber ohne Problem ein großes Array von Zeigern anlegen. Wenn sie eine neue Zeile benötigen, lassen Sie jeweils einen Eintrag auf eine String Variable zeigen die dann dynamisch angelegt wird. Da ein Zeiger nur 4 Byte belegt benötigt das Array nur wenig Speicher im Vergleich zu einem String array.

Ein Zeiger wird so angelegt:

New(Zeiger);

Zeiger ist eine Zeigervariable Bsp.:

Type Zeile string[80];

var zeiger Zeile;

begin
New(Zeiger);
Zeiger^:='Hallo';
writeln(zeiger^);
dispose(zeiger);
end.

New macht folgendes: Es erkennt den Zeigertyp (in diesem Fall Zeiger auf 80 Zeichen), holt sich vom Speicher soviel Bytes wie der Typ benötigt (hier also 80 Bytes) und weist dem Zeiger die Adresse zu auf diese 80 Bytes. Danach (und erst jetzt) zeigt der Zeiger auf einen Bereich denn man mit Daten füllen kann. Hier wird also der String "Hello" dort gespeichert und dann ausgegeben. Zuletzt ruft man die zweite wichtige Prozedur auf: Dispose - sie gibt den Speicher wieder frei, danach kann man nicht mehr auf den Zeiger zugreifen, da er nicht mehr auf den Speicher zeigt.

Also es ist Prinzipiell ganz einfach mit Zeigern:

  1. Zeiger vereinbaren: Geht mit ^Typname: Merke: Bei der Vereinbarung ist das ^ vor dem Typ!
  2. Speicherplatz mit New(Zeiger) anfordern
  3. Nun kann der Zeiger benutzt werden. Die Daten auf die er zeigt bekommt man indem man nun das ^ nach dem Zeigernamen setzt. Zeiger ist eine Variable die eine Adresse enthält: Zeiger^ sind die Daten die an der Adresse stehen.
  4. Freigeben des Speichers mit Dispose.
Zugegeben, das ist noch etwas wenig praxisnah, da Zeiger erst wichtig werden, wenn sie an Listen oder Bäume herangehen oder in Delphi Programmieren. Dort arbeiten sie dauernd mit Zeigern - nur merken Sie es nicht.

Dateien

Nun möchte man ab und an auch etwas speichern. Dann werden Dateien wichtig. Unter Pascal gibt es 3 Arten von Dateizugriffen: Fangen wir mal an mit Textdateien, die kennen sie sicher schon. Eine Textdatei ist eine ungeordnete Sammlung von ASCII Zeichen, eben Text, oder aber auch Zahlen als Text. Ihr Merkmal ist, das sie auch ein Mensch am Bildschirm lesen kann. Neben Dokumenten sind z.B. auch die Windows INI Dateien Textdateien.

Eine Textdatei wird zeilenweise gelesen. Dies geschieht mit Read bzw. Readln und Write bzw. Writeln. Im Prinzip funktionieren Textdateien wie die Eingaben über die Tastatur. Der häufigste Fall ist das Einlesen von Strings, aber auch Fliesskommazahlen oder Integer Werte können so eingelesen werden, dann wird automatisch konvertiert, sofern möglich.

Es gibt 5 Dinge bei der Bearbeitung einer Datei zu tun:

Nein keine Angst, das ist ganz einfach. Hier das einfachste Dateihandling Programm:
program datei_t;

var Datei : Text; {Dateivariable deklariert}
I : integer;

begin
assign(datei,'Test.txt'); {Datei mit "Test.txt" verknüpfen}
rewrite(datei); {Datei zum schreiben öffnen}
for i:=1 to 10 do writeln(datei,i); {Werte von 1 bis 10 in Datei schreiben}
close(datei); {Datei schließen - erst jetzt wird auf die Disk geschrieben!}

reset(datei); {Datei nun zum Lesen öffnen - die Verknüpfung ist nicht nochmals nötig!}
while not eof(datei) do {solange wie das Dateiende (EOF = End of File) nicht erreicht ist}
readln(datei,i); {Wert einlesen}
Close(datei);
erase(datei); {war nur ein Test die Datei kann nun gelöscht werden}
end;

In diesem Beispiel sieht man die auch die elementaren Dateifunktionen:

Typisierte Dateien:

Textdateien sind ganz gut und schön, aber eine Adressverwaltung bauen Sie doch eher aus Records auf oder? Viel leistungsfähigere Funktionen gibt es in Pascal für Typisierte Dateien. Eine Typisierte Datei ist eine Datei aus einem Typ. Obgleich sie jeden Typ benützen können, machen die einfachen Typen wie Byte, Integer oder Double in der Praxis wenig Sinn. Sie besteht also aus einer festen Struktur und damit kann man auch auf einzelne Elemente zugreifen ohne die Datei sequentiell zu lesen. Daher sind Records ideal zum Speichern in Dateien. Sie müssen sich nun nur noch ein paar Unterschiede zu Textdateien merken:

Untypisierte Dateien

Wenn Sie Dateien mit binärem Inhalt wie z.B. Bilder bearbeiten, so arbeiten sie mit untypiserten Dateien, d.h. Dateien ohne Typ. Diese werden allerdings nun anders eingelesen als typisierte. Binäre Dateien werden Blockweise eingelesen, so wie auf ihrer Festplatte die Dateien als einzelne Sektoren gespeichert werden.

Deklariert werden untypisierte Dateien als FILE. Das OF Typname entfällt natürlich weil sie keinem Typ gehorchen (Bitte beachten Sie: Ein FILE OF BYTE ist eine typisierte Datei auf die Sie wahlfrei zugreifen können, FILE dagegen eine untypisierte Datei).

Beim Öffnen mit Reset oder Rewrite ist ein zweiter Parameter anzugeben der angibt wieviel Bytes ein Record umfasst, die kleinste Einheit auf die zugegriffen wird. Üblicherweise will man Byteweise zugreifen und wird hier z.B. Angeben Reset(datei,1). Lässt man den Parameter weg so wird 128 angenommen, was selten das ist, was man wünscht.

Lesen und schreiben tut man mit den Prozeduren Blockwrite(dateivariable,VAR Puffer,Puffergröße,VAR gelesene_Bytes) und Blockwrite(dateivariable,VAR Puffer,geschriebene_Bytes).

Beispiel: Eine Routine die eine Datei kopiert:

procedure Kopier(Datei1,Datei2 : String);

Type Puffer Array [1..60000] of Byte;

VAR File1,file2 : File;
Pufzeiger : ^Puffer;
anzahl_bytes : longint;

begin
Assign(file1,datei1);
Assign(file2,datei2);
{$I-}
reset(file1,1);
if ioresult>0 then exit;
{$I+}
Rewrite(file2,1);
New(Pufzeiger);
while not eof(file1) do
begin
blockread(file1,pufzeiger^,sizeof(pufzeiger^),anzahl_bytes);
blockwrite(file1,pufzeiger^,anzahl_bytes);
end;
dispose(pufzeiger);
close(file1);
close(file2);
end;

Diese Beispiel zeigt auch wie man eine Zeigervariable benutzt: Es wäre reine Verschwendung 60 Kilobytes für einen Puffer zu reservieren, der nur kurz innerhalb der Prozedur benötigt wird. Wenn Sie einmal das Lesen einer Textdatei und einer untypisierten Datei vergleichen, so werden sie sehen, das untypisierte Dateien erheblich schneller sind. Das hat zwei Gründe. Zum einen muss der Inhalt nicht interpretiert werden, d.h. es wird nicht nach Zeilenende gesucht und dann versucht die Zeile in Variablen zu zerlegen und diese zuzuweisen. Zum zweiten arbeiten Textdateien mit einem sehr kleinen Standardpuffer von 128 Bytes, das ist kleiner als die Clustergröße mit der ihre Festplatte arbeitet (typische Werte hier 4-16 K). Zumindest letzteres kann man aber beseitigen indem man einen Textbuffer anbietet:

var buffer = Array [1..8192] of Byte {8 K Buffer}
                            ...
                        Assign(Datei,Dateiname);
                        Settextbuf(Datei,buffer);

Mit SetTextbuf (Dateivariable,Array,Arraygröße) kann man einen Buffer zuordnen, der dann benutzt wird. Es sind zwei Dinge zu beachten: Das Zuordnen muss vor dem ersten Zugriff geschehen - Am besten nach Assign, oder direkt nach Rewrite,Append oder Reset, aber vor dem ersten Read/Write. Zum zweiten muss der Puffer über die Lebensdauer einer Datei existieren. Ein beliebter Fehler ist es eine Datei in Prozedur A zu öffnen und in Prozedur B zu bearbeiten. Wenn der Puffer hier in A deklariert ist, so liegt er auf dem Stack und wird bei Prozedur B mit lokalen Variablen überschrieben!

Compiler Switches

Sie werden sich wundern was für ein Kommentar {$I-} sein soll. Das ist kein normaler Kommentar. Alle Kommentare mit die {$ beginnen sind Compiler Anweisungen. Viele kann man davon im Menü einstellen. Einige aber die lokal wirken, muss man im Programm selbst setzen. Hier ist es der Compilerswitch für die I/O Überprüfung. Er schaltet die I/O Prüfung ab, wenn eine Datei zum Lesen geöffnet wird. Der häufigste I/O Fehler ist der, das eine Datei nicht vorhanden ist, was einen Fehler verursacht. Dieser steht in der Variable IORESULT. Hier ist nur wichtig, das diese 0 ist wenn alles ok ist und sonst ein Fehler (größer 0) dort steht. In diesem Fall wird die Prozedur verlassen.

Es gibt im Prinzip zwei Compiler Switchsorten:

lokale: Können in einer Quelltextdatei mehrmals geändert werden. Der $I Switch für die Prüfung von I/O Results ist so einer. Mann wird ihn an kritischen Stellen nutzen und an anderen ausschalten. Auch bedingte Compilierung (Teile des Quelltexts compilieren und andere nicht je nach Einstellungen) gehören dazu.

Globale: Wirken auf das ganze Programm. z.B. Das Einschalten der 80286 Befehle oder die Verwendung eines nummerischen Coprozessors. Diese können nur einmal im Programm gesetzt werden.

Die wichtigsten Compiler Switches

Es gibt sehr viele die auch alle ihre Berechtigung haben. Hier möchte ich nur die ansprechen mit denen man als Anfänger am häufigsten zu tun hat:

$R: Bereichsprüfung

Der Compiler bricht ab wenn man einen Wertebereich verlässt. Es gibt hier vor allem zwei Fälle wo dies zum Tragen kommt:

$Q: Überlaufsprüfung

Ähnlich wie die Bereichsprüfung, jedoch bei einfachen Variablen: Wenn Sie zu einer Smallint Variable (Wertebereich -127... 128) die den Wert 120 hat, 10 addieren passt das Ergebnis nicht mehr in 1 Byte und es ergibt sich nicht der Wert 130 sondern -126, das ist wohl nicht das was sie haben wollten. $Q macht Sie darauf aufmerksam. Als guter Programmierer verwenden Sie aber doch immer etwas größere Variablen oder Teilbereichstypen oder?

$V: Strenge VAR Strings

Wenn Sie Strings an eine Prozedur als VAR Parameter übergeben müssen nach Standard Pascal der formale Parameter und der Aktuelle übereinstimmen. Bei einem Funktionskopf procedure x(var y : string[10]) wäre es nicht erlaubt, diesen mit einem String[20] oder String [11] aufzurufen. Dieser Switch erlaubt es das auszuschalten. Wenn sie mit TP 7.0 und höher arbeiten brauchen sie ihn gar nicht, denn es gibt hier die leistungsfähigeren offenen Strings.

$S: Stack Überprüfung

Diesen Schalter braucht man eigentlich immer. Der Hintergrund: Es gibt ein Stacksegement in dem bei Aufruf einer Funktion lokale Variablen, Parameter etc. gespeichert werden. Hat man das zu klein eingestellt so verabschiedet sich das Programm recht endgültig zumeist auch noch der Debugger und Computer dazu. Das tritt leider erst auf, wenn das Programm mal an eine Stelle kommt wo viele lokale Variable vorliegen. Mit Stacküberprüfung bekommt man noch eine schöne Fehlermeldung und kann dann den Wert für das Stacksegment vergrößern.

$B: Boolsche Ausdrücke:

Die schon erwähnte Auswertung nach dem Kurzschlussprinzip. Bestimmt ob ein Ausdruck der Form (expr1 or expr2) noch ausgewertet wird wenn expr1 schon wahr ist und man expr2 somit nicht mehr braucht. Manche Sachen Funktionieren nur mit $B- andere nur mit $B+, was hängt von ihrem Programmierstill ab.

$I: IO Prüfung ein oder aus

Wird typischerweise bei einem Reset / Rewrite eingesetzt. Um festzustellen ob eine Datei geöffnet werden kann oder nicht

Machen Sie sich mit dem Compiler Switches vertraut. Sie sind ihre Freunde. Anders als in C können sie auf Überlauf prüfen lassen oder ob eine Variable ihren Definitionsbereich verlassen hat. So was ist beim Debuggen sehr nützlich. Erst wenn sie das Programm fehlerfrei haben schalten sie die Überprüfungen aus.


Zum Thema Computer ist auch von mir ein Buch erschienen. "Computergeschichte(n)" beinhaltet, das was der Titel aussagt: einzelne Episoden aus der Frühzeit des PC. Es sind Episoden aus den Lebensäufen von Ed Roberts, Bill Gates, Steve Jobs, Stephen Wozniak, Gary Kildall, Adam Osborne, Jack Tramiel und Chuck Peddle und wie sie den PC schufen.

Das Buch wird abgerundet durch eine kurze Erklärung der Computertechnik vor dem PC, sowie einer Zusammenfassung was danach geschah, als die Claims abgesteckt waren. Ich habe versucht ein Buch zu schreiben, dass sie dahingehend von anderen Büchern abhebt, dass es nicht nur Geschichte erzählt sondern auch erklärt warum bestimmte Produkte erfolgreich waren, also auf die Technik eingeht.

Die 2014 erschienene zweite Auflage wurde aktualisiert und leicht erweitert. Die umfangreichste Änderung ist ein 60 Seiten starkes Kapitel über Seymour Cray und die von ihm entworfenen Supercomputer. Bedingt durch Preissenkungen bei Neuauflagen ist es mit 19,90 Euro trotz gestiegenem Umfang um 5 Euro billiger als die erste Auflage. Es ist auch als e-Book für 10,99 Euro erschienen.

Mehr über das Buch auf dieser eigenen Seite.

Sitemap Kontakt Neues Hier werben Bücher vom Autor Buchempfehlungen Top 99