Modula-2 Kurs, Teil 3

Herzlich willkommen bei der dritten Runde in unserem Modula-2-Kurs. Sie werden diesmal einige Erweiterungen der Datentypen und der Anweisungsformen für Modula-Programme kennenlernen. Doch zunächst zurück zu den Aufgaben am Ende der letzten Folge.

Wenn Sie die vor einem Monat vorgestellten Grundlagen durchgearbeitet haben, sollten Sie die Hausaufgaben leicht gelöst haben. Die Antworten stehen wie immer im Kasten. Bei richtigen Lösungen können wir uns zusammen mit weitergehenden Daten- und Kontrollstrukturen befassen.

Im letzten Teil dieser Serie haben Sie die meisten der Basistypen kennengelernt. Typisch dabei war, daß es sich immer um eine Variable handelte, die genau ein Objekt des gleichen Typs aufnehmen konnte. Damit lassen sich allerdings noch nicht alle Programmieraufgaben lösen. Stellen Sie sich eine Reihe von statistischen Daten vor, für jeden Monat einen Wert. Nehmen wir an, die Zahlen entsprechen Ihren monatlichen Telefonrechnungen, und Sie wollen beispielsweise eine Jahressumme oder die durchschnittlichen Kosten ermitteln.

Mit den bisherigen Mitteln würde Ihr Programm zur Ermittlung Ihrer jährlichen Telefonkosten ungefähr wie in Listing 3 aussehen.

Sie müssen also für jeden der Monatswerte einen eigenen Bezeichner vergeben und eine extra Deklaration notieren. Dabei handelt es sich immer um Werte des gleichen Typs, die logisch Zusammenhängen. In einem solchen Fall können Sie die Datenstruktur Feld verwenden.

MODULE Rechnung;

FROM ReallnOut IMPORT WriteReal ;

VAR TelJanuar, TelFebruar, TelMaerz, TelApril, TelMai, TelJuni, 
    TelJuli, TelAugust, TelSeptember, TelOktober, TelNovember, 
    TelDezember, TelJahr : REAL ;
BEGIN
    ...
    TelJahr:=TelJanuar+TelFebruar+TelMaerz+TelApril+TelMai+
             TelJuni+TelJuli+TelAugust+TelSeptember+
             TelOktober+TelNovember+TelDezember;
    WriteReal(TelJahr,5);
END Rechnung.

Listing 3

Felder

Ein Feld ist eine geordnete Reihe von Objekten gleichen Typs. Um im Bild der Schubladen zu bleiben: Ein Feld ist eine große Schublade, in der sich nebeneinander angeordnet weitere Schubladen befinden. Es handelt sich damit um einen zusammengesetzten Typen. Wie werden nun die “Unterschubladen" bezeichnet?

In einem Feld sind alle Elemente der Reihe nach durchnumeriert und werden durch ihre Nummer bezeichnet. Die Ordnungszahl ist der Index eines Feldelements. Für alle Elemente eines Feldes ist der Feldname der Oberbegriff, der durch den Index ergänzt wird.

In Modula werden Felder als Variablen vom Typ ARRAY deklariert. Dazu kommt eine Angabe über die Anzahl der “Unterschubladen” und über ihren Typ. Die zwölf Monatsabrechnungen lassen sich in eine Feld-Variable packen:

VAR Tel: ARRAY [1..12] OF REAL;

Die Deklaration vereinbart eine Variable mit dem Namen “Tel”, die zwölf Elemente vom Typ REAL enthält. Diese Objekte sind von 1 bis 12 durchnumeriert. Im Programm wird dann der Wert für den ersten Monat mit “Tel[1]” angesprochen. 1 ist dabei der Index des Wertes. Dementsprechend wäre die November-Abrechnung in “Tel[11]” wiederzufinden.

Für die Indizes und die Deklaration der Index-Bereiche müssen abzählbare Konstanten verwendet werden. Anfang und Ende des gewünschten Bereiches werden mit getrennt. Abzählbare Bereiche müssen nicht unbedingt wie hier von positiven ganzzahligen Konstanten begrenzt werden. Ebenfalls möglich ist eine Deklaration

VAR Werte: ARRAY [-100..100] OF REAL ;

Der Typ des Index’ ist hierbei allerdings INTEGER. Setzen Sie in einem Ausdruck eine Variable zur Angabe des Index' ein, muß sie hier INTEGER sein, während im ersten Beispiel sowohl INTEGERS als auch CARDINALs möglich sind.

Der dritte Typ mit einem abzählbaren Wertebereich, den Sie kennen, ist BOOLEAN. Modula-2 erlaubt auch eine Deklaration

VAR Ergebnis: ARRAY [FALSE..TRUE] OF INTEGER:

Für den Index muß dann in Ausdrücken eine Variable oder Konstante vom Typ BOOLEAN eingesetzt werden. Felder mit CHAR als Indextyp sind ebenfalls möglich.

Die bis jetzt beschriebenen Felder verfügten nur über einen Index. Es handelte sich um eindimensionale Felder, quasi jeweils eine Aneinanderreihung von Variablen. Für ein Schachprogramm z.B. braucht man ein Spielbrett, das im Rechner als zweidimensionales Feld dargestellt wird.

Ein zweidimensionales Feld ist in Modula ein eindimensionales Feld, dessen Elemente wiederum eindimensionale Felder sind. Entsprechend werden sie in der Deklaration notiert:

VAR Brett: ARRAY [1..8J OF ARRAY [1..8] OF INTEGER;

Das Schachbrett steht hier als ein Feld von Reihen. Jede Reihe ist ein Feld von Spielpositionen. Bei der Verwendung im Programm werden die Indizes nacheinander durch Kommata getrennt geschrieben:

Brett[1,4]:=1;

Der Typ von “Brett[ 1,4]” ist hier INTEGER. Es ist ebenso möglich, auf eine komplette Reihe zuzugreifen:

Brett[4]:=Brett[3];

Da nur ein Index angegeben ist, hat “Brett[3]" den Typ “ARRAY [1..8] OF INTEGER“. Sieht man die Deklaration eines Feldes mit mehreren Dimensionen als Schachtelung von eindimensionalen Feldern an, geht jeder Index eine Schachtelungstiefe weiter. Natürlich können in dieser Schachtelung die Typen der Indexbereiche unterschiedlich sein. Eine Deklaration

VAR Kompliziert: ARRAY [-1..1] OF ARRAY [FALSE..TRUE] OF REAL;

ist also korrekt. Gültige Zuweisungen wären:

Kompliziert[-1,TRUE] :=0.0;
Kompliziert [-1,TRUE] :=Kompliziert[1,FALSE]

Aber nicht

Kompliziert[-1]:=Kompliziert[1,TRUE];

oder gar

Kompliziert[-1]:=FALSE;

Im ersten Fall hat die linke Seite der Zuweisung den Typ “REAL”, die rechte aber “ARRAY [FALSE..TRUE] OF REAL”. Das “FALSE” aus dem zweiten Beispiel paßt als BOOLEAN-Wert natürlich auch nicht.

# Auflösung von Teil II
  1. In Modula sind nur die Namen c) und d) gültig. In a) befindet sich ein Leerzeichen (der Compiler erkennt “Null" und wundert sich, was das “Problemo" soll); in b) ist das Sonderzeichen "_" vorhanden, das zwar in Pascal verwendet werden kann, aber nie in Modula-2. e) beginnt mit einer Ziffer - der Compiler erwartet eine Zahlenkonstante, aber “0Problemo" würden auch Sie nicht als Zahl ansehen.

  2. Lediglich a) und e) sind dieselben Bezeichner, c) ist kein gültiger Name, da ein Sonderzeichen vorkommt, und die anderen Möglichkeiten unterscheiden sich durch die Groß- und Kleinschreibung.

  3. Nur a) und e) sind erlaubt, b) ergibt einen syntaktischen Fehler, denn die Bezeichner der Variablen “a", ”b" und “c" die mit dem Typ CARDINAL deklariert werden sollen, müssen mit einem Komma getrennt werden, c) ist zwar syntaktisch korrekt, nicht aber inhaltlich. Die Werte in “a” sollen aus einem Unterbereich von -100 bis 10 stammen. Und sie sollen vom Typ CARDINAL sein. Das ist nicht möglich, da CARDINAL nur positive ganze Zahlen enthält, also alle Werte von -100 bis -1 ungültig sein würden. In d) stecken zwei Fehler: Modula-2 unterscheidet zwischen Groß- und Kleinschreibung, also ist “var" nicht das Schlüsselwort “VAR", das eine Variablendeklaration einleitet. Und dann ist der Typ "REALO" keiner der Basistypen, die Sie kennengelernt haben (Sie werden in dieser Folge Typdeklarationen kennenlernen, mit denen Sie einen Typ REALO selber definieren können).

  4. Das Programm müßte wie in Listing 1 lauten. Wichtig dabei ist, daß Sie “ch" richtig deklarieren. Anstelle von “Read", das ein Zeichen einliest, muß “ReadCard" verwendet werden, damit die Typen stimmen. Falls Sie die Änderung in der Zeile, die mit “FROM InOut ...” beginnt, vergessen haben, macht das nichts, denn dieses Konzept werden Sie erst in der fünften Folge kennenlernen. Überlegen Sie sich aber folgendes: Wenn “Read" eine Variable vom Typ CHAR mit einem von der Tastatur eingelesenen Wert füllt, dann kann es keine Variable vom Typ CARDINAL füllen. Ebenso kann “ReadCard" keinen CHAR-Wert einlesen; ich hatte Ihnen ja in der Aufgabe gesagt, daß “ReadCard" zum Einlesen eines CARDINAL-Wertes verwendet werden muß.

 1: MODULE HelloWorld;
 2:
 3: FROM InOut IMPORT WriteString, WriteLn, ReadCard ;
 4:
 5: VAR ch:CARDINAL;
 6:
 7:  BEGIN
 8:   WriteString('Hallo Welt!');
 9:   WriteLn;
10:   ReadCard(ch);
11: END HelloWorld.

Listing 1

  1. Das Programm muß wie in Listing 2 lauten. Wichtig sind hierbei die Deklaration von “GleichNuIl" und das Zuweisungsstatement. Der Ausdruck "=" benutzt den Operator der hier zwei INTEGERs als Operanden und ein BOOLEAN-Ergebnis hat.

Falls Sie die Aufgaben falsch gelöst habe, sollten Sie sofort dieses Heft zuschlagen und nochmal die letzte Folge durchsehen. Ich hatte es beim letzen Mal schon herausgestellt: Wir sind noch tief in den Grundlagen, und wenn Sie hier nicht gründlich sind (“Grundlagen” kommt hier von “gründlich lernen'’), werden Sie bald nicht mehr mitkommen.

 1: MODULE HelloWorld;
 2:
 3: FROM InOut IMPORT WriteString, WriteLn, ReadCard ;
 4:
 5: VAR ch:CARDINAL;
 6:     GleichNull:BOOLEAN;
 7: BEGIN
 8:     WriteString('Hallo Welt!');
 9:     WriteLn;
10:     ReadCard(ch);
11:     GleichNull:=(ch=0);
12: END HelloWorld.

Listing 2

Zeichenketten

Modula-2 kennt keinen Typen für Strings. Es sind auch keinerlei Funktionen eingebaut, die mit Zeichenketten arbeiten. Ein String wird dargestellt als ein Feld von Zeichen. Brauchen wir eine Zeichenkette mit achtzig Zeichen, kann sie mit

VAR Meldung : ARRAY [0..80] OF CHAR;

deklariert werden. Wenn Sie genau hin-schauen, sehen Sie, daß das Feld genau 81 Elemente hat. Es gibt zwei gebräuchliche Methoden, einen String im Rechner darzustellen. Die erste schreibt die tatsächliche Länge der Zeichenkette in das erste Byte (hier Meldung[0]), die zweite schreibt ein Byte 00 an das Ende der gültigen Zeichen.

Es gibt keine Festlegung, wie Zeichenketten in Modula gehandhabt werden. Auf dem ATARI ST ist es (bedingt durch das Betriebssystem) üblich, eine 00 an das Ende einer Zeichenkette zu schreiben. Ihr Compiler wird ebenso Vorgehen.

Funktionen für Manipulationen von Zeichenketten sind in einem Standard-Modul vorhanden. Auf diese Module gehe ich in einer der späteren Folgen ein.

Dennoch kennt der Modular-Compiler eine spezielle Funktion für Zeichenfelder, nämlich die Zuweisung. Sie können schreiben:

Meldung := 'Hello world!';

Da “Meldung” vorher als ein Feld aus CHAR deklariert wurde, füllt es der Compiler automatisch mit den angegebenen Zeichen auf und kümmert sich um die angesprochene Behandlung der Endmarkierung. Dies funktioniert nur bei Zeichen.

Aufzählungstypen

Die Numerierung der Monate im obigen Beispiel mit der Telefonabrechnung ist sehr schlecht lesbar. Besser wäre es, wenn wir in einem Programm anstelle einer Zahl den wirklichen Monatsnamen verwenden könnten. Dazu muß aber der Compiler die Bezeichner für die Namen kennen. Notwendig ist also eine Vereinbarung, daß Monate die Namen “Januar”, “Februar” bis “Dezember” tragen.

Eine solche Vereinbarung wird in einer Typdeklaration vorgenommen. In ihr können Sie ähnlich einer Variablendeklaration dem Compiler mitteilen, daß er einen neuen Typ kennen soll, der bestimmte Werte annehmen kann. Eine Typdeklaration beginnt mit dem Schlüsselwort “TYPE" und besteht wieder aus einer linken und rechten Seite, die durch ein Gleichheitszeichen getrennt werden.

Für den Typnamen können Sie einen beliebigen Bezeichner verwenden. Auf der rechten Seite einer Typdeklaration steht eine Aufzählung aller möglichen Werte oder eine andere Beschreibung des Typs. Einen Typ, der durch eine Aufzählung der möglicher Werte beschrieben wird, nennt man in Modula einen Aufzählungstyp. Bei den Monatsnamen sähe eine Typdeklaration so aus:

TYPE Monat = (Januar, Februar, Maerz. April, Mai, Juni, Juli, August, September. Oktober, November, Dezember);

Dem Compiler ist danach der Typ “Monate“ bekannt, der die Werte “Januar" bis “Dezember“ annehmen kann. Er kann nun wie einer der Basis-Typen verwendet werden. Anstelle der obigen Deklaration des Feldes der monatlichen Telefonrechnungen mit Zahlen kann geschrieben werden:

VAR Tel : ARRAY [Januar..Dezember] OF REAL;

Im Programm wird jetzt der Rechnungswert für Januar mit “Tel[Januar]” und für November mit “Tel[November]” angesprochen.

Damit “Monat“ ein neuer Typ mit einem genau festgelegten Wertebereich deklariert wurde, kann er auch für einfache Variablen und in Zuweisungen verwendet werden:

VAR Jahresanfang: Monat;
...
Jahresanfang: = Januar;
...

Durch Aufzählungstypen werden Ihre Programme erheblich besser lesbar und dem eigentlichen Problem angepaßt. In einem Programm wird beschrieben, wie ein Problem gelöst werden soll, und mit einem Aufzählungstypen können Sie die Problemlösung so notieren, wie Sie sie auch mündlich formulieren würden. Sie wollen ja nicht die Rechnungen der Monate 1 bis 12 aufsummieren, sondern die Werte von Januar bis Februar. Das Programm sieht jetzt wie in Listing 4 aus.

Bitte machen Sie sich den Unterschied klar zwischen einem Programm, in dem für bestimmte “Dinge” Zahlen verwendet werden, und einem Programm, in dem die “Dinge" beim wirklichen Namen genannt werden. Die beiden gelisteten Programme leisten dasselbe - bei Verwendung eines Aufzählungstyps ist sogar mehr Schreibarbeit notwendig. Dennoch ist das zweite Programm “besser”, denn es ist nicht mehr davon abhängig, ob dem Monat “Januar” die Codierung “1” zugeordnet wird. Es wird damit leichter lesbar und ist leichter zu verändern. Während der Entwicklung einer Software sind Veränderungen ständig notwendig, woraus sich auch die Forderung nach einer guten Lesbarkeit ergibt.

In einer Hochsprache lassen sich alle Programme auf unterschiedliche Art und Weise formulieren. Die beste ist immer die, die für Erweiterungen den größten Raum läßt. Und damit ist immer die Notierung gemeint, die mit kleinen Veränderungen größten Effekt erzielt. Sie erfordert immer einen Programmtext, der von der tatsächlichen Realisierung abstrahiert, der sich nie auf die konkrete Codierung einläßt. Ein Programm, das sich in den Statements möglichst nur auf Festlegungen in den Deklarationen bezieht, wird immer das Programm sein, das sich am besten portieren und erweitern läßt.

Modula-2 bietet diese Möglichkeiten (bei den abstrakten Datentypen wird dieser Aspekt noch sehr wichtig), und Sie sollten sie nutzen, auch wenn damit einige zusätzliche Deklarationszeilen und etwas mehr an Denkarbeit verbunden sind. Alle mechanische Arbeit sollten Sie dem Rechner und der Sprache überlassen, alle Gedanken-Akrobatik, die die Möglichkeiten einer Sprache ausnutzt, müssen Sie übernehmen - sie wird sich später dadurch auszahlen, daß der Rechner Ihre Vorarbeit würdigt.

Der Unterschied zwischen einer implementationsabhängigen und einer abstrakten Formulierung mag in meinen Beispielen noch nicht so deutlich werden. Er bewirkt aber eine andere Denkweise beim Programmieren.

Records

Damit kennen Sie also den ersten zusammengesetzten Typ in Modula-2, die Felder und die Aufzählungstypen, die die Arbeit mit Feldern (und anderem) lesbarer machen.

Es gibt allerdings auch Datenstrukturen, die sich nur schlecht in Felder fassen lassen. Jedes Feld hat einen Grundtyp, der allen Feldelementen gleich ist. Eine Adresse z.B. besteht allerdings aus verschiedenen Grundtypen, z.B. einem CARDINAL für die Postleitzahl und einem Zeichenfeld für den Namen.

Für solche Fälle existiert der zusammengesetzte Typ RECORD. Um auf das Bild der Schubladen zurückzukommen, beschreibt er eine Schublade, die aus mehreren unterschiedlich großen Fächern besteht. Dabei sind diese Fächer gleichberechtigt und nicht wie bei Feldern nach Nummern geordnet. Bei Records erhält jeder Bestandteil einen eigenen Typ und einen eigenen Namen.

Als Beispiel soll das Adressenbeispiel dienen, das zwar nun wirklich nicht neu ist, dennoch die Sache recht gut illustriert. Eine Adresse besteht aus mehreren Elementen: Einem Vor- und Nachnamen, einem Straßennamen, einer Hausnummer, einer Postleitzahl und einem Ortsnamen. Genau so wird auch in Modula-2 eine Deklaration eines entsprechenden Records notiert:

VAR adresse : RECORD
                Vorname,
                Nachname,
                Strassenname    : ARRAY [0..80] OF CHAR; 
                Hausnummer,
                PLZ             : CARDINAL;
                Ortname         : ARRAY [0..80] OF CHAR;
              END;

Das Schlüsselwort “RECORD” leitet die Beschreibung eines Records ein. Es folgt eine Liste der Bezeichner der Komponenten und deren Typs, durch Semikola getrennt. Wie bei den normalen Variablendeklarationen können Objekte gleichen Typs durch Kommata getrennt aufgezählt werden. Das “END” schließt die Liste ab.

Welche Variablen werden mit dem Textstück deklariert? “adresse” ist wie oben beschrieben die “große” Schublade mit Fächern. Sie ist deklariert worden mit einem bestimmten Record-Typ. Der Record, den “adresse” darstellt, besteht aus den drei Zeichenfeldern “Vorname". “Nachname” und “Strassenname”, den ganzzahligen positiven Werten “Hausnummer” und “PLZ” sowie dem Zeichenfeld “Ortsname”.

Alle Bestandteile des Records machen nur im Zusammenhang mit “adresse" Sinn, denn nur dort kommen sie vor. In einem Ausdruck oder einer Zuweisung werden sie dementsprechend notiert:

adresse.Vorname:='Minni'; 
adresse.Nachname:='Maus'; 
adresse.Strassenname:=’Quakweg'; 
adresse.Hausnummer:=13; 
adresse.PLZ:=4141; 
adresse.Ortsname:='Entenhausen';

Ein Element eines Records wird angesprochen durch den Namen der Recordvariablen, dem Trennzeichen “.” und dem Namen des Elements. In einem Record kann jeder Name nur einmal Vorkommen. Ob “Vorname” schon an anderer Stelle verwendet wurde, ist dem Compiler egal, ihm ist von außen einzig und allein “adresse.Vorname” bekannt.

Kleine Zusammenfassung

An dieser Stelle eine kleine Zusammenfassung der Modula-2-Bestandteile, die bisher behandelt wurden:

Variablen müssen vor ihrer Verwendung deklariert werden. Variablen-Deklarationen werden mit dem Schlüsselwort “VAR” eingeleitet. Jede Deklaration gibt einen Bezeichner und einen Typ durch getrennt, an. Sind mehrere Variablen gleichen Typs zu deklarieren, so können ihre Bezeichner durch Kommata getrennt werden.

Es gibt Basistypen und zusammengesetzte Typen. Basistypen sind (unter anderem) BOOLEAN, CHAR, INTEGER, CARDINAL, REAL und Aufzählungstypen. Aufzählungstypen werden vorher in einer Typdeklaration (mit Schlüsselwort “TYPE” eingeleitet) unter Angabe ihres Wertebereichs deklariert.

Zusammengesetzte Typen sind Felder und Records. Felder sind eine Aneinanderreihung von Datenobjekten, die alle den selben Typ haben und der Reihe nach geordnet sind. Der Indexbereich muß ein abzähbarer Typ sein. Mehrdimensionale Felder sind Schachtelungen von eindimensionalen. Ein Objekt eines Feldes wird mit dem Feldnamen plus einem Index in “[“ und “]” eingeschlossen bezeichnet.

Records bestehen aus mehreren Elementen, die jeweils einen eigenen Namen und Typ bekommen. Ein Objekt aus einem Record wird durch den Record- und den Elementnamen, getrennt durch bezeichnet.

MODULE Rechnung;

FROM InOut IMPORT WriteReal ;

TYPE Monat = (Januar, Februar, Maerz, April, Mai, Juni, Juli,
              August, September, Oktober, November, Dezember);

VAR Tel : ARRAY [Januar..Dezember] OF REAL;

BEGIN
    ...
    TelJahr:=Tel[Januar]+Tel[Februar]+Tel[Maerz]+Tel[April]+Tel[Mai]+
             Tel[Juni]+Tel[Juli]+Tel[August]+Tel[September]+
             Tel[Oktober]+Tel[November]+Tel[Dezember];
    WriteReal(TelJahr....
END Rechnung.

Listing 4

Eine Zuweisung ist eine Anweisung, durch die der Inhalt einer Variablen verändert wird. Auf der linken Seite einer Zuweisung steht das Objekt, das verändert werden soll; auf der rechten Seite vom Trennsymbol ":=" ein Ausdruck. Ein Ausdruck besteht aus Operanden und Operationen. Der Typ eines Ausdrucks ergibt sich aus den verwendeten Operanden und den dazu vorhandenen Operationen. In einer Zuweisung muß der Typ des Ausdrucks mit dem Typ der zu verändernden Variablen übereinstimmen. Mit Casts und Umwandlungsfunktionen lassen sich die Typen anpassen.

Damit ist ein halbwegs komplettes Handwerkszeug zu Daten und Zuweisungen vorhanden. Der folgende Abschnitt handelt von Kontrollanweisungen, den bedingten Ausführungen.

Bedingte Ausführung

Ein Modula-2-Programm beschreibt das, was vom Rechner abgearbeitet werden soll in Anweisungen, den Statements. Sie kennen bis jetzt das Zuweisungsstatement. Programme, die immer nur eine bestimmte Anweisungsfolge ausführen, sind recht unflexibel. Es wäre wünschenswert, wenn die Ausführung in bestimmten Situationen unterschiedlich verlaufen könnte. Es gibt also Bedingungen, die die Ausführung jeweils unterschiedlicher Statement-Folgen auslösen sollen. Im Programm wird dies mit bedingten Statements notiert.

Angenommen, ein Programm soll zwischen verschiedenen Temperatursystemen umwandeln können. Es gibt die Temperaturbestimmung nach Celsius und nach Fahrenheit. Besagtes Programm soll einen Temperaturwert einlesen und ihn je nach Wunsch des Benutzers in das jeweils andere System umwandeln können.

Sein Listing steht in Listing 5. Zur Eingabe des Temperaturwertes durch den Benutzer wird die vorprogrammierte Funktion “ReadReal” verwendet, die aus “RealInOut” genommen wird. Analog dazu dient “WriteReal” zur Ausgabe einer REAL-Zahl, wobei noch die Anzahl der zu verwendenden Stellen angegeben werden muß.

Read” liest ein Zeichen ein; “WriteString” gibt eine Zeichenkette aus, und “WriteLn“ schließlich erzeugt einen Zeilenvorschub auf dem Bildschirm.

Nach einer Meldung soll der Benutzer einen Temperaturwert eingeben, der in “TemperaturEin” abgelegt wird. Als nächstes erfolgt die Frage, ob von Celsius nach Fahrenheit gewandelt werden soll. Die Eingabe der Benutzers darauf steht als Zeichen in “Antwort”.

Beantwortet der Benutzer diese Frage mit “J” oder “j”, muß die Formel für die Umrechnung Celsius nach Fahrenheit verwendet werden. Sie lautet “Fahrenheit = 9/5 * Celsius + 32”. Anderenfalls muß nach “Celsius = (Fahrenheit - 32) * 5/9” gerechnet werden.

Das Programm soll also je nach Inhalt von “Antwort” unterschiedliche Zuweisungen ausführen. Im Modula-Programm wird diese Situation mit der IF-THEN-ELSE-END-Konstruktion notiert. Sie hat folgende Form:

IF <Bedingung> THEN 
    Anweisungen1
ELSE
    Anweisungen2
END;

<Bedingung> ist ein Ausdruck vom Typ BOOLEAN. Ergibt seine Berechnung TRUE, so werden Anweisungen1 ausgeführt. Ergibt er FALSE, führt der Rechner Anweisungen2 aus. Der ELSE-Zweig kann, falls nicht benötigt, wegfallen.

MODULE Umrechnung;

FROM InOut IMPORT Read, WriteString, WriteLn ;
FROM ReallnOut IMPORT ReadReal, WriteReal ;

VAR Antwort                      : CHAR ;
    TemperaturEin, TemperaturAus : REAL ;

BEGIN
    WriteString('Fahrenheit <-> Celsius Wandler'); WriteLn;
    WriteString('Welche Temperatur --> ');
    ReadReal(TemperaturEin); WriteLn;
    WriteString('Celsius nach Fahrenheit (J/j) ? ');
    Read(Anwort); WriteLn;
    IF ((Antwort='J') OR (Antwort='j')) THEN
        TemperaturAus:=(9.0/5.0)*TemperaturEin+32;
    ELSE
        TemperaturAus:=(TemperaturEin-32)*(5.0/9.0);
    END;
    WriteReal(TemperaturAus,6);
    IF ((Antwort='J') OR (Antwort='j')) THEN 
        WriteString(' Fahrenheit');
    ELSE
        WriteString(' Celsius');
    END;
    WriteLn;
END Umrechnung.

Listing 5

Im Beispiel ist die Bedingung der Ausdruck“ ((Antwort='J') OR (Anweisung='j'))“. Er ergibt genau dann TRUE, wenn der Benutzer die Frage, ob von Celsius nach Fahrenheit gewandelt werden soll, durch Eingabe von “J“ oder “j” beantwortet hat. Der Programmfluß wird in diesem Fall mit der Anweisung zur Berechnung von Celsius nach Fahrenheit fortgeführt. Ergibt die Bedingung FALSE arbeitet der Rechner die Anweisung nach dem ELSE ab.

Nach der entsprechenden Verarbeitung der IF-Anweisung fährt das Programm mit “WriteReal” fort. Eine analoge IF-Anweisung folgt weiter unten, um die korrekte Maßeinheit auf dem Bildschirm auszugeben.

Eine IF-Anweisung besteht also aus einer Bedingung, die den Programmfluß steuert, und zwei Anweisungsfolgen, die je nach Ergebnis der Bedingung ausgeführt werden. Der ELSE-Zweig kann wegfallen.

Fallunterscheidungen

Es gibt Bedingungen, die sich nur schlecht in einem BOOLEAN-Wert ausdrücken lassen. Denken Sie an eine Menüauswahl, bei der der Benutzer einen von mehreren Kennbuchstaben eingeben soll. Je nach Auswahl soll eine bestimmte Anweisungsfolge ausgeführt werden. Mit dem IF-THEN-ELSE-Mechanismus sieht das so aus:

IF Befehle='A' THEN
    ...
ELSE IF Befehl='B' THEN
        ...
    ELSE IF Befehl='C' THEN
            ...
        ELSIF Befehle='D' THEN
        ...
        END;
    END;
END;

Dieses Programmstück ist schlecht lesbar und fehleranfällig. Das “ELSIF“ ist übrigens eine Abkürzung für “ELSE IF ... END“, die die IF-THEN-ELSE-Anweisung etwas erweitert. Praktischer wäre eine Anweisung, die einfach die möglichen Werte für den Befehl und die dazugehörigen Anweisungen auflistet. Dafür gibt es in Modula-2 die CASE-Anweisung. Sie hat folgendes Aussehen:

CASE <Wert> OF 
    <Inhalt1> : <Anweisungen1>
    | <Inhalt2> : <Anweisungen2>
...
ELSE
    <AnweisungenSonst>
END;

Das Programm arbeitet eine solche Konstruktion so ab, daß es die Variable <Wert> nimmt und der Reihe nach mit den Konstanten <Inhalt1>, <Inhalt2> ... vergleicht. Ergibt sich eine Übereinstimmung, so werden die dazugehörigen Anweisungen ausgeführt. Trifft kein Vergleich zu, werden die im ELSE-Zweig angeführten Anweisungen abgearbeitet. Ist in einem solchen Fall kein ELSE-Zweig vorhanden, bricht das Programm mit einem Fehler ab.

Sollen mehrere Inhalte von <Wert> zu derselben Anweisungsfolge führen, können die verschiedenen Möglichkeiten durch Kommata getrennt aufgeführt werden (“<Inhalt1a>, <Inhalt1b> :”).

Das Beispiel der Temperatur-Umwandlung soll nun erweitert werden. Es gibt neben der Celsius- und Fahrenheitskala noch eine dritte Skala, nach Reaumur benannt. Das Programm soll nun auf die Eingabe eines Kennbuchstabens für die gewünschte Darstellungsart entsprechend reagieren. Der Benutzer gibt wie bisher zunächst einen Temperaturwert ein und teilt dann dem Programm mit, in welcher Skala die Eingabe gemeint war. Das Programm rechnet daraufhin die Eingabe in Celsius um. Danach kann der Benutzer auswählen, nach welcher Skala das Programm den Wert ausgeben soll.

MODULE Umrechnung;

FROM InOut      IMPORT Read, WriteString, WriteLn ;
FROM RealInOut  IMPORT ReadReal, WriteReal ;

VAR Antwort                     : CHAR ;
    TemperaturEin, TemperaturAus,
    Temperatur                  : REAL ;

BEGIN
    WriteString('Temperaturskalen-Wandler'); WriteLn; 
    WriteString('Welche Temperatur —> ');
    ReadReal(TemperaturEin); WriteLn;
    WriteString('Skala der Eingabe (C/R/F) ? ');
    Read(Anwort); WriteLn;
    CASE Antwort OF
        'F' : Temperatur:=(TemperaturEin-32)*(5.0/9.0);
    |   'R' : Temperatur:=(5.0/4.0)*TemperaturEin;
    ELSE
        Temperatur:=TemperaturEin;
    END;
    WriteString('Skala der Ausgabe (C/R/F) ? ');
    Read(Antwort); WriteLn;
    CASE Antwort OF
        'F','f' : TemperaturAus:=(9.0/5.0)*Temperatur+32;
    |   'R','r' : TemperaturAus:=(4.0/5.0)*Temperatur;
    ELSE
        TemperaturAus:=Temperatur;
    END;
    WriteReal(TemperaturAus,6);
    CASE Antwort OF
        'F','f' : WriteString(' Fahrenheit');
    |   'R','r' : WriteString(' Reaumur');
    ELSE
        WriteString(' Celsius');
    END;
    WriteLn;
END Umrechnung.

Listing 6

Zur Auswahl der Skala werden die drei Kennbuchstaben F, R und C angeboten. Das Programm (Listing 6) rechnet in einer CASE-Anweisung aufgrund der Auswahl die Eingabe in Celsius um. Die Auswahl von C und allen anderen Buchstaben außer F und R führt in der ersten CASE-Anweisung in den ELSE-Teil. Dort wird angenommen, daß die Eingabe schon in Celsius war und unverändert der Variablen zugewiesen wird.

In einer zweiten Auswahl bestimmt der Benutzer wieder durch Eingabe des Kennbuchstabens das Skalensystem. Die Anweisungen in dem CASE-Konstrukt rechnen dementsprechend die interne Celsius-Temperatur in die gewünschte Skala um. Bei dieser CASE-Anweisung wird von der oben genannten Angabe mehrere Male der Inhalt von der Variablen “Antwort” für dieselbe Anweisungsfolge benutzt. Es ist egal, ob Groß- oder Kleinbuchstaben eingegeben werden.

Die letzte CASE-Anweisung zur korrekten Ausgabe der Maßeinheit arbeitet analog.

Noch eine Zusammenfassung

Es gibt also zwei Anweisungsarten, die in verschiedene Anweisungsfolgen verzweigen: IF-THEN-ELSE aufgrund einer Bedingung vom Typ BOOLEAN und die Fallunterscheidung mit CASE, bei der für eine Variable verschiedene Inhalte und die dazu auszuführenden Anweisungsfolgen angegeben werden. In einer CASE-Anweisung muß allen denkbaren Inhalten einer Variablen eine Anweisungsfolge zugeordnet sein, notfalls mit dem ELSE-Zweig.

Damit wäre dieser dritte Teil des Modula-2 Kurses beendet. In der nächsten Folge wird es um Schleifen gehen, und Sie können endlich mehr und interessantere Beispiellistings kennenlernen.

Bis dahin ...

RT

# Hausaufgaben
  1. Deklarieren Sie ein zweidimensionales 3*3-Feld “Testfeld", dessen Elemente Daten vom Typ INTEGER aufnehmen sollen. Schreiben Sie dazu Zuweisungen, mit denen alle Felder auf -1 gesetzt werden (Tip: Es geht mit fünf Anweisungen).

  2. Sie sollen ein Programm schreiben, mit dem der Stromverbrauch Ihrer Haushaltsgeräte gemessen werden soll. Der Hausmann oder die Hausfrau will dem Rechner jeweils die Zeiten der Benutzung eines Geräts eingeben und aus der Verrechnung ermitteln, welche Geräte den meisten Verbrauch verursachen. Welches Programmgerüst würden Sie bevorzugen:

a)

MODULA Haushalt;

VAR VerbrauchProStunde : ARRAY [1..5] OF REAL;

BEGIN 
    Verbrauch[1]:=0.6; 
    Verbrauch[2]:=3.0; 
    Verbrauch[3]:=1.0; 
    Verbrauch[4]:=1.0;
    Verbrauch[5]:=3.5;
...
END Haushalt.

b)

MODULE Haushalt:
...
TYPE Geräte = (Mixer, Waschmaschine, Fernseher, Kuehlschrank, Herd);
VAR VerbrauchProStunde : ARRAY[Mixer..Herd] OF REAL ;

BEGIN 
    Verbrauch[Mixer]:=0.6; 
    Verbrauch[Waschmaschine]:=3.0; 
    Verbrauch[Fernseher]:=1.0; 
    Verbrauch[Kuehlschrank]:=1.0; 
    Verbrauch[Herd]:=3.5;
...
END Haushalt.

Was passiert mit den Programmen, wenn ein Staubsauger, ein Eierkocher und eine Geschirrspülmaschine angeschafft werden? Was passiert, wenn Hausmann Karl abends in der Kneipe seinem Kollegen Otto das Programm erklären will?

  1. Deklarieren Sie ein Feld, das in einem Schachprogramm verwendet werden könnte. Allerdings soll mit Hilfe eines Aufzählungstyps das Programm lesbarer werden, so daß man z.B. “Brett[1,5]:=Dame;" schreiben kann. Es soll auch möglich sein, ein Feld gleich “Leer" zu setzen.

  2. Die Koordinate eines Bildschirmpixels wird durch einen X- und einen Y-Wert angegeben. Deklarieren Sie eine Record-Variable “Punkt" und weisen Sie ihr die Koordinate 100,140 zu.

  3. Schreiben Sie basierend auf Aufgabe 3 eine Anweisung, die für ein bestimmtes Feld des Schachbretts (das Feld wird durch die Variablen x und y identifiziert, also Brett[x,y]) die Zeichenkette “Leeres Feld" ausgibt, wenn das Feld gleich “Leer" ist und ansonsten “Figur auf Feld".

  4. Schreiben Sie ebenfalls basierend auf Aufgabe 3 eine Anweisung, die für ein Feld des Brettes den Namen der Figur ausgibt. Geben Sie auch “Leeres Feld" aus.



Aus: ST-Computer 03 / 1989, Seite 100

Links

Copyright-Bestimmungen: siehe Über diese Seite