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
- 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
-
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.
-
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.â
-
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.
-
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.
- 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.
- 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
-
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!
-
Schreiben Sie ein Àhnliches Programm mit WHILE-DO!
-
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;
...
- 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.