Claus SchönleberHitchhacker´s Guide To PASCAL [Vol. 1] |
||
|
[ Start | Beispielprogramme Download | Home ] | |
InhaltCompilerProgrammieren Datentypen, Variablen, Standardfunktionen Logik Verzweigung, Strukturierung Schleifen Felder (Arrays) Verteiler: CASE Zeichenketten (Strings) Textdateien Module (Prozeduren, Funktionen) Anhang (Operatoren, abgeleitete Funktionen)
|
PASCAL-ModuleEin großes Problem bei der Formulierung von großen Programmen ist die Tatsache, daß man nicht mehr übersehen kann, wie die einzelnen Teile zusammenhängen. Stellen Sie sich vor, der von Ihnen verwendete Algorithmus schreibe folgende Schritte vor:
Blackbox PrinzipWarum uns das reicht, läßt sich erklären mit dem "Blackbox"-Prinzip. Ein Beispiel: Um zu verstehen, wie ein Computer funktioniert, fängt man mit dem Arbeitsprinzip von Dioden und Transistoren an. Daraus baut man größere Einheiten, die aus Transistoren zusammengebaut sind, die Flip-Flops. An dieser Stelle vergißt man die Einzelteile des Flip-Flops, da die Funktionsweise der Transistoren schon klar ist. Nur die Arbeitsweise von Flip-Flops interessiert noch. Die Flip-Flops sind jetzt sogenannte "Blackboxes" mit definierten Eingangs- und Ausgangsparametern. Aus Flip-Flops werden in der nächsten Stufe integrierte Bausteine zusammengesetzt, die sehr viele Flip-Flops enthalten. Jetzt interessieren nicht mehr die einzelenen Flip-Flops, sondern die Wirkungsweise von integrierten Bausteinen, die in bestimmter Art und Weise zusammengesetzt sind. Diese Bausteine sind nun Blackboxes höherer Ordnung. Die nächste Stufe ist zum Beispiel ein Mikroprozessor, eine weitere Stufe kann die Hardware für einen Computer sein. Das Wichtige dabei ist, daß man in jeder höheren Stufe von der bisher betrachteten Stufe abstrahieren kann und muß. Nicht mehr die Einzelteile interessieren, sondern ihr Zusammenspiel, soweit es von außerhalb der "Blackbox" wahrgenommen werden kann.Diese Betrachtungsweise ist notwendig, um größere Zusammenhänge zu verstehen. Um die Funktionsweise eines Computers zu verstehen, ist es ungünstig, sich mit dem Halbleiterprinzip zu beschäftigen. Es nutzt genauso wenig, wie die Beschäftigung mit Molekularchemie bei der Betrachtung eines Rembrandts. Dasselbe gilt natürlich auch für die Gestaltung von Programmen. Kann man mehrere elementare Operationen in eine Blackbox zusammenfassen, so bekommt das Programm eine übersichtlichere Struktur. Auch diese Zusammenfassungen kann man wieder, bei Bedarf, zu höheren Einheiten zusammenfassen. Dies wird durch die Einführung von Unterprogrammen erreicht. Eigentlich ist der Name irreführend, denn es handelt sich um Teile oder Module eines nach dem Baukastenprinzips zusammengestellten Programms. Diese Module, wie sie hier heißen sollen, werden in PASCAL in zwei Kategorien eingeteilt: Prozeduren und Funktionen. Wir haben schon einige Vertreter dieser beiden Arten von Modulen kennengelernt, Standardfunktionen und -prozeduren. Interessanter ist jedoch die Möglichkeit, solche Module selber zu definieren und zu benennen. Das soll im Folgenden besprochen werden. Merksatz: Prozeduren und Funktionen sind nur aktiv während der Zeit zwischen Aufruf und Ende ihrer Arbeit. Prozeduren, FunktionenEin Problem, das vor allen anderen besprochen werden muß, ist die Tatsache, daß man bei der Benutzung von Modulen, gleichgültig ob es sich um Hardware (Platinen) oder Software (Prozeduren, Funktionen) handelt, die Eingangs- und Ausgangsparameter irgendwie festlegen muß.Hier ist ein grundlegender, wenn auch nicht strenger Unterschied zwischen beiden Prinzipien zu sehen:
ParameterübergabeDie beiden Methoden sind:
Schauen wir uns dafür noch einmal das Schaubild einer Variablen an: Eine Variable hat im Speicher (Adressraum der Maschine) eine feste Adresse, vergleichbar mit einer Postadresse. Sie ist im Schaubild durch die Zahl "0x12345" dargestellt. Bei der Deklaration einer Variablen werden der Name (hier: "a") und der Typ (integer) mit einer Adresse im Speicher assoziiert. Dies wird in einer internen Tabelle vermerkt. Die Variable bekommt nun durch irgendwelche Aktionen einen Inhalt, im Bild durch das Symbol 5 dargestellt. Übergabe "per value" (Wertübergabe)Will man nun einer Prozedur oder Funktion einen aktuellen Parameter übergeben, so kann man das zum Beispiel "per value" machen. Das bedeutet, daß man für die Dauer der Aktivierung der Prozedur oder Funktion eine zweite, temporäre Variable gleichen Typs einrichtet und den Wert des Parameters dorthin kopiert. Jetzt kann mit diesem Wert gerechnet werden. Der Parameter selbst kann durch eine Zuweisung an diese Kopie nicht verändert werden.Übergabe "per reference" (Referenzübergabe)Will man eine Übergabe "per reference" machen, wird dem Modul nicht der Wert übergeben, sondern nur die Adresse des Parameters. Das bedeutet, daß man durch Zuweisungen an den Parameter diesen verändern kann. Das nennt man (kontrollierten) Seiteneffekt.In beiden Fällen bekommt der Parameter einen gesonderten Namen. Im Falle der Übergabe "per value" bezeichnet dieser Name tatsächlich eine neue, jedoch nur zeitweise eingerichtete Variable, im Falle der Übergabe "per reference" stellt die neue Bezeichnung nur einen Platzhalter für die zu übergebene Variable dar. In diesem Fall muß man sich für die vorgegebene Bezeichnung immer den eingesetzten Parameter denken. Bei der Übergabe "per reference" wird keine Kopie angelegt, sondern
ein Rückverweis auf den aufrufenden Parameter erzeugt. Die
Die Übergabe "per reference" wird im Modulkopf (siehe nächsten Abschnitt) mit dem Wort VAR eingeleitet. Die ProzedurEine Prozedur wird definiert wie folgt:Es sind, bevor wir uns ein Beispiel ansehen, ein paar Bemerkungen notwendig. Innerhalb von Prozeduren und Funktionen müssen wir drei, genauer gesagt vier Arten von "Variablen" unterscheiden:PROCEDURE <name> {(<parameterliste>)}; <deklarationsteil> <block>;
(Formale) Parameter werden im Prozedur- bzw. Funktionskopf definiert. Lokale Variablen werden im moduleigenen Deklarationsteil definiert. Die Deklaration unterscheidet sich formal nicht von der Deklaration der Globalen Variablen, bis auf einen Unterschied: Lokale Variablen gelten ausschließlich innerhalb des Moduls, in dem sie definiert wurden (lokaler Geltungsbereich). Sobald das Modul abgearbeitet worden ist, hören sie auf zu existieren. Innerhalb eines Moduls kann man auch Globale Variablen benutzen; das ist jedoch weitestgehend zu vermeiden, da durch ihre Benutzung unerwünschte Seiteneffekte auftreten können. Merksatz: Bei der Benutzung von Modulen (Prozeduren und Funktionen) sollte darauf geachtet werden, daß die im Modul verwendeten Daten nur über definierte Parameter übergeben, Zwischenergebnisse nur über Lokale Variablen verarbeitet werden. Nur in Ausnahmefällen sollten Globale Variablen in einem Modul direkt verändert werden. Daß sollte aber sehr sorgfältig geplant und durchgeführt werden. Ach, eigentlich sollte man das gar nicht machen! Dieses Prinzip dient nicht nur der Reinheit der Programmstruktur. Es hat auch einen praktischen Hintergrund. Will man nämlich ein Modul austauschen, so ist es sehr viel leichter, wenn man sich an das Prinzip gehalten hatte. Hat man reichhaltig mit Globalen Variablen hantiert, fallen Änderungen im Nachhinein sehr schwer. Somit ist die Einhaltung des Prinzips eine Voraussetzung für die leichte Wartbarkeit (und damit der Übersicht) von Quelltexten. Wir kommen zu zwei Beispielen: 1. Tauschen zweier VariablenIn diesem Beispiel werden einer Prozedur mit Namen "swap" zwei Parameter (x und y) "per reference" übergeben. Das bedeutet, daß hier ein erwünschter Seiteneffekt auftritt. Als Hilfsvariable wird eine Variable "h" benutzt, die nur während der Bearbeitung von "swap" notwendig ist und deswegen lokal definiert ist. Sobald "swap" fertig ist, "vergißt" das System das Vorhandensein dieser Variable. Sie gilt dann wieder als nicht definiert. Aufgerufen wird diese Prozedur innerhalb des Programms.PROCEDURE swap (VAR x,y : real); VAR h : real; BEGIN h := x; x := y; y := h; END; Die Parameter x und y in der Prozedurdefinition werden formale Parameter genannt. Beispielprogramm zur Prozedur "swap": Die Variablen a und b, mit denen die Prozedur swap im Hauptprogramm aufgerufen wird, werden aktuelle Parameter genannt.PROGRAM proc_beispiel; VAR a,b : real;PROCEDURE swap (VAR x,y : real); VAR h : real; BEGIN h := x; x := y; y := h; END; BEGIN (*** des Hauptprogramms ***) write ('Geben Sie zwei Zahlen ein: '); readln (a,b); writeln ('a= ',a,' b= ',b); swap (a,b); writeln ('a= ',a,' b= ',b); END. Um den Seiteneffekt zu demonstrieren, wird der Inhalt von "a" und "b" vor und nach dem Aufruf der Prozedur "swap" auf dem Bildschirm ausgegeben. Die Variablen "a" und "b" im Prozeduraufruf werden ja "per reference" übergeben; somit sind in der Prozedur "swap" der Parameter "x" mit der Adresse von "a" und der Parameter "y" mit der Adresse von "b" assoziiert. Eine Zuweisung an "x" und "y", wie sie in der Prozedur "swap" geschieht, verändert also die im Aufruf enthaltenen Variablen "a" und "b". Wie man am Beispiel schön sieht, steht im Hauptteil des Programms nur noch die Anweisung "Tausche a und b" (swap (a,b)); Diese Anweisung reicht einem Programmierer vollständig, um diese Problemlösung vollständig zu erfassen. Wie getauscht wird, ist hier nicht wichtig, kann aber bei Bedarf unter der Prozedur nachgesehen werden. Ein weiterer Vorteil: Ändert sich das Verfahren, das in der Prozedur formuliert worden ist, was immer mal wieder vorkommen kann, dann braucht man nicht das eigentliche Programm zu ändern, sondern muß nur das Verfahren in der Prozedur ändern. Dies dient auch wieder zur Vereinfachung der Wartung. 2. Inkrementieren einer VariablenDazu sind mehrere Dinge zu sagen:PROCEDURE incr (VAR argument : integer; step : integer); BEGIN IF (step <> 1) THEN argument := argument + step ELSE argument := succ (argument) END;
PROCEDURE p (VAR x : real; s,t : real; w : STRING);Parameter gleichen Typs und gleichen Übergabeprinzips werden, getrennt durch Kommata, zusammengefaßt.
Wieder wird der Inhalt der übergebenen Variablen vor und nach der Ausführung zur Kontrolle auf dem Bildschirm ausgegeben. Wie man im Programm sieht, braucht man die Schrittweite "s" nach der Prozedur nicht mehr. Deswegen die übergabe "per value".PROGRAM proc_beispiel2; VAR a,s : integer; PROCEDURE incr (VAR argument : integer; step : integer); BEGIN IF (step <> 1) THEN argument := argument + step ELSE argument := succ (argument) END; BEGIN (*** des Hauptprogramms ***) write ('Geben Sie eine Zahl ein: '); readln (a); write ('Schrittweite: '); readln (s); writeln ('a= ',a); incr (a,s); writeln ('a= ',a); END. Die FunktionDie Definition einer Funtion sieht so aus:Auch hier gelten beide Übergabemethoden. Da Funktionen aber keine Anweisungen sind und nur innerhalb von Ausdrücken verwendet werden, was eine Wertrückgabe (Konstante) erfordert, muß bei Funktionen zusätzlich der Rückgabetyp angegeben werden.FUNCTION <name> {(<parameterliste>)} : <rückgabetyp>; <deklarationsteil> <block> Damit das in der Funktion berechnete Ergebnis auch seinen Weg zurück in den Ausdruck findet, muß der Ergebniswert "der Funktion zugewiesen" werden. Dabei wird der Funtionsname als Variable aufgefaßt. Sehen Sie sich diesen Umstand in den unten aufgeführten Beispielen bitte genau an! Hier die zwei Beispiele: 1. PotenzierenZum Verfahren soll hier nichts gesagt werden; es ist keine schlechte Übung, selber darauf zu kommen.FUNCTION power (basis,exponent : integer) : real; VAR i : integer; result : real; BEGIN result := 1; FOR i := 1 TO exponent DO BEGIN result := result * basis; END; power := result; END; Daß diese Funktion nicht sinnlos ist, liegt an der Tatsache, daß in PASCAL kein Operator existiert (wie zum Beispiel in BASIC), der eine Potenzierung zuließe. Der Rückgabetyp ist deswegen real, weil dadurch der erlaubte Wertebereich des Ergebnisses größer wird. Ein Beispielprogramm zur Funktion "power": Wie Sie sehen, kann der Funktionsaufruf, bei Bedarf, direkt in einen Prozeduraufruf (wie hier bei "writeln") geschrieben werden. Natürlich kann das Ergebnis eines Funktionsaufrufs auch einer typenkompatiblen Variablen zugewiesen werden, was wir im nächsten Beispiel vorfinden werden.PROGRAM funktions_beispiel; VAR a,b : integer; FUNCTION power (basis,exponent : integer) : real; VAR i : integer; result : real; BEGIN result := 1; FOR i := 1 TO exponent DO result := result * basis; power := result; END; BEGIN (*** des Hauptprogramms ***) write ('Geben Sie eine Zahl ein: '); readln (a); write ('Geben Sie den Exponenten ein: '); readln (b); writeln (a,' hoch ',b,' = ',power (a,b)); END. 2. Suchen einer TextdateiAuch zu dieser Funktion zunächst einige erklärende Worte:FUNCTION search (VAR f : text; n : STRING) : boolean; BEGIN assign (f,n); {$I-} reset (f); {$I+} close (f); search := (ioresult = 0); END;
Die Funktion "search" werden wir in dem einen oder anderen Beispielprogramm in diesem und im zweiten Teil des Buches ausgiebig benutzen. Im ELSE-Fall steht natürlich normalerweise der Block, der die Datei bearbeitet, keine Textmeldung.PROGRAM funktions_beispiel; VAR filename : STRING; workfile : text; found : boolean; FUNCTION search (VAR f : text; n : STRING) :boolean; BEGIN assign (f,n); {$I-} reset (f); {$I+} close (f); search := (ioresult = 0); END; BEGIN (*** des Hauptprogramms ***) write ('Dateiname: '); readln (filename); found := search (workfile,filename); IF NOT found THEN writeln ('Datei ',filename,' nicht gefunden!') ELSE writeln ('Datei vorhanden!'); END.
[Ende Teil 1]
|
|
|
(c) 2001 Schoenleber.com |