Modula-2 Kurs, Teil 4

Aller guten Dinge sind drei, allerdings nicht im Modula-2 Kurs, zu dessen vierter Folge ich Sie begrüße. Die Lösungen der Hausaufgaben aus dem letzten Teil finden Sie im Kasten.

Im Zeit- bzw. Platzplan dieser Serie hänge ich um circa eine halbe Folge nach. Anfangs wollte ich schon im dritten Teil die Statements halbwegs komplettieren, aber die bedingten Anweisungen nahmen dann doch zuviel Platz ein. Daher in dieser Folge die dritte große Klasse von Statements, die Schleifen.

Schleifen

Sie kennen bis jetzt zwei Arten von Anweisungen, die “normalen” und die bedingten. Letztere waren die IF-THEN-ELSE- und CASE-Statements. Was noch fehlt, ist die wiederholte Ausführung einiger Anweisungen, die Schleifen.

Schleifen bewirken, daß Anweisungsfolgen wiederholt ausgeführt werden. Die Anweisungen brauchen nur einmal im Programm notiert zu werden; der Rechner durchläuft sie bei der Ausführung mehrmals.

Es gibt mehrere Arten von Schleifen, die einfachste ist die Zählschleife, bei der die Anzahl der Durchläufe vorgegeben ist.

Weiterhin gibt es bedingte und unbedingte Schleifen. Bei ersteren entscheidet das Zutreffen einer Bedingung darüber, ob ein weiterer Durchlauf stattfindet; letztere werden endlos wiederholt, so keine besondere Anweisung zur Beendigung der Schleife vorkommt.

FOR-Schleifen

In Modula-2 gibt es vier Arten von Schleifen. Fangen wir mit der FOR-Schleife an. Sie hat folgende Form:

FOR <Laufvariable> := <Anfang> TO <Ende> DO
   ... Anweisungen ...
END;

Der Rechner setzt die Variable <Laufvariable> - die natürlich vorher deklariert sein muß - auf den Wert <Anfang> und führt die folgenden Anweisungen aus. Trifft er auf das END wird die Laufvariable erhöht und mit dem Wert <Ende> verglichen. Ist sie nicht größer, werden die Anweisungen erneut ausgeführt. Anderenfalls fährt der Rechner mit dem der FOR-Anweisung folgenden Statement fort.

Die Laufvariable muß von abzählbarem Typ sein. Die “Erhöhung” der Laufvariablen ist bei einem INTEGER einleuchtend durchführbar - eine Eins wird addiert. Bei einer Laufvariablen vom Typ REAL wäre das nicht möglich, denn was sollte der Nachfolger von 0.01 sein? 0.02 oder 0.011 oder 0.0100001? Ihr Compiler wird den Versuch, eine nichtabzählbare Laufvariable zu verwenden, mit einer Fehlermeldung abweisen.

Abzählbare Typen sind nicht nur ganze Zahlen wie INTEGER und CARDINAL. Als Laufvariable können auch BOOLEANs oder selbstdefinierte Aufzählungstypen verwendet werden. Es muß immer eine “Erhöhung” möglich sein. Erlaubte Schleifenanweisungen sind:

FOR i:=1 TO 10 DO ... END; (Typ INTEGER oder CARDINAL) 
FOR h:=FALSE TO TRUE DO ... END; (BOOLEAN)
FOR m:=Januar TO Dezember DO ... END; (selbstdefinierter Monat) 
FOR c:='A' TO 'Z' DO ... END; (CHAR)

<Anfang> und <Ende> können Konstanten oder Variablen sein. Sie müssen allerdings denselben Typ wie die Laufvariable haben.

Die Variablen <Laufvariable> und - so vorhanden - <Anfang> und <Ende> sind in dem Schleifenkörper für Veränderungen tabu. Nehmen Sie nie Zuweisungen an diese Variablen in der Schleife vor. Es gibt Sprachen, bei denen schon der Compiler solche Zuweisungen verbietet - Modula-2 gehört leider nicht dazu. Sie werden allerdings feststellen, daß Sie nach einiger Zeit solche Veränderungen auch als Programmautor nicht mehr durchschauen.

In einigen Anwendungen ist es wünschenswert, die Größe der “Erhöhung”, die Schrittweite pro Schleifendurchlauf, vorzugeben. So z.B. bei der Ausgabe des jeweils fünften Elements eines Feldes.

Modula-2 bietet dafür das BY-Anhängsel:

FOR i:=1 TO 50 BY 5 DO 
   WriteInt(Feld[i],5);
END;

Im Gegensatz zu einer “normalen” Zählschleife rechnet der Computer die Laufvariable nicht um Eins, sondern um Fünf hoch. Die Angabe einer solchen Schrittweite ist nicht in allen Fällen erlaubt, genauer nur bei INTEGER und CARDINAL. Dies ist einsichtig, wenn Sie sich fragen, was denn mit folgenden Anweisungen gemeint sein könnte (siehe unten):

FOR b:=FALSE TO TRUE BY TRUE DO ... END;
FOR m:=Januar TO Dezember BY Februar DO ... END; 
FOR c:='A' TO 'Z' BY DO ... END;

Sie sehen, es macht keinen Sinn. Eine Laufvariable vom Typ INTEGER können Sie durch eine negative Schrittweite auch “rückwärts” laufen lassen, was in einigen Anwendungen notwendig ist:

FOR i:=100 TO 1 BY -1 DO ... END;

Bei der Schrittweite muß es sich um eine Konstante oder um einen konstanten Ausdruck handeln.

Ein Beispiel, das bei FOR-Schleifen gerne zur Illustration verwendet wird, ist das “Sieb des Eratosthenes”, eine Methode zur Ermittlung von Primzahlen.

Zur Erinnerung: Primzahlen sind ganze Zahlen, die nur durch sich selbst und 1 teilbar sind, also z.B. 7, 13 oder 89. Der Algorithmus basiert auf einem Feld von BOOLEAN-Werten. Nach der Berechnung soll gelten, daß i genau dann eine Primzahl ist, wenn im Feldelement mit Index i der Wert TRUE eingetragen ist. Ansonsten handelt es sich nicht um eine Primzahl.

Dabei wird wie folgt vorgegangen: Anfangs billigen wir jeder Zahl den Status Primzahl zu, d.h. alle Feldelemente werden auf TRUE gesetzt. Die eigentliche Berechnung beginnt am Anfang des Feldes und nimmt sich die erste gefundene Primzahl i. Alle Zahlen, die ein Vielfaches davon sind, können keine Primzahl sein, da sie den Teiler i haben. Also werden die entsprechenden Feldelemente auf FALSE gesetzt. Bei der nächsten gefundenen Primzahl wiederholt sich dieser Vorgang.

Wenn das zu bearbeitende Feld n Elemente enthält, so müssen alle Zahlen bis n/2 bearbeitet werden, da alle darüberliegenden keine Vielfachen im untersuchten Bereich haben können. Die Vielfachen selber sollen ebenfalls nur bis zur Grenze des Feldes als solche markiert werden; somit stehen für i nur Vielfache bis n/i in Frage. Aus diesen Überlegungen ergeben sich Anfang und Ende der jeweiligen Schleifen in dem Programm. Sie finden es in Listing 1.

Nach den entsprechenden Deklarationen werden in den Zeilen 11 bis 13 alle Feldelemente auf TRUE geschaltet. In zwei geschachtelten Schleifen erfolgt das eigentliche Errechnen der Primzahlen wie beschrieben. Die äußere Schleife läuft von 2 bis zur Hälfte der Feldgröße (Zeile 14). Mit der IF-Abfrage (Zeile 15) wird entschieden, ob Vielfache gelöscht werden müssen, was dann in einer weiteren FOR-Schleife geschieht (Zeilen 16-18). Dabei ist j der Multiplikator der gefundenen Primzahl i, wodurch der Feldindex des Vielfachen errechnet ist.

Nach Beendigung der Schleifen sind alle Zahlen, für die ein TRUE im Feld steht, Primzahlen. In einer kleinen Ausgabeschleife (Zeilen 21-26) wird jedes Element überprüft und bei TRUE der Index auf dem Bildschirm ausgegeben.

# Antworten von Teil III
  1. Das Programm muß wie in Listing 1 aussehen. Die ersten drei Zuweisungen setzen jeweils ein Feldelement auf.
    TYPE Schachfigur = (Bauer, Springer, Laeufer, Turm, Dame, Koenig, Leer); 
    VAR Brett = ARRAY [1..8] OF ARRAY [1..8] OF Schachfigur;
    MODULE Feldaufgabe;

    VAR Testfeld : ARRAY [1..3] OF ARRAY [1..3] OF INTEGER;

    BEGIN
       Testfeld[1,1]:=-1;
       Testfeld[1,2]:=-1;
       Testfeld[1,3]:=-1;
       Testfeld[2]:=Testfeld[1];
       Testfeld[3]:=Testfeld[1];
     END Feldaufgabe.

Listing 1

  1. Das könnte man auch für die restlichen sechs Elemente machen, aber die hier gezeigte Lösung ist etwas eleganter. Da “Testfeld[1]” vom Typ “ARRAY[1.3] OF INTEGER” ist, kann es auch “Testfeld[2]” und “Testfeld[3]” zugewiesen werden. Die Typgleichheit ist beachtet, und Modula unterstützt die Zuweisung von kompletten Feldern.

  2. Programmversion b) ist besser, auch wenn sie länger ist und a) dasselbe leistet. Soll das Programm um drei Geräte erweitert werden, muß sich der Programmierer Gedanken über die Kodierung machen. Ist die Zahl 3 schon für ein Gerät vergeben?

Karl hat es leichter. Er sagt einfach “Wir nehmen alle Geräte vom Mixer bis zur Geschirrspülmaschine und verarbeiten die Daten” Otto, ein abgebrühter BASIC-Programmierer, müßte sagen: “Wir addieren einfach die Geräte 1 bis 7.” Karl meint darauf: “Ob der Fernseher das Gerät 3 ist, entscheidet der Compiler, und mir ist es eigentlich auch egal.” Otto: “Muß ich mal im Listing nachschlagen.”

  1. Die Deklarationen (siehe Listing Oben) leisten das Gewünschte. “Schachfigur” ist ein Aufzählungstyp, der alle Figuren einschließlich “Leer” benennt und dem Compiler bekanntmacht. “Brett” ist ein zweidimensionales Feld, dessen Elemente jeweils eine Schachfigur aufnehmen können.

  2. Der Programmausschnitt muß so aussehen:

    ...
    VAR Punkt : RECORD
                   x,y:INTEGER;
                END;
    BEGIN
       Punkt.x:=100;
       Punkt.y:=150;
    END ...

Der Record besteht aus zwei Komponenten, x und y. Bei den Zuweisungen wird jeweils der Name des Records und dessen Komponente durch getrennt notiert.

  1. Die Anweisung lautet:
    ...
    IF (Brett[x,y]=Leer) THEN 
       WriteString('Leeres Feld')
    ELSE
       WriteString('Figur auf Feld'); 
    END
    WriteLn;
    ...

Es handelt sich um eine einfache IF-THEN-ELSE-Konstruktion, die bei Zutreffen der Bedingung ‘Brett[x,y]=Leer” in die Ausgabeanweisung “Leeres Feld” verzweigt. Ansonsten wird “Figur auf Feld” ausgegeben.

  1. Die Anweisung lautet:
    ...
    CASE Brett[x,y] IF Bauer
    WriteString('Bauer');
    | Springer :
    WriteString('Springer');
    | Laeufer  :
    WriteString('Läufer' );
    | Turm     :
    WriteString('Turm');
    | Dame     :
    WriteString('Dame');
    | Koenig   :
    WriteString('König');
    ELSE
       WriteString('Leeres Feld');
    END;
    ...

Die Fallunterscheidung führt entsprechend dem Feldinhalt zu Anweisungen, die den Namen der jeweiligen Figur ausgeben. Steht keine Figur auf einem Brett, wird der ELSE-Zweig ausgeführt. Da es nur einen Inhalt von “Brett[x,y]” gibt, der nicht extra aufgeführt ist, könnte man auch auf den ELSE-Zweig verzichten und schreiben:

...
| Koenig	: WriteString('König');
| Leer   : WriteString('Leeres Feld');
END;

WHILE-Schleifen

Wenn keine festen Grenzen für eine Schleife festzulegen sind, benötigt man ein Konstrukt, dessen Ausführung beim Eintreten einer Bedingung beendet wird. In Modula lautet das:

WHILE <Bedingung> DO 
   ... Anweisungen ...
END;

In einer WHILE-Schleife berechnet der Computer zunächst den Ausdruck <Bedingung>. Er muß vom Typ BOOLEAN sein. Ergibt die Auswertung TRUE, führt der Rechner die folgenden Anweisungen aus und testet erneut. Ergibt <Bedingung> FALSE, wird die Ausführung mit dem Statement nach der WHILE-DO-END-Anweisung fortgeführt.

Ist die Bedingung konstant, wird der Schleifenkörper entweder nie betreten oder nie wieder verlassen. Zur Verknüpfung mehrerer Bedingungen dienen die entsprechenden Operatoren NOT, AND und OR, was aber nur den normalen Regeln für BOOLEAN-Ausdrücke entspricht.

Im Schleifenkörper muß “etwas geschehen”, das die Bedingung beeinflußt. Eine Schleife

WHILE (i>0) DO j:=i+1 END;

kommt nie zu einem Ende, da die Bedingung von den Statements des Schleifenkörpers unabhängig ist. Arbeitet eine WHILE-Schleife mit Zählern, wird typischerweise innerhalb des Schleifenkörpers eine Erhöhung oder Erniedrigung eine Zählvariablen vorgenommen. Hängt die Bedingung von einer Eingabe des Benutzers ab, geschieht die Beeinflussung der Bedingung in einer Einlesefunktion.

Das Beispiel der obigen Endlosschleife trifft allerdings nur dann zu, wenn der Wert von i vor dem WHILE-Statement größer Null war. Bei -1 ergibt die Bedingung beim ersten Mal schon FALSE. Also geschieht der Eintritt in die Schleife in Abhängigkeit von den vorherigen Anweisungen. Da somit der Schleifenkörper bei WHILE nie ausgeführt wird, wenn die Bedingung anfangs schon nicht zutrifft, wird die WHILE-Schleife auch “abweisend” genannt.


MODULE Sieb;

FROM InOut IMPORT WriteInt, WriteLn;

CONST OBERGRENZE = 1000 ;

VAR zahl: ARRAY [2..OBERGRENZE] OF BOOLEAN; 
    i, j: INTEGER;

BEGIN
    FOR i:=2 TO OBERGRENZE DO 
        zahl[i]:=TRUE 
    END;
    FOR i:=2 TO OBERGRENZE DIV 2 DO 
        IF zahl[i] THEN
            FOR j:=2 TO OBERGRENZE DIV i DO 
                zahl[i*j]:=FALSE;
            END;
        END;
    END;
    FOR i:=2 TO OBERGRENZE DO 
        IF zahl[i] THEN 
            WriteInt(i,5);
            WriteLn;
        END;
    END;

Listing 1

REPEAT-Schleifen

“Nichtabweisend” ist hingegen die REPEAT-Schleife, die in Modula-2 so aussieht:

REPEAT
    ... Anweisungen ... 
UNTIL <Bedingung>;

Hier findet die Auswertung von <Bedingung> erst nach Ausführung des Schleifenkörpers statt. Ergibt sie FALSE, geht die Ausführung wieder bei REPEAT weiter, ansonsten bei der folgenden Anweisung. <Bedingung> muß wiederum vom Typ BOOLEAN sein.

Der Schleifenkörper einer REPEAT-UNTIL-Anweisung wird also immer mindestens einmal durchlaufen. Da der erste Durchlauf unabhängig von der Bedingung ist, darf keine Annahme über ihr Zutreffen gemacht werden. Ein Beispiel:

...
ReadInt(n);
REPEAT 
    x:=x DIV n; 
    n:=n-1;
UNTIL (n=0);
...

Hier kann es zu einem Fehler kommen, obwohl es den Anschein hat, daß eine Division durch Null durch die Bedingung abgefangen wird. Hat aber n vor Eintritt in die Schleife den Wert Null, so scheitert die Division, bei der die Annahme gemacht wurde, daß n ungleich Null sei. Das Beispiel muß also abweisend formuliert werden:

...
ReadInt(n);
WHILE (n#0) DO 
    x:=x DIV n; 
    n:=n-1;
END;
...

LOOP-Schleifen

Als letzte Schleifenform gibt es in Modula auch eine unbedingte Schleife:

LOOP
... Anweisungen ...
EXIT;
... Anweisungen ...
END;

Alle Anweisungen zwischen LOOP und END werden endlos wiederholt. Es gibt keine Vorgabe über die Anzahl der Durchläufe oder eine Bedingung. Allerdings existiert eine Möglichkeit zum Abbruch der Schleife, nämlich durch die EXIT-Anweisung. Sie bewirkt, daß die Programmausführung bei der Anweisung nach dem zum LOOP gehörigen END weitergeführt wird.

EXIT ist nur innerhalb einer LOOP-END-Schleife erlaubt und erzeugt ansonsten einen Laufzeitfehler. Man wird zweckmäßigerweise ein EXIT von einer Bedingung abhängig machen, also in eine IF-Anweisung verpacken. Der Unterschied zur REPEAT- und WHILE-Schleife besteht somit darin, daß die Auswertung einer Abbruchbedingung nicht nur am Anfang oder Ende des Schleifenkörpers stattfindet.

Die LOOP-Schleife ist schlecht lesbar, da die Abbruchbedingung mitten im Schleifenkörper liegt. Man könnte sie auch als Ersatz für das GOTO ansehen, das bei der Entwicklung von Pascal nach Modula-2 wegfiel.

Rrrring... rrring...

Kommen wir nun zu einem größeren Beispiel für die Verwendung von Schleifen, das in Listing 2 abgebildet ist. Dazu eine Vorbemerkung: Leider sind die Bibliotheken von Modula-2 (alles, was mit IMPORT bereitgestellt wird) schlecht genormt. Es gibt Unterschiede in der Bibliotheksstruktur, in der Namensgebung und in der Parameterverwendung zwischen den verschiedenen Implementierungen. Ich habe daher in dem Beispiel, das in dieser Form mit TDI-Modula arbeitet, die notwendigen Änderungen für die Systeme SPC und Megamax extra notiert. Sie müssen sie je nach verwendetem System einsetzen.

Bei einem ersten Blick auf das Listing sehen Sie etwas Neues: Kommentare. Alle Zeichen, die zwischen “(” und “)” stehen, gelten als Kommentar, die vom Compiler überlesen werden. Ein gut kommentiertes Listing ist besser lesbar und besser zu strukturieren.

Um nun z.B. das SPC-System zu verwenden, müssen Sie Zeile 10 “auskommentieren". Dazu wird der Kommentar “(* TDI )” entfernt und die gesamte Zeile von einem Paar “( ... *)” umschlossen. Damit überliest der Compiler diese Zeile. Zeile 11 muß aber gelesen werden. Dazu müssen Sie die Kommentarklammem entfernen und “SPC" löschen oder als Kommentar markieren (wie ursprünglich in Zeile 10).

Ob Kommentare geschachtelt werden können, also ob “(* kommentierter (* Kommentar *) *)” Probleme macht oder nicht, ist compilerabhängig und steht in Ihrem Handbuch.

Nun aber zu dem Programm. Es soll das Beispiel mit der Telefonabrechnung erweitern. Sie sollen interaktiv die Kosten für die Monate eingeben, die Gesamt- und Durchschnittskosten abfragen und eine Übersicht ausgeben können. Die Auswahl soll in einem Menü geschehen, und für die Eingabe des Monats sollen die wirklichen Monatsnamen und nicht etwa Zahlen verwendet werden können.

Die ersten Zeilen nehmen die Anforderungen von Bibliotheksroutinen in Anspruch. Sie müssen wie beschrieben angepaßt werden. Die Zeilen 14 bis 17 deklarieren einen Typ EinMonat, für die Benennung der Monate. Warum dabei ein Monat Undefiniert vorkommt, wird später deutlich. Der Typ String dient zur Aufnahme einer Zeichenkette. Zur Erinnerung: Zeichenketten werden in Modula-2 als Felder von Zeichen aufgefaßt, und der Compiler ist in der Lage, Zuweisungen durchzuführen.

An Variablen brauchen wir ein Feld von Fließkommazahlen zur Aufnahme der Rechnungsbeträge (TelRechn) und ein Feld, in das die Namen der Monate als Zeichenketten kommen (MonatsName). In den Zeilen 21 bis 24 werden weitere Variablen deklariert, die für Schleifen, Berechnungen und Eingaben verwendet werden.

Das eigentliche Programm beginnt in den Zeilen 28 bis 30 mit einer Begrüßungsmeldung auf dem Bildschirm. Danach erfolgt die Initialisierung der Datenfelder, wobei eine FOR-Schleife alle Rechnungsbeträge auf 0.0 setzt (Zeilen 32 bis 34). Die Namen der Monate werden einzeln zugewiesen (Zeilen 36 bis 43). Sie benötigen wir später zur Ausgabe und zur Auswertung einer Eingabe.

Ab Zeile 44 beginnt eine große REPEAT-Schleife, in der das Hauptprogramm liegt. In ihm wird ein Menü präsentiert und je nach Auswahl eine Aktion aufgerufen. Der Menüpunkt 5 soll das Programm beenden, demnach lautet die UNTIL-Bedingung “auswahl='5'" (Zeile 107). Der Hauptteil des Programms wird ausgeführt, bis der Benutzer eine ‘5’ eingibt.

In der Schleife findet zunächst die Anzeige des Menüs (Zeilen 46 bis 52) und das Einlesen der Auswahl statt. Read(auswahl) liest ein Zeichen von der Tastatur und steckt es in die Variable (auswahl). Damit nur gültige Eingaben angenommen werden - also ‘1’ bis ‘5’ - ist um diese Anweisung eine REPEAT-Schleife gelegt (Zeilen 53 bis 55). Der Rechner liest Eingaben ein, bis das Zeichen im gültigen Bereich liegt.

Die gewünschte Aktion wird in Abhängigkeit von der Eingabe in einer CASE-Anweisung ausgewählt. Dabei sind nur ‘1’ bis ‘4’ berücksichtigt. ‘5’ - die Auswahl “Ende” - soll nur das Ende der Hauptschleife bewirken und keine direkte Aktion.

Die erste Aktion ist die Eingabe eines Rechnungsbetrages für einen Monat. Dabei soll der Benutzer direkt die Monatsnamen eingeben und danach den Wert. Damit nur gültige Monate angenommen werden, liegt um das Einlesen und Aus werten eine REFEAT-Schleife (Zeilen 58 bis 71), die genau dann verlassen wird, wenn ein gültiger Monat erkannt und in monat abgelegt wurde.

Zum Einlesen dient ReadString, das eine Zeichenkette von der Tastatur einliest. Der Komfort dieser Bibliotheksroutine ist vom verwendeten System abhängig. Nun soll herausgefunden werden, welchen Monat der Benutzer eingegeben hat. Da die Felder Indizes vom Typ EinMonat haben, müssen die folgenden Anweisungen in Abhängigkeit von der Zeichenkette MonatsEingabe die Variable monat entsprechend setzen.

Dies geschieht, indem der Rechner das Feld MonatsName Element für Element mit der Eingabe vergleicht, wozu hier eine WHILE-Schleife dient. Davor (Zeile 61) wird monat auf den Anfangswert Januar gesetzt. Die Schleife soll nun solange arbeiten, bis entweder alle Monatsnamen verglichen wurden oder eine Gleichheit des aktuellen Namens mit der Eingabe vorhanden ist. Im Schleifenkörper wird einfach zum nächsten Monat weitergegangen.

Solange monat nicht gleich Undefiniert ist, könnten noch gültige Übereinstimmungen erfolgen. Damit steht ein Teil der Schleifenbedingung fest (Zeile 62). Solange keine Übereinstimmung des aktuellen Monatsnamens (MonatsName[monat]) besteht, muß weitergesucht werden. Das ist die zweite Schleifenbedingung in Zeile 63. Die Bibliotheksfunktion Compare vergleicht zwei Zeichenketten und muß entsprechend dem verwendeten System angepaßt werden.

Um einen Monat weiterzugehen, verwendet das Programm in Zeile 66 die Anweisung INC(monat). INC ist in Modula eingebaut und nimmt bei Aufzählungstypen den nächsten möglichen Wert. Somit ist “INC(Februar)” gleich “Maerz”. Das Gegenstück ist übrigens DEC, das den Vorgänger in einer Aufzählung auswählt (“DEC(maerz)” ergibt “Februar”).

Ist die Schleife beendet, gibt es nur zwei Möglichkeiten. Entweder hat monat den Wert Undefiniert, und die Eingabe paßt auf einen Monatsnamen, oder monat enthält genau den gefundenen Monat. Bitte spielen Sie diese Schleife einmal auf Papier für die Eingabe “Mai” und die Eingabe “Hanswurst” durch.

Ist monat gleich Undefiniert, gibt es eine Fehlermeldung auf dem Bildschirm (Zeilen 68 bis 70), und die Eingabe muß wiederholt werden. Der Rest des Einlesens ist einfach. Da wir nun wissen, in welches Feldelement ein neuer Wert kommt, können wir die Bibliotheks-Routine Read Real aufrufen, die eine Fließkommazahl von der Tastatur einliest und in das Feldelement TelRechn[monat] schreibt.

Die Ausgabe einer Liste aller Rechnungswerte ist einfach. Eine FOR-Schleife durchläuft das Feld TelRechn und gibt jeweils den Monatsnamen und den gespeicherten Wert aus (Zeilen 75 bis 81).

Ebenfalls recht trivial ist das Errechnen der Jahres- und Durchschnittskosten (Zeilen 81 bis 103). Mit einer FOR-Schleife werden zunächst in summe alle Rechnungsbeträge aufsummiert. Damit kann das Programm die Jahreskosten schon ausgeben. Für die Durchschnittskosten ist eine zusätzliche Division durch zwölf nötig (Zeile 98).

Damit sind alle gewünschten Funktionen des Hauptprogramms vorhanden. Bei Beendigung verabschiedet sich unser Telefonprogramm noch höflich (Zeile 109). Jetzt wäre es Zeit für Sie, das Programm abzutippen, zu übersetzen und alle Funktionen auszuprobieren. Versuchen Sie dabei, anhand des Listings zu verfolgen, welche Anweisungen der Rechner gerade ausführt.

Damit ist diese Folge des Modula-Kurses beendet. Beim nächsten Mal kommen wir zu Prozeduren, Funktionen und Parametern. Bis dahin!

Hausaufgaben

  1. Das Programm aus Listing 5 des dritten Teils soll solange Umrechnungen ausführen, wie der Benutzer nicht “E” oder “e” für Ende eingibt. Programmieren Sie mit einer REPEAT-UNTIL-Schleife!

  2. Schreiben Sie ein ähnliches Programm mit WHILE-DO!

  3. Der folgende Teil eines Nonsens-Programms ist extrem schlecht geschrieben. Es liest Zeichen von der Tastatur ein und zählt sie mit. Die Taste “J” wird nicht gezählt. Nach 10 Versuchen ist Schluß. Schreiben Sie das Programm mit WHILE-DO in einem besseren Stil!

    ...
    versuch:=0;
    LOOP 
        versuch:=versuch+1;
        Read(ch);
        IF ch=’J' THEN 
            versuch:=versuch-1;
        END;
        IF versuch=10 THEN 
            EXIT 
        END:
        WriteInt(versuch,5);
        WriteLn;
    END;
    ...
  1. Das folgende Programm gibt die Zahlen 1 bis 10 nacheinander auf dem Bildschirm aus:
    MODULE Aufgabe;
    FROM InOut IMPORT WriteInt;
    VAR i:INTEGER;
    BEGIN
        FOR i:=1 TO 10 DO 
            WriteInt(i,5)
        END
    END Aufgabe.

Schreiben Sie drei Programme mit anderen Schleifenanweisungen, die das Gleiche leisten.


MODULE Rechnung;

FROM InOut      IMPORT Read, Write, WriteString,
                       WriteLn, Readstring;
FROM RealInOut  IMPORT ReadReal, WriteReal;
(* SPC+Megamax:
FROM InOut      IMPORT Read, Write, WriteString,
                       WriteLn, Readstring, ReadReal, WriteReal ; *)

FROM Strings    IMPORT Compare, CompareResults ; (* TDI *)
(* FROM Strings IMPORT Compare ;                    SPC *)
(* FROM Strings IMPORT Compare, Relation ;      Megamax *)

TYPE EinMonat = (Januar, Februar, Maerz, April, Mai, Juni,
                 Juli, August, September, Oktober, 
                 November, Dezember, Undefiniert) ; 
     String   = ARRAY [0..80] OF CHAR ;

VAR TelRechn    : ARRAY [Januar..Dezember]OF REAL;
    MonatsName  : ARRAY [Januar..Dezember]OF String;
    monat       : EinMonat ;
    auswahl     : CHAR ;
    summe       : REAL ;
    MonatsEingabe : String ;

BEGIN
    (* Begrüßung *)
    WriteString('Telefonmanager');
    WriteLn;
    WriteLn;
    (* Alle Felder löschen *)
    FOR monat:=Januar TO Dezember DO 
        TelRechn[monat]:=0.0;
    END;
    (* Monatsnamen initialisieren *)
    MonatsName[Januar]:='Januar';
    MonatsName[Februar]:='Februar';
    MonatsName[Maerz]:='März';
    MonatsName[April]:='April';
    MonatsName[Mai]:='Mai';
    MonatsName[Juni]:='Juni';
    MonatsName[Juli]:='Juli';
    MonatsName[August]:='August';
    MonatsName[September]:='September';
    MonatsName[Oktober]:='Oktober';
    MonatsName[November]:='November';
    MonatsName[Dezember]:='Dezember';
    REPEAT
        (* Menü anzeigen *)
        WriteLn;
        WriteString('(1) Kosten eingeben'); WriteLn; 
        WriteString('(2) Liste ausgeben'); WriteLn; 
        WriteString('(3) Gesamtkosten'); WriteLn; 
        WriteString('(4) Durchschnittskosten'); WriteLn;
        WriteString('(5) Ende'); WriteLn; WriteLn; 
        WriteString('Auswahl: ');
        REPEAT
            Read(auswahl); WriteLn ;
        UNTIL (auswahl>'0') AND (auswahl<'6');
        CASE auswahl OF
            '1' (* Monatsnamen einlesen *)
                REPEAT
                    WriteString('Welcher Monat ? '); 
                    Readstring(MonatsEingabe); WriteLn; 
                    monat:=Januar;
                    WHILE (monat#Undefiniert) AND
(* TDI: *) (Compare(MonatsEingabe,MonatsName[monat])#Equal) DO 
(* SPC:    (Compare(MonatsEingabe,MonatsName[monat])#0) DO *) 
(* Megamax:(Compare(MonatsEingabe,MonatsName[monat])#equal) DO *)
                        INC(monat); (* nächster Monat *) 
                    END;
                    IF monat=Undefiniert THEN (* nicht gefunden *) 
                        WriteString('Falsche Eingabe !'); WriteLn;
                    END;
                UNTIL monat#Undefiniert;
                (* Wert einlesen *)
                WriteString('Kosten ? ');
                ReadReal(TelRechn[monat]); WriteLn ;
            | '2' : FOR monat:=Januar TO Dezember DO
                        WriteString(MonatsName[monat]); 
                        WriteString(' : ');
                        WriteReal(TelRechn[monat],7);
(* SPC+Megamax: WriteReal(TelRechn[monat],7,2);*) 
                        WriteLn;
                    END;
            | '3' : (* aufsummieren *)
                    summe:=0.0;
                    FOR monat:=Januar TO Dezember DO 
                        summe:=summe+TelRechn[monat];
                    END;
                    (* ausgeben *)
                    WriteString('Gesamtkosten : ');
                    WriteReal(summe,5);
(* SPC+Megamax: WriteReal(TelRechn[monatJ,7,2);*) 
                    WriteLn;
            | '4' : (* aufsummieren *)
                    summe:=0.0;
                    FOR monat:=Januar TO Dezember DO 
                        summe:=summe+TelRechn[monat];
                    END;
                    (* Durchschnitt bilden *) 
                    summe:=summe/12.0;
                    (* ausgeben *)
                    WriteString('Durchschnittskosten/Monat : '); 
                    WriteReal(summe,5);
(* SPC+Megamax: WriteReal(TelRechn[monat],7,2);*)
                    WriteLn;
        ELSE
            (* Hier rutscht '5' hinein *)
        END;
    UNTIL auswahl='5’;
    (* Verabschieden *)
    WriteString('Tschüß'); WriteLn;
END Rechnung.


Aus: ST-Computer 04 / 1989, Seite 108

Links

Copyright-Bestimmungen: siehe Über diese Seite