Claus Schönleber

Hitchhacker´s Guide To PASCAL  [Vol. 1]

 [ zurück | weiter ]
   [ Start | Beispielprogramme Download | Home ]

Inhalt

Compiler
Programmieren
Datentypen,
Variablen,
Standardfunktionen
Logik
Verzweigung,
Strukturierung
Schleifen
Felder (Arrays)
Verteiler: CASE
Zeichenketten
(Strings)
Textdateien
Module
(Prozeduren,
Funktionen)
Anhang
(Operatoren,
abgeleitete
Funktionen)
 

 

 

PASCAL-Module 

Ein 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: 
  • Quadriere A und B 
  • Summiere die Quadrate 
  • Ziehe die Wurzel aus dem Ergebnis 
Dieses Verfahren sagt alles über die Lösung aus, was interessiert. Wie quadriert wird, wie summiert wird oder wie die Wurzel gezogen wird, ist für das Ergebnis nicht relevant. Die Erkenntnis, daß alle drei Operationen durchgeführt werden, reicht für das Verständnis des Verfahrens aus. Es ist nicht notwendig, und es ist auch nur störend, wenn hier noch ausführlichst die drei Operationen bis ins kleinste Detail beschrieben werden würden. Nun wissen wir ja, daß es in PASCAL die beiden Funktionen "sqr" und "sqrt" gibt, die genau das tun, ohne uns damit zu belasten, selber definieren zu müssen, wie das geht. 

Blackbox Prinzip 

Warum 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, Funktionen

Ein 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: 

  • Funktionen geben prinzipiell ein Ergebnis, also eine Wertkonstante zurück. Deswegen können Funktionen nur in Ausdrücken verwendet werden. Sie bilden alleine keine eigenständige Anweisung. 
  • Prozeduren haben wie Funktionen gewisse Daten als Eingangsparameter, geben aber normalerweise keinen Wert zurück, sondern agieren mit den ihnen übergebenen Eingangsparametern eigenständig. Somit sind Prozeduren "kleine" Programme innerhalb von Programmen, die eigenständig etwas tun. Sie können deswegen als Anweisungen gelten. 
Diese Parameterübergabe kann verschieden ablaufen. In PASCAL sind zwei Methoden definiert. 

Parameterübergabe

Die beiden Methoden sind: 
  1. Übergabe per Wert (per value) 
  2. Übergabe per Adresse (per reference) 
Wo ist der Unterschied? 

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
Übergabe "per reference" weist dadurch genau auf die aufrufende Variable zurück und kann deswegen deren Inhalt verändern. Die Übergabe "per value" kann den Wert der aufrufenden Variable nicht verändern. 

Die Übergabe "per reference" wird im Modulkopf (siehe nächsten Abschnitt) mit dem Wort VAR eingeleitet. 

Die Prozedur 

Eine Prozedur wird definiert wie folgt: 
PROCEDURE <name> {(<parameterliste>)}; 
  <deklarationsteil> 
  <block>; 
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: 
  1. Globale Variablen 
  2. Parameter (per reference) 
  3. Parameter (per value) 
  4. Lokale Variablen 
Globale Variablen sind grundsätzlich die, die im Deklarationsteil des (Haupt-)Programms definiert worden sind. Das ist der Ort unmittelbar hinter dem Programmkopf, d.h. vor dem ersten BEGIN. Globale Variablen sollten vermieden werden, weil sie unkontrollierte Seiteneffekte erzeugen, d.h. man übersieht als Programmierer, daß man einer Variablen an einer bestimmten Stelle im Programm einen definierten Wert zugewiesen hat und benutzt sie an anderer Stelle zu anderen Zwecken. Der erste Wert geht dadurch verloren. Solche Fehler sind nur schwer ermittelbar.

(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 Variablen 

PROCEDURE swap (VAR x,y : real); 
  VAR h : real;
BEGIN 
  h := x; 
  x := y; 
  y := h; 
END; 
In 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. 

Die Parameter x und y in der Prozedurdefinition werden formale Parameter genannt.

Beispielprogramm zur Prozedur "swap": 

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. 
Die Variablen a und b, mit denen die Prozedur swap im Hauptprogramm aufgerufen wird, werden aktuelle Parameter genannt.

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 Variablen 

PROCEDURE incr (VAR argument : integer; step : integer); 
BEGIN 
  IF (step <> 1) THEN 
    argument := argument + step 
  ELSE 
    argument := succ (argument) 
END; 
Dazu sind mehrere Dinge zu sagen: 
  • Wenn mehrere Parameter unterschiedlichen Typs oder übergabeprinzips in der Parameterliste auftreten, werden die unterschiedlichen Parameter durch Semikolon getrennt. Beispiel: 
PROCEDURE p (VAR x : real; s,t : real; w : STRING); 
Parameter gleichen Typs und gleichen Übergabeprinzips werden, getrennt durch Kommata, zusammengefaßt. 
  • Der Parameter "argument" muß "per reference" übergeben werden, da die übergebene Variable ja verändert werden soll. 
  • Der Parameter "step" wird "per value" übergeben, was ausreicht, denn dieser Wert wird nur innerhalb der Prozedur für die Berechnung des Ergebnisses gebraucht. 
  • Die IF..THEN-Anweisung wird deswegen benutzt, weil die Standardfunktion "succ" schneller ausgeführt wird, als die Addition. Sie erfordert aber die Schrittweite +1. Bei anderen Schrittweiten wird deswegen normal addiert. 
Beispielprogramm zur Prozedur "incr": 
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. 
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". 

Die Funktion 

Die Definition einer Funtion sieht so aus: 
FUNCTION <name> {(<parameterliste>)} : <rückgabetyp>; 
  <deklarationsteil> 
  <block> 
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. 

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. Potenzieren 

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; 
Zum Verfahren soll hier nichts gesagt werden; es ist keine schlechte Übung, selber darauf zu kommen. 

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":

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. 
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. 

2. Suchen einer Textdatei 

FUNCTION search (VAR f : text; n : STRING) : boolean; 
BEGIN 
  assign (f,n); 
  {$I-} 
  reset (f); 
  {$I+} 
  close (f); 
  search := (ioresult = 0); 
END; 
Auch zu dieser Funktion zunächst einige erklärende Worte: 
  • Dateivariablen müssen "per reference" übergeben werden. 
  • Die beiden Zeichenketten in den Kommentarklammern sind sogenannte Compilerdirektiven und stammen von TurboPASCAL. TurboPASCAL-Direktiven werden in Kommentarklammern, unmittelbar gefolgt von einem Dollarzeichen ("$"), formuliert. Der nachfolgende Buchstabe ist eine Direktive an den Compiler. Das "I" mit dem "-" bedeutet, daß Laufzeitfehler unterdrückt werden, das "+" bedeutet, daß sie nicht unterdrückt werden. Um eine (Text-)Datei zu suchen, muß die Fehlermeldung unterdrückt werden, um einen Programmabbruch zu verhindern, falls die Datei beim Probeöffnen nicht existiert. Das Ergebnis dieses Zugriffs wird in der vordefinierten Systemvariablen "ioresult" abgelegt. Der Wert 0 bedeutet, daß kein Fehler passiert ist. Deswegen wird mit 0 verglichen. 
Beispielprogramm für die Funktion "search": 
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. 
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. 


[Ende Teil 1]
 
 [ zurück | weiter ]
   (c) 2001 Schoenleber.com