Modula-2 Kurs, Teil 5

Zum fünften Mal heiße ich Sie jetzt schon willkommen zum Modula-2-Kurs. Auch wenn es noch eine ganze Menge zu den letzten Teilen zu sagen gäbe, gehen wir heute zusammen einen großen Schritt weiter: Es geht um Prozeduren, Funktionen, formale und aktuelle Parameter, um lokale Variablen und einiges mehr.

Die Programme, die Sie bisher zu lesen bekamen und die Sie bisher für die Aufgaben geschrieben haben, bestanden jeweils aus Importen, Deklarationen und einer Liste von Statements, die nacheinander ausgeführt wurden. Jede Aktion mußte mit einer oder mehreren Anweisungen aufgeschrieben werden. Wenn Sie mit den bisherigen Mitteln an zwei Stellen eine Meldung ausgeben und eine Taste einlesen wollten, dann mußten Sie eben zweimal eine Reihe ziemlich ähnlicher Statements notieren. Auf die Dauer ist das natürlich lästig und verlängert den Programmtext.

Prozeduren

Eine Prozedur beschreibt eine Reihe von Anweisungen, die unter dem Namen der Prozedur zusammengefaßt werden. Durch einen Prozeduraufruf im Programm führt der Rechner die vorher festgelegten Aktionen aus. Im Programmtext wird eine Prozedurdefinition notiert, die aus einem Prozedurkopf und einem -körper besteht:

PROCEDURE <Bezeichner>; 
<Deklarationen>
BEGIN
    <Anweisungen>
END <Bezeichner>;

Alle aufgeführten Anweisungen werden bei einem Aufruf mit dem Namen <Bezeichner> ausgeführt. Sie sehen schon an der Beschreibung, daß auch bei Prozeduren Deklarationen möglich sind, z.B. für Variablen.

Lokalität

Alle innerhalb einer Prozedur deklarierten Datenobjekte - also Variablen, Konstanten oder Typen - gelten als “lokal”. Lokal bedeutet, daß ihre Bezeichner nur innerhalb der Prozedur bekannt sind. Deklarieren Sie eine Variable in einer Prozedur, können Sie sie im Prozedurkörper wie gewohnt verwenden, da sie aber lokal ist, ist sie dem Compiler außerhalb der Prozedur unbekannt.

Was passiert nun, wenn im Hauptprogramm eine Variable x vom Typ INTEGER deklariert wurde und in einer Prozedur x vom Typ CHAR? In diesem Fall bezieht sich jeder Verweis auf x außerhalb der Prozedur auf einen INTEGER-Wert. Innerhalb des Prozedurkörpers überlagert das lokale x das vorher deklarierte globale x, also gilt x hier als CHAR.

Damit können lokale Objekte unabhängig von der “Außenwelt”, also dem Hauptprogramm, deklariert werden, da es zu keinen Namenskonflikten kommen kann. Folge ist aber auch, daß in besagter Prozedur der globale INTEGER-Wert x nicht mehr erreichbar ist.

Neben der “Bekanntheit” eines Datenobjekts gibt es noch dessen “Lebensdauer”, die in anderem Zusammenhang noch wichtig wird. Lokale Variablen in Prozeduren werden eingerichtet, wenn die Ausführung des Prozedurkörpers beginnt. Sind alle Anweisungen einer Prozedur abgearbeitet, werden intern auch alle Variablen vernichtet und verlieren damit ihren Inhalt.

Ein Beispielprogramm muß an mehreren Stellen einen Tastendruck vom Benutzer erwarten. Ohne Prozeduren müssen Sie dazu mehrmals den gleichen Programmtext notieren. Die Aufgabe, auf eine Taste zu warten, soll in eine Prozedur verlagert werden. Sie könnte wie in Listing 1 aussehen.

PROCEDURE Meldung;
VAR ch:CHAR;
BEGIN
    WriteString('Bitte Tastedrücken'); 
    Read(ch);
    WriteLn;
END Meldung;

Listing 1

Der Prozedurkopf leitet eine Prozedurdeklaration ein und teilt dem Compiler mit, daß die folgenden Anwei sungen unter dem Namen Meldung bekannt sein sollen Zum Einlesen eine Taste muß eim CHAR-Variable deklariert werden die unter dem Namen ch nur innerhalb von Meldung bekannt ist und ihren Inhalt bei Beendigung der Prozedur verliert.

Mit BEGIN werden die Anweisungen eingeleitet. Die simple Aufgabe besteht darin, eine Aufforderung zum Tastendruck auf dem Bildschirm auszugeben, eine Taste mit Read einzulesen und einen Zeilenvorschub auszugeben. Die drei Anweisungen leisten das Gewünschte. Es würde ohne Prozeduren in dieser Form mehrmals in Ihrem Programmtext auftauchen.

END Meldung; schließt die Prozedurdeklaration ab. Im Programmtext kann die Prozedur nun mit der neuen Anweisung

Meldung;

aufgerufen werden. Beim laufenden Programm wird dadurch ch eingerichtet, die drei Anweisungen ausgeführt und ch wieder vernichtet. Der Rechner arbeitet dann an der Stelle nach dem Aufruf weiter.

# Anworten von Teil IV
  1. Das Programm muß wie folgt aussehen:
    ...
    WriteString('Celsius nach Fahrenheit (J/j) ? ');
    Read(Anwort); WriteLn;
    REPEAT
        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;
        WriteString('Celsius nach Fahrenheit oder Ende (J/j/E/e) ? ');
        Read(Antwort); WriteLn;
    UNTIL (Antwort='E') OR (Antwort='e') ;

Der Benutzer muß sich zunächst für eine Umrechnung entscheiden. Bei der nächsten Anfrage hat er die Möglichkeit, abzubrechen. Die Entscheidung im Programm findet am Ende des REPEAT-WHILE statt.

  1. Mit WHILE-DO sieht das Programm so aus:
    ...
    WriteString( 'Celsius nach Fahrenheit oder Ende (J/j/E/e) ? ');
    Read(Antwort); WriteLn;
    WHILE (Antwort#'E') AND (Antwort#'e') DO 
        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;
        WriteString('Celsius nach Fahrenheit oder Ende (J/j/E/e) ? ');
        Read(Antwort); WriteLn;
    END;
    ...

Wenn der Benutzer schon beim ersten Mal “E” oder “e” eingibt, wird keinerlei Berechnung ausgeführt. Nicht nur die Abfrage der Bedingung findet an anderer Stelle statt, auch muß die Bedingung selber negiert werden.

    ...
    versuch:=1;
    WHILE (versuch<10) DO 
        Read(ch);
        IF ch#'J' THEN
            versuch:=versuch+1;
        END;
        Writelnt(versuch,5);
        WriteLn;
    END;
    ...
  1. Die Hauptteile der drei Programme müssen so aussehen:
    Mit REPEAT-UNTIL:

    BEGIN 
        i:=1;
        REPEAT
            WriteInt(i,5); 
            i:=i+1;
        UNTIL (i=11)
    END.

    Mit WHILE-DO:

    BEGIN 
        i:=1;
        WHILE (i<11) DO 
            WriteInt(i,5); 
            i:=i+1 
        END 
    END.

    Mit LOOP-EXIT:

    BEGIN 
        i:=1;
        LOOP
            WriteInt(i,5); 
            i:=i+1;
            IF (i=11) THEN EXIT END 
        END 
    END.

Parameter

Die gelistete Prozedur kann nur eine einzige Aufgabe übernehmen. Sie gibt immer denselben Text auf dem Bildschirm aus und ist damit sehr unflexibel. Die Meldung “Falsche Eingabe" würde eine zweite Prozedur erfordern.

Sinnvoll wäre es, der Prozedur quasi “mitzuteilen", welchen Text sie ausgeben soll. Dazu gibt es das Sprachmittel der Parameter. Ein Parameter ist etwas, das beim Aufruf einer Prozedur mitgegeben wird. In Modula-2 kann hinter dem Namen einer Prozedur eine Liste aller Parameter folgen. Ein formaler Parameter wird beschrieben durch einen formalen Namen und einen Typ.

PROCEDURE <Bezeichner>
         (<Parameterliste>);

Ein Beispiel, in dem einer Prozedur ein Wert übergeben wird:

PROCEDURE Nummer(n:INTEGER); 
BEGIN 
    n:=n+1;
    WriteInt(n,5);
END Nummer;

Der formale Name entspricht ein bißchen den lokalen Variablen. Beim Aufruf richtet der Rechner eine lokale Variable unter dem angegebenen Namen ein und schreibt den übergebenen Wert ein. Ein Aufruf

Nummer(5);

führt zur Existenz der lokalen Variablen n und zum Setzen von n auf 5. In einer Prozedur mit Parametern wird ganz normal mit den Parametervariablen umgegangen, nur daß eben ihr Wert schon beim Aufruf gesetzt wird und ein jeweils anderes Verhalten der Prozedur bewirkt.

Als aktueller Parameter beim Aufruf können alle Ausdrücke verwendet werden, deren Ergebnis vom Typ des formalen Parameters ist. Möglich wäre auch

Nummer(10+5);
Nummer(a);
Nummer(a+b);

Der Rechner wertet jeweils den gesamten Ausdruck im Moment des Prozeduraufrufs aus. Bei Nummer(a) erhält der formale Parameter n den aktuellen Wert von a. Eine etwaige Veränderung von n in Nummer führt nur zur Veränderung von n, aber nicht von a.

Dieses Vorgehen wird “Call-by-value" genannt. Die Prozedur erhält als Parameter das Ergebnis, also den Wert eines Ausdrucks geliefert. Alle Veränderungen an der Parametervariablen sind lokal und haben keinen Einfluß auf die Daten “außerhalb" der Prozedur.

Das Gegenstück dazu ist “Call-by-name". Dabei erhält die Prozedur nicht den Wert des Parameters beim Aufruf, sondern eine Referenz darauf. Als Parameter können nur noch Variablen übergeben werden. In der Prozedurdefinition werden diese Parameter durch das Schlüsselwort VAR gekennzeichnet. Ein Beispiel:

PROZEDURE Dopple(VAR n:INTEGER); 
BEGIN 
    n:=n+n;
END Dopple:

n ist hier ein variabler Parameter, für den der Name des aktuellen Parameters beim Aufruf eingesetzt wird. Bei einem Aufruf mit Dopple(a); - a ist dabei vorher als INTEGER deklariert und hat den Wert 5-führt die Prozedur tatsächlich die Zuweisung a:=a+a; aus, da ihr für n eine Referenz auf a übergeben wurde. Die Variable a hat hinterher den Wert 10.

Wäre in diesem Beispiel n als Werteparameter, also ohne VAR, definiert, hätte die Prozedur der lokalen Variablen n den Wert 5+5 zugewiesen, a bliebe unverändert.

Da immer ein Name benötigt wird, können beim Aufruf für variable Parameter nur Variablen eingesetzt werden. Ausdrücke sind nicht mehr erlaubt, da auf sie keine Referenz möglich ist. Der Compiler erkennt Fehler z.B. bei:

Dopple(1);
Dopple(5+5);
Dopple(a+1);
Dopple(a+b);

Ob variable oder Werteparameter verwendet werden, hängt von der Anwendung ab. Soll eine Prozedur aufgrund eines Wertes eine bestimmte Aktion ausführen, werden Sie sicherlich einen normalen Werteparameter benutzen. Durch die Lokalität der Parametervariablen steigt die Sicherheit beim Programmieren, da Fehler in einer Prozedur nur auf diese beschränkt bleiben.

Immer wenn die Prozedureinen Parameter bearbeiten soll - so wie in Dopple, muß ein variabler Parameter verwendet werden. Dabei ist zu beachten, daß eine solche Prozedur auf Variablen des Hauptprogramms zugreift und sie verändert.

Offene Feldparameter

Es gibt einen Sonderfall in der Behandlung von Parametern - die Felder. Prozeduren, die mit Feldern als Parametern arbeiten, könnten aufgrund der Typstrenge immer nur Felder einer Größe verarbeiten. Gerade bei der Behandlung von Zeichenketten hätte das fatale Folgen.

Der Ausweg sind die offenen Felder als Parameter. Dabei wird als Parametertyp eine ganze Klasse von Feldern notiert, indem die Größenangabe wegfällt. In einer Definition

PROCEDURE x(f:ARRAY OF CHAR);

kann als Parameter ein Feld mit fünf Elementen oder auch eines mit 100 Elementen eingesetzt werden, ohne daß sich ein Fehler ergibt. Natürlich müssen die Felder den angegebenen Grundtypen - hier CHAR - haben. Eine Schachtelung offener Felder ist nicht möglich.

Als Beispiel sehen Sie in Listing 2 (siehe nächste Seite) das obige Listing der Meldungsprozedur, bei der die auszugebende Meldung als Parameter übergeben wird. Strings werden bekanntlich in Modula-2 als Felder von Zeichen dargestellt, die Verwendung offener Feldparameter ist bei allen String-Manipulationen notwendig, um unterschiedlich lang deklarierte Zeichenketten verarbeiten zu können.

1: PROCEDURE Meldung(text:ARRAY OFCHAR); 
2: VAR ch:CHAR;
3: BEGIN
4:      WriteString(text);
5:      Read(ch);
6:      WriteLn;
7: END Meldung;

Listing 2

Wie geht man nun in einer Prozedur mit einem solchen Parameter um? Die Indexgrenzen sind nicht bekannt, man weiß nicht, bei welchem Element das Feld beginnt und wo es endet.

Das jeweils erste Element eines offenen Feldparameters erhält den Index 0. Dabei ist es ist es unwichtig, ob der aktuelle Parameter z.B. als ARRAY [100..200] deklariert wurde.

Um die Größe des Feldes festzustellen, gibt es die Funktion HIGH. Sie ergibt errechnet die Anzahl der Feldelemente minus 1. Bei einem ARRAY [100..200] als aktuellem Parameter würden in der Prozedur die Feldgrenzen [0..HIGH(f)], also [0..100] gelten. HIGH(f) ergibt hier 100, also 101 Feldelemente minus 1.

Funktionen

Prozeduren, die ein Ergebnis abliefern, werden als Funktionen bezeichnet. Sie verarbeiten die Parameter und erzeugen daraus einen Funktionswert. Dieser Funktionswert kann dann in Ausdrücken verwendet werden, wobei der Aufruf der Funktion Teil des Ausdrucks ist.

Eine Funktion Quadrat soll aus einem INTEGER-Wert dessen Quadrat errechnen und das Ergebnis als LONGINT abliefern. In einer Zuweisung könnte sie so verwendet werden:

x:=Quadrat(a)+12+Quadrat(10);

Da Funktionen in allen Ausdrücken verwendet werden können, ist natürlich auch eine Funktion bzw. deren Ergebnis als Werteparameter erlaubt:

x:=Quadrat(Dopple(10));

Bei der Auswertung des Parameters für Quadrat wird Dopple(10) ausgeführt und dann das Funktionsergebnis 20 als Parameter in Quadrat eingesetzt. x erhält dann den Wert 400 zugewiesen.

Definiert wird eine Funktion wie eine Prozedur mit einem etwas erweiterten Kopf:

PROCEDURE <Bezeichner>
         (<Parameterliste>): 
          <Ergebnistyp>;

Das Klammerpaar muß auch dann in der Definition und im Aufruf notiert werden, wenn keine Parameter übergeben werden.

Auf die Parameterliste folgt nach einem Doppelpunkt eine Angabe über den Typ des Funktionsergebnisses. Mögliche Ergebnistypen sind in der momentanen Definition von Modula-2 alle einfachen Typen, also z.B. INTEGER, LONGCARD oder CHAR. Die Rückgabe von zusammengesetzten Typen, also Arrays und Records, als Funktionsergebnis ist in einigen Compilern jetzt schon möglich. Die anstehende Revision von Modula wird dies wohl als Standard festlegen.

1: PROCEDURE Dopple(n:INTEGER):INTEGER; 
2: BEGIN 
3:      RETURN n+n;
4: END Dopple;
5:
6: PROCEDURE Quadrat(n:INTEGER):LONGINT; 
7: BEGIN
8:      RETURN LONGINT(n*n);
9: END Quadrat;

Listing 3

1: PROCEDURE Summiere(feld.ARRAY OF INTEGER):LONGINT; 
2: VAR  index:INTEGER;
3:      ergebnis:LONGINT;
4: BEGIN
5:      ergebnis:=0;
6:      FOR index:=0 TO HIGH(f) DO
7:          ergebnis:=ergebnis+LONGINT(feld[index]);
8:      END;
9:      RETURN ergebnis;
10: END Summiere;

Listing 4

1: PROCEDURE SummiereQuadrate(feid:ARRAY OF INTEGER):LONGINT; 
2: VAR  index:INTEGER;
3:      ergebnis:LONGINT;
4:
5: PROCEDURE Quadrat(n:INTEGER):LONGINT;
6: BEGIN
7:      RETURN LONGINT(n*n);
8: END Quadrat;
9:
10: BEGIN
11:     ergebnis:=0;
12:     FOR index:=0 TO HIGH(f) DO
13:         ergebnis:=ergebnis+Quadrat(feld[index]);
14:     END;
15:     RETURN ergebnis;
16: END SummiereQuadrate;

Listing 5

Wie liefert eine Funktion nun ihr Ergebnis ab? Modula kennt dazu die RETURN-Anweisung, die den folgenden Ausdruck auswertet, ihn als Ergebnis abliefert und die Funktion beendet. Der Typ des Ausdrucks muß mit dem vorher festgelegten Ergebnistyp übereinstimmen. Eine Funktion ohne RETURN führt zu einem Laufzeitfehler.

Die Definitionen der oben genannten Funktionen zum Quadrieren und Verdoppeln finden Sie in Listing 3. Das Beispiel in Listing 4 ist eine Funktion, die beliebig große Felder von INTEGER-Werten aufsummiert und das Ergebnis als LONGINT zurückgibt.

Lokale Prozeduren

Die schon bei den Variablen angesprochene Lokalität ist ebenso bei Prozeduren möglich. Prozedurdefinitionen können geschachtelt werden, wobei die gleichen Regeln wie für lokale Variablen gelten.

Soll die obige Funktion Summiere z.B. die Summe der Quadrate aller Feldelemente errechnen, so könnte sie - sehr umständlich - z.B. wie in Listing 5 formuliert werden.

Quadrat ist hierbei eine lokale Funktion innerhalb von SummiereQuadrate. Außerhalb ist sie wie z.B. die Variable ergebnis nicht bekannt. Gibt es außerhalb der Funktion eine Definition von Quadrat, so wird sie innerhalb von SummiereQuadrat durch die lokale Prozedurdefinition überlagert.

Auch hier hat die Lokalität den Vorteil, daß alle Vorgänge in der Funktion nach außen nicht sichbar sind. Sie hängen z.B. nicht von der Definition einer Funktion Quadrat an irgendeiner anderen Stelle im Programm ab. Zudem muß bei der Programmierung der Funktion nicht auf eventuelle Namenskonflikte Rücksicht genommen werden.

Modula-Domain

Während der Fertigstellung des fünften Teils dieser Serie erreichte mich eine Diskette, deren Inhalt für Modula-Interessenten fast sensationell ist: Ein vollständiger Modula-Compiler auf Basis des ETH-Systems als Public Domain!

Das System enthält einen kompletten Compiler, Standard- und GEM-Module, einen Link-Loader und einen Debugger. Implementiert wurde es am Lehrstuhl für Prozeßrechner der TU München.

Die nichtkommerzielle Weitergabe und Benutzung des Systems ist ausdrücklich Ziel der Implementierung. Wie es scheint, haben die ATARI-Benutzer damit einen PD-Compiler zur Verfügung, dessen Leistungsumfang in etwa dem TDI-System entspricht.

Ich habe in der kurzen Zeit noch keine ausführlichen Tests vornehmen können, daher soll dies eine erste Vorankündigung sein. Sollte sich das Paket als brauchbar erweisen - und alles deutet daraufhin, werde ich es als Basissystem für diesen Kurs verwenden.

Bitte warten Sie mit Anfragen bis zur nächsten Ausgabe. Bitte schreiben Sie mich nicht direkt an, und bitte fragen Sie nicht etwa an der TU München nach, denn auf der Diskette wird ausdrücklich jede Beratung etc. ausgeschlossen.

Nach Klärung aller inhaltlichen und rechtlichen Fragen wird das System im Public Domain-Service der ST-Computer erhältlich sein, allerding erst dann, wenn die Diskette quasi offiziell in das Verzeichnis aufgenommen wurde.

An dieser Stelle ein dickes Dankeschön an Jörg Rangnow vom PD-EXPRESS, der mir die Diskette zugänglich gemacht hat! Thanxalot!

Die Aufgabe von Prozeduren

Welche Funktion erfüllen Prozeduren eigentlich in einem Programm? Sie finden sicherlich drei Anwendungen: Effizienz, Flexibilität und Strukturierung. Wenn in einem Programm eine Aufgabe, bestehend aus mehreren Statements, mehrfach an verschiedenen Stellen auszuführen ist, bieten sich Prozeduren an. Einerseits sparen Sie sich Tipparbeit, weiterhin erzeugt der Compiler weniger Programmcode. Schließlich vermindern Sie damit die Anzahl potentieller Fehlerquellen. Damit benutzen Sie Prozeduren quasi als Makros, also als rein textuelle Erleichterung.

Viel wichtiger ist allerdings die Universalität. Eine Prozedur oder Funktion mit Parametern kann verschiedenen Aufgaben dienen. Das Problem wird aufgrund der Parameterfolgen genauer angegeben und eine einheitliche Lösung angeboten. Wenn Sie ein Feld sortieren wollen, kann eine Prozedur so universell formuliert werden, daß sie auf Feldern beliebiger Größe arbeitet, daß sie beliebige Sortierschlüssel oder z.B. in Abhängigkeit von der Feldgröße einen besonders effizienten Algorithmus verwendet. Damit benutzen Sie Prozeduren als ein weit mächtigeres Mittel als Makros.

Schließlich dienen Prozeduren und Funktionen der Strukturierung. Sie machen ein Programm einfacher lesbar, es läßt sich einfacher warten, und schließlich ist es mit einem Top-Down-Entwurf auch einfacher zu entwerfen. Auch wenn ein Programm nur aus den klassischen Grundschritten Eingabe-Verarbeitung-Ausgabe (man nennt das auch EVA...) besteht, mag es sinnvoll sein, jeden Teil als eigenständige Prozedur zu formulieren.

Wann sollten Sie also Prozeduren verwenden? Nun, Sie tun es ständig, wenn Sie Routinen aus Modulen importieren. Aber auch in Ihrem Programmtext sollten Sie im Hauptteil möglichst nur Prozeduraufrufe verwenden. Verwenden Sie Prozeduren immer dann, wenn das Problem sich logisch in mehrere Teile aufteilen läßt, wenn Sie Standardabläufe - z.B. Fehlermeldungen - entwerfen, oder wenn Sie feststellen, daß Sie denselben Programmtext schon zum dritten Mal eintippen.

Prozeduren und Funktionen gewinnen noch in anderen Teilen von Modula-2 eine ungeheure Bedeutung, so bei den Modulen, bei Prozedurvariablen und bei den Coroutinen. Sie werden feststellen, daß Sie bei einigermaßen großen Programmen weit über 90 Prozent Prozedurdefinitionen schreiben.

# Hausaufgaben
  1. Das folgende Programm gibt mehrmals einen Wert aus. Welche Zahlen erscheinen am Bildschirm? Erarbeiten Sie die Lösung auf Papier, nicht am Rechner!
    MODULE Aufgabe1;
    FROM InOut IMPORT WriteInt,
                      WriteLn;
    VAR i:INTEGER;

    PROCEDURE a(n:INTEGER);
    VAR i:INTEGER;

    PROCEDURE b(i:INTEGER);
    BEGIN 
        i:=i+1;
        WriteInt(i,5); WriteLn;
    END b;

    PROCEDURE c(VAR i:INTEGER);
    BEGIN 
        i:=i+1;
        WriteInt(i,5); WriteLn; 
        i:=i+1;
    END c;

    BEGIN
        WriteInt(n,5); WriteLn; 
        b(n); 
        n:=n+1; 
        i:=0; 
        b(n); 
        n:=n+1; 
        c(n);
        WriteInt(n,5); WriteLn;
        WriteInt(i,5); WriteLn;
    END a;

    BEGIN 
        i:=10;
  1. Schreiben Sie eine Prozedur, die einen Balken von “*“-Zeichen zur grafischen Anzeige von Werten ausgibt. Ein Wert von n soll durch n Sternchen dargestellt werden. Zur Zeichenausgabe kann Write und WriteLn verwendet werden.

  2. Erweitern Sie die Prozedur so, daß durch Parameter ein zusätzlicher Text als Legende ausgegeben wird und das Symbol beim Aufruf wählbar ist.

  3. Schreiben Sie eine Funktion, die den Durchschnitt der Werte eines INTEGER-Feldes errechnet und ihn als Ergebnis ausgibt.

Zusammenfassung

In Modula-2 dienen Prozeduren der Strukturierung und der Zusammenfassung von wiederkehrenden Aufgaben in einem Programm. Prozeduren werden global oder lokal definiert und sind unter einem Namen bekannt. Jede Prozedurdefinition besteht aus dem Namen, einer Parameterliste und bei Funktionen einer zusätzlichen Angabe über den Ergebnistyp.

Jeder formale Parameter hat einen Namen und einen Typ. Bei Werteparametern wird eine Parametervariable wie eine lokale Variable behandelt und mit dem Wert des aktuellen Parameters, eines Ausdrucks, vorbelegt. Bei einem variablen Parameter wird eine Referenz auf die als aktueller Parameter übergebene Variable eingesetzt. Bei variablen Parametern kann die Prozedur die übergebene Variable direkt verändern.

Felder können ohne besondere Festlegung der Feldgröße als offene Feldparameter behandelt werden. Die Größe des aktuell übergebenen Felds läßt sich mit der HIGH-Funktion ermitteln. Bei Funktionen wird ein Ergebnis vom definierten Typ mittels RETURN zurückgegeben.

Damit ist dieser fünfte Teil der Modula-Serie beendet. Die nächste Folge wird aus drei Teilen bestehen: Rekursion, Standardfunktionen und der neue Typ der SETs. Außerdem kann ich mich ab dem nächsten Mal mit dem Public Domain-Compiler endlich auf ein für alle Leser verfügbares System stützen.

RT



Aus: ST-Computer 05 / 1989, Seite 92

Links

Copyright-Bestimmungen: siehe Über diese Seite