← ST-Computer 06 / 1986

EinfĂŒhrung in die Programmiersprache Pascal Teil 5

Kurse

In den vorangegangenen vier Teilen dieses Kurses wurden hauptsĂ€chlich Befehle besprochen, die auch in Sprachen wie FORTRAN oder BASIC vorhanden sind oder nachgebildet werden können. Nun wird es jedoch interessanter, denn die in diesem Teil besprochene Vereinbarungen von Datentypen sind in diesen Sprachen nicht vorhanden. Es ist deshalb an der Zeit fĂŒr Programmierer dieser Sprachen, in diesen Kurs einzusteigen.

Bisher wurden nur die Datentypen REAL, INTEGER, CHAR und *BOOLEAN verwendet. Dies sind die in Pascal vordefinierten Standard-Datentypen. Pascal stellt dem Benutzer jedoch noch eine Vielzahl von weiteren Typen zur VerfĂŒgung. Einen Überblick ĂŒber diese Typen und ihre Zugehörigkeit gibt die Grafik (Bild 1).

BILD 1: Standardfunktionen der einfachen Datentypen

1. Standard-Datentypen

Zur Veranschaulichung werden die auf die Standardtypen anwendbaren Funktionen in einer Tabelle dargestellt. Dabei werden auch die möglichen Datentypen fĂŒr Argument und Ergebnis angegeben (siehe Bild 2).

Die Funktion PRED(x) liefert den VorgĂ€nger des Zeichens x bezĂŒglich der ASCII-Tabelle. SUCC(x) ist dementsprechend die Nachfolgefunktion. Außerdem gibt es noch die Standardfunktion ODD(x). Sie liefert den Wert TRUE, wenn das Argument eine ungerade Zahl ist.

PRED('H') = 'G' SUCC('A') = 'B' ODD(2) = false

EXP(x) ist die Potenzfunktion zur Basis e. LN(x) ist deren Umkehrfunktion, der Logarithmus von x zur Basis e.

Als ErgĂ€nzung sei noch erwĂ€hnt, daß der Datentyp BOOLEAN außer den gebrĂ€uchlichen Vergleichsoperationen >, <, = usw. noch die Operationen AND, OR und NOT erlaubt.

AND (logisches UND)
OR (logisches ODER)
NOT (logisches NICHT)

2. Benutzerdefinierte Datentypen

FĂŒr bestimmte Anwendungen ist es nĂŒtzlich, eigene Datentypen definieren zu können. Dadurch lassen sich zum einen manche Fehler leichter aufspĂŒren und zum anderen kann man Übersicht in die Programm bringen, weil der selbstdefinierte Datentyp seinen Verwendungszweck sichtbar macht.

Das Definieren von Datentypen geschieht im Vereinbarungsteil und dort direkt vor der Variablenvereinbarung. Das SchlĂŒsselwort ist TYPE.

In Pascal gibt es zwei Arten von definierbaren Datentypen, der AufzÀhltyp und der Teilbereichstyp. Der AufzÀhltyp entsteht einfach durch AufzÀhlung der Elemente:

TYPE TAGE =
(MO,DI,MI,DN,FR,SA,SO);

Nun können beliebige Variablen mit diesem Typ definiert werden, z. B.:

VAR TAG : TAGE;

Diese Variable lĂ€ĂŸt sich nicht einfach mit READ einiesen oder mit WRITE ausgeben. Möglich jedoch Zuweisungen und Vergleichsoperationen:

TAG: = mi; if TAG < sa then write('werktag') if TAG in [mo,di,mi,do,fr] then...

Jedes Element des Types WOCHENTAG hat einen Stellenwert bezĂŒglich der Funktion ORD, innerhalb der AufzĂ€hlung. Das erste Element (hier: MO) hat den Wort 0:

ORD(mo) = 0 ORD(so) = 6

Durch die somit klare Reihenfolge der Elemente dieses Datentyps kann er zur Schleifensteuerung eingesetzt werden:

for TAG: = mo to mi do...

Es ist möglich, einen Teil eines Standard- oder eines AufzÀhltyps als neuen Datentyp zu definieren. Diesen nennt man deshalb Teilbereichstyp. Es ist z. B. dann sinnvoll, diesen Typ einzusetzen, wenn man damit den Wertebereich einer Variablen eingrenzt:

TYPE ZIFFER = 1 ... 9;
VAR NUMMER : ZIFFER;

Die Variable NUMMER kann somit nur Werte von 1 bis 9 annehmen. Wird ihr ein anderer Wert zugewiesen, so kommt es zu einer Fehlermeldung.

BILD 2: DATENTYPEN

TYPE
TAGE = (MO,DI,MI,DN,FR,SA,SO);
WERKTAGE = MO ... FR;
WOCHENENDE = SA ... SO;

Bei der Definition von Teilbereichstypen ist zu beachten, daß sich die Typenmengen nicht ĂŒberschneiden (!) oder außerhalb des Bereichs liegen.

Anmerkung: die Standard-Datentypen sind lediglich vordefinierte Typen, sie lassen sich wie folgt erklÀren:

TYPE INTEGER = -32768 ... 32767;
BOOLEAN = (true,false);

Zur Veranschaulichung der soeben besprochenen Typen ist das Programm DEMO (Listing 1) gedacht. Dabei wird auch eine Möglichkeit der Ausgabe von Elementen dieser Typen gezeigt, die nur indirekt erfolgen kann und hier mit einer CASE-Anweisung realisiert wird.

3. Strukturierte Datentypen

Zu den strukturierten Datentypen zÀhlen ARRAY (Feld), STRING (Zeichenkette), SET (Menge), FILE (Datei) und RECORD (Verbund).

Der Typ SET wird benutzt, um eine Menge zu definieren. Die Form ist:

Bezeichner = SET of Menge

z. B.:

TYPE tage = (mo,di,mi,dn,fr,sa,so); VAR arbeitstag : SET of tage;

oder:

VAR arbeitstag : SET of (mo,di,mi,dn,sa,so);

(Anmerkung: die beiden vorherigen Vereinbarungen definieren eine Variable 'arbeitstag’ kann die Werte einer Menge von Wochentagen annehmen. Übergeben werden sie ihr mit der Anweisung:

arbeitstag:=[mo,di,mi,do,fr] oder arbeitstag: = [mo ... fr]
Struktogramm zu Listing 1
program DEMO; { Definition von Aufzaehl- & Unterbereichstypen } type sprachen=(assembler,fortran,cobo1,basic, logo,pascal,c,modula2); hprog = assembler .. modula2; var hprogsprache : hprog; procedure ausgabe(a:sprachen); begin write(ord(a),') '); case a of assembler : writeln('assembler'); basic : writeln('basic'); c : writeln('c'); cobol : writeln('cobol'); fortran : writeln('fortran'); logo : writeln('logo'); modula2 : writeln('modula-2'); pascal : writeln('pascal'); end end; { of AUSGABE } begin { Hauptprogramm } writeln('hoehere PROGRAMMIERSPRACHEN;'); for hprogsprache:=fortran to modula2 do AUSGABE(hprogsprache); writeln; writeln('MASCHINENSPRACHE;'); AUSGABE(assembler) end.

Listing 1

Die Variable ’arbeitstag’ ist somit eine Untermenge von 'tage’. Bei diesem Datentyp besteht eine Ähnlichkeit zum AufzĂ€hl- und Unterbereichstyp. So zum Beispiel die EinschrĂ€nkung, daß man die Variablen nicht direkt einiesen und ausgeben kann.

Um die Anwendung des Typs SET zu zeigen, wird er dem normalen AufzĂ€hlund Unterbereichstyp gegenĂŒbergestellt. Die Programme WOCHENTAGl und WOCHENTAG2 (Listing 2 & 3) liefern das gleiche Ergebnis, sie sind jedoch mit verschiedenen Datentypen aufgebaut.

program WOCHENTAG1; type tage = (mo,di,mi,dn,fr,sa,so); var tag : tage; begin for tag:=mo to so do begin writeln(ord(tag)+1); if tag in [mo..fr] then writeln('Wochentag') else writeln('Wochenende') end end.

Listing 2

Als Operatoren mit Mengen sind die ĂŒblichen mathematischen Mengenoperationen erlaubt:

a+b Vereinigung
(ergibt die Menge aller Elemente von a und b)

a-b Differenz
(alle Elemente von a, die nicht in b enthalten sind)

a*b Schnittmenge
(alle Elemente die in a und b enthalten sind)

a< = b Teilmenge
(ist wahr, wenn a in b enthalten ist)

a> =b Teilmenge
(ist wahr, wenn b in a enthalten ist)

a=b Gleichheit
(ist wahr, wenn a und b gleich sind)

a IN b
(ist wahr, wenn a ein Element von b ist)

program WOCHENTAG2; { SET Demo } type tage = (mo,di,mi,dn,fr,sa,so); var arbeitstag : set of tage; tag : tage; begin arbeitstag:=[mo..fr]; for tag:=mo to so do begin writeln(ord(tag)+1); if tag in arbeitstag then writeln('arbeitstag') else writeln('Wochenende') end end.

Listing 3

ARRAY

Wenn man fĂŒr eine Aufgabe eine feste Anzahl von Elementen des gleichen Typs benötigt, so kann man sie als Feld (ARRAY) anlegen:

Bezeichner: ARRAY[...] of Datentyp

z. B.:

TYPE feld = array[1...10] of integer; VAR zahlen : feld;

bzw.:

VAR zahlen : array[1...10] of integer;

Damit ist ein Feld namens 'zahlen’ festgelegt, das 10 Elemente enthĂ€lt. Die Elemente sind vom Datentyp INTEGER und einzeln aufrufbar:

zahl[1] := 1986 zahl[2*x-1] := 5

Hierbei wird dem ersten Element des Feldes der Wert 1986 zugewiesen. Werte von anderen Datentypen wĂŒrden bei diesem Beispiel zu einer Fehlermeldung fĂŒhren.

Als Index bei der Array-Definition sind alle Grundtypen außer REAL zugelassen:

array ['a'...'z'] of real

Als Datentyp fĂŒr das Array sind alle Grundtypen zugelassen. Außerdem ist es möglich, ein ARRAY[] OF ARRAY[] zu definieren:

TYPE horizontal = array[1...10] of integer; VAR feld : array[1...5] of horizontal;

bzw.

VAR feld : array [1...5] of array[1...10] of integer;

bzw.:

VAR feld: array[1...5,1...10] of integer;

Ein so definiertes Feld nennt man zweidimensional. Eine Zuweisung zu diesem Feld hat dann folgende Form:

feld[1,1] := 9

Um Felder speicherplatzeffizient anzulegen, gibt es in PASCAL den Typ PACKED ARRAY. FĂŒr ihn gelten weitgehend die gleichen Vereinbarungen wie fĂŒr das normale ARRAY.

Ein Beispiel fĂŒr die Verwendung eines ARRAYS zeigt das Programm HORNER, das im dritten Teil dieses Kurses besprochen wurde. Weiter unten wird im Programm ADRESSDATEI ebenfalls ein Feld benutzt.

STRING

Der Datentyp STRING entspricht im wesentlichen einem PACKED ARRAY[1...N] OF CHAR. Er ist in STANDARD-PASCAL nicht definiert, jedoch in den meisten Pascal-Versionen vorhanden.

Bei den meisten Pascal-Compilern hat eine als STRING definierte Variable eine MaximallÀnge von 80 Zeichen. Die Lange kann jedoch im Bereich von 1 bis 255 liegen. Dazu wird definiert:

name : STRINGfx]

In UCSD-PASCAL sind zur String-Manipulation folgende Funktionen vorgesehen:

LENGTH
gibt die aktuelle LĂ€nge des Strings an

POS
Position eines Strings in einem String

STR
Umwandlung einer Integer-Zahl in einen String

COPY
Ausgabe eines Stringteils

DELETE
Löschen von Zeichen aus einem String

INSERT
EinfĂŒgen eines Strings in einen String

CONCAT
Stringaddition

Diese Befehle sind bis auf STR auch im ST-PASCAL von ATARI enthalten und dort oder in einem entsprechenden Handbuch nachzulesen (Syntax usw.).

RECORD

Ein RECORD (Verbund) ist die Zusammenfassung mehrerer Elemente, die auf irgend eine Art und Weise zusammengehören. Die Elemente mĂŒssen nicht vom selben Datentyp sein. Der RECORD hat die Form:

TYPE Recordname = RECORD Element 1 : Datentyp; Element 2 : Datentyp; .. Element n : Datentyp END;

Ein charakteristisches Beispiel ist eine Adressdatei (siehe Listing 4). Unter dem Namen 'ADRESSE’ werden alle Variablen zusammengefaßt und deklariert:

TYPE ADRESSE = RECORD name : string[10]; vorname : string[10]; Strasse : string[20]; plz : integer; ort : string[15]; telefon : string[10]; END;

Die Variablen können von jedem Typ sein, also auch RECORDS und ARRAYS. Damit ist z. B. ein ARRAY of RECORDS möglich.

Den mit der TYPE-Anweisung festgelegten RECORD kann man nun als Datentyp fĂŒr ein Feld verwenden:

VAR FREUNDE : ARRAY [1...100] of ADRESSE;

Damit wird ein Datenfeld mit 100 Elementen vom Typ ADRESSE festgelegt. Auf die einzelnen Elemente des Datenfeldes bzw. RECORDS kann nun einzeln zugegriffen werden:

FREUNDE[1].NAME: = 'schneider'; FREUNDE[1].VORNAME: = 'harald';

Dabei muß als erstes der Name des Feldes angegeben werden (FREUNDE) und dann in Klammern der Index. Nach einem Punkt folgen dann die Variablen des RECORDS. Wird ein RECORD geschachtelt, so folgt nach einem weiteren Punkt die Variable dieses RECORDS:

FREUNDE[1].NAME.VOR: = 'harald':

Das Programm ADRESSDATEI (Listing) zeigt einige Besonderheiten und Vereinfachungen des RECORD-Befehls.

Die Anweisung WITH erlaubt es, den Namen des RECORDS beim Aufrufen wegzulassen:

WITH FREUNDE[nummer] DO begin NAME.VOR:= ... ...

anstelle von:

FREUNDE[nummer].NAME. VOR: =...

Diese Vereinfachung ist vor allem bei großen RECORDS sehr nĂŒtzlich. Die WITH-Anweisung kann auch verschachtelt werden. Sie hat dann folgende Form:

WITH a DO WITH b DO WITH c DO ...

oder verkĂŒrzt:

WITH a,b,c DO ...

Das Programm ADRESSDATEI (Listing 4) zeigt die Verwendung des RECORD-Befehls. Alle RECORDS werden unter TYPE deklariert. Die RECORDS ’datum’ und ’name’ sind ein Unterverbund von ’adresse’. An diesem Beispiel sieht man auch den Unterschied, ob man einen RECORD direkt verschachtelt (’name’) oder ihn, als Datentyp einer RECORD-Variable, extern definiert (’datum’).

Die zweite Methode (extern) ist unĂŒbersichtlicher, außerdem tritt bei ihr ein zusĂ€tzlicher Bezeichner (’datum’) auf, der bei der anderen Methode nicht benötigt wird. Ihr ist jedoch dann der Vorzug zu geben, wenn der Datentyp ’datum’ mehrmals auftritt.

In der Variablendeklaration wird ein Feld ('freunde’) mit hundert Elementen angelegt, dessen Datentyp fĂŒr RECORD ’adresse’ ist. Mit dieser Vereinbarung ist das GerĂŒst fĂŒr die Adressdatei angelegt. Man kann es natĂŒrlich noch beliebig erweitern und verĂ€ndern, das Prinzip bleibt jedoch das gleiche.

Die Dateneingabe wird von einer Prozedur erledigt. Dabei ist den Variablen der READLN-Anweisungen besondere Aufmerksamkeit zu schenken. Das Hauptprogramm ist nur fĂŒr die Eingabe der Adressen vorgesehen. Es kann aber leicht erweitert werden. Die Eingabe der Adressen kann durch Eingabe von ’*’ als Vornamen beendet werden, ansonsten liest das Programm 100 Adressen ein.

Bei der Eingabe bricht das Programm ab, wenn man im Feld ’plz’ oder beim Datum zu große Zahlen oder Buchstaben eingibt.

So, das war’s. Und nun viel Erfolg beim Probieren dieser recht komplexen Befehle.

Der nÀchste Teil des Kurses beschÀftigt sich mit weiteren Datentypen. Das Hauptgewicht liegt dabei bei dem letzten strukturierten Typ FILE. (MN)

STRUKTOGRAMM zu Listing 4
program ADRESSDATEI; TYPE datum = RECORD monat : 1..12; tag : 1..31; jahr : 1986..2000; END; adresse = RECORD name : RECORD vor : string[10]; nach : string[10]; END; strasse : string[20]; plz : integer; ort : string[15]; geburtsdatum : datum; telefon : string[10]; END; VAR freunde : array[1..100] of adresse; nummer : integer; procedure eingabe (nummer : integer); begin with freunde[nummer] do begin write('vorname: '); readln(name.vor); write('nachname: '); readln(name.nach); write('strasse: '); readln(strasse); write('plz: '); readln(plz); write('ort: '); readln(ort); writeln('geburtsdatum:'); write('tag: '); readln(geburtsdatum.tag); write('monat: '); readln(geburtsdatum.monat); write('jahr: '); readln(geburtsdatum.jahr); write('telefon: '); readln(telefon); end; end; begin writeln('ADRESSDATEI'); writeln('-----------'); repeat nummer:=nummer+1; writeln(‘Adresse Nummer:',nummer); eingabe(nummer) until freunde[nummer].name.vor='*'; writeln('ENDE der Eingabe') end.

Listing 4