Von Thomas Grasel Wie schon in der letzten Ausgabe ange- kündigt will ich mich diesmal mit Prozeduren und Funktion beschäftigen. Bisher haben wir ja alle Programm- schritte im sogenannten Hauptprogramm ausgeführt. Bei längeren Programmen wird das schnell unübersichtlich. Außerdem werden evtl. Programmteile mehrmals programmiert weil man nicht beliebig zu ihnen verzweigen kann. Abhilfe schaffen da Unterprogramme die Routinen zusammenfassen und aus dem Hauptprogramm auslagern, und es somit verkürzen. In Pascal heißen diese Unterprogramme Prozeduren oder Funktionen. Bevor ich damit beginnen kann auf die Programmierung dieser Unterprogramme genau einzugehen muß ich noch einmal kurz auf die Datentypen von Pascal eingehen. Zunächst einmal die sogenannten 'einfachen Datentypen': byte, integer, real, char, boolean Diese wurden in den bisherigen Demo's schon benutzt und dürften somit bekannt sein. 'Strukturierte Datentypen': array, record, set, file Diese Datentypen unterscheiden sich von den 'einfachen Datentypen' dadurch, daß der User ihre 'Größe' selbst bestimmen kann. Das Array (Feld) wurde ja bereits in der letzten Ausgabe vorgestellt und anhand des Beispielprogrammes 'hezdez' eingeübt. Mit dem 'array'-Befehl kann man ja (fast) beliebig große Felder erstellen. var ch: char hingegen erstellt nur ein Feld der Größe 1! Nun aber zu den auch schon in der letzten Ausgabe angesprochenen Records. Mit 'record' lassen sich Felder ver- schiedenartiger Komponenten zusammen- fassen. Z.B. var pers: record name : array(+1..9)+ of char vorname: array(+1..9)+ of char alter : integer geschl : char end Die einzelnen Komponenten spricht man folgendermaßen an: pers.name :='Meyer ' pers.alter :=22 pers.geschl:='M' Im Gegensatz zu den Arrays können also verschiede Datentyen zusammengefaßt werden. In dem Verbund 'pers' können, wie oben gezeigt, wieder Arrays benutzt werden. Ebenso ist der umgekehrte Weg möglich: var daten: array(+1..4)+ of record alter : integer geschl: char end Hier können die Komponenten wiefolgt angesprochen werden: daten(+1)+.alter:=22 daten(+3)+.geschl:='M' Auf die letzten beiden Datentypen werde ich später eingehen. Ebenso werde ich die Records später noch einmal genauer besprechen. Nun aber noch zu einem wichtigen Punkt, der direkten und indirekten Typverein- barung. Bisher wurden Variablen so vereinbart: var feld1: array(+1..8)+ of boolean feld2: array(+1..8)+ of boolean Es kann aber auch der Datentyp getrennt vereinbart werden: type feld = array(+1..8)+ of boolean var feld1: feld feld2: feld Neben der besseren Lesbarkeit bieten einmal indirekt vereinbarte Typen den Vorteil, daß sie wie oben angedeutet für verschiedene Variablen benutzt werden können. Zu beachten ist das bei Typenvereinbarungen das Gleichzeichen verwendet wird und nicht wie bei Variablenzuweisungen der Doppelpunkt. Parameter von Prozeduren und Funktionen müssen entweder 'einfache Datentypen' oder indirekt vereinbarte Typen sein. Daher auch der kurze Ausflug zu den Variablen. Aber nun zu den Prozeduren. Ein Pascal- programm ist immer wiefolgt aufgebaut: program ... const ... type ... var ... procedure ... <= Nur hier Reihenfolge function ... (fast) beliebig! begin <= Hauptprogramm ... end. Als erstes kommt der Programmkopf, 'program' gefolgt von dem Programm- namen. Danach kommen die Konstantenverein- barungen, falls keine benögtigt werden kann dieser Punkt entfallen. Hinter 'type' können jetzt die Typen- vereinbarungen vorgenommen werden. Falls keine benötigt werden kann auch dieser Punkt entfallen. Jetzt werden wie gewohnt die soge- nannten 'globalen' Variablen definiert. Auch dieser Punkt kann gegebenenfalls entfallen. Nun kommen quasi in beliebiger Reihen- folge Prozeduren und Funktionen. Die freie Wahl der Reihenfolge ist nur dadurch eingeschränkt, daß eine Funktion oder Prozedur nur eine andere aufrufen kann die vor!!! ihr deklariert worden ist. Danach folgt das Hauptprogramm. Die Reihenfolge Programmkopf, Kon- stanten, Typen, Variablen, Prozeduren / Funktionen, Hauptprogramm, ist fest- gelegt und darf nicht verändert werden! Im folgenden werde ich zuerst auf die Prozeduren genauer eingehen. Eine Prozedur ist weitestgehend wie ein Programm aufgebaut. An Stelle des Schlüsselwortes 'program' steht hier 'procedure'. Sowie hinter 'end' ein Semikolon anstatt des Punktes. Hierzu ein kleines Demo: program proc1 var anzahl,zaehler: integer procedure kopf var i: integer begin for i:=1 to 17 do write('*') writeln writeln('* Unterprogramm *') for i:=1 to 17 do write('*') writeln writeln('Aufruf #',zaehler) end begin writeln('Demo zu Prozeduren') writeln writeln('Wieoft soll zum Unterprogramm') write('verzweigt werden ? ') readln(anzahl) for zaehler:=1 to anzahl do kopf writeln writeln('ENDE') end. Wie sie sehen wird die Prozedur im Hauptprogramm ganz einfach mit ihrem Namen aufgerufen. Die Prozedur hat eine sogenannte lokale Variable 'i'. Die Variablen 'anzahl' und 'zaehler' heißen globale Variablen. Lokale Variablen sind dem Hauptprogramm nicht bekannt! D.h. eine Abfrage der Variable 'i' im Hauptprogramm hätte eine Fehlermeldung zur Folge (Variable nicht bekannt!). Der Prozedur hingegen sind alle globalen Variablen bekannt. D.h. die Prozedur kann sie, wie oben gezeigt, lesen und gegebenenfalls ändern. Prozeduren haben wir auch bisher schon genutzt! So sind z.B. write( ) und read( ) ebenfalls Prozeduren. Diese werden uns schon vom Pascal bereit gestellt. Sie weisen jedoch eine Besonderheit auf. Hinter dem Prozedurnamen stehen in Klammern irgend welche Werte, soge- nannte Parameter. Den Sinn solcher Parameter soll folgendes Demo klären: program proc2 var anzahl: integer procedure stern(anz: integer) var i: integer begin for i:=1 to anz do write('*') writeln anz:=0 end begin writeln('Prozedur mit Parametern') repeat write('Anzahl der Sterne (0 fuer Ende) : ') readln(anzahl) stern(anzahl) until anzahl=0 end. Wie sie sehen, wird die Prozedur jetzt nicht mehr nur mit ihrem Namen aufge- rufen, sondern zusätzlich mit einem Parameter. In diesem Fall spricht man von einem Werteparameter (call by value), der Inhalt der Variablen 'anzahl' wird beim Prozeduraufruf in die lokale Variable 'anz' kopiert. Hierbei muß unbedingt darauf geachtet werden, daß 'anzahl' und 'anz' vom gleichen Typ sind! Die Variable 'anz' wird in Klammern direkt hinter den Prozedurnamen in den 'Prozedurkopf' geschrieben. Auffällig ist das das Schlüsselwort 'var' im Programmkopf nicht erscheint. Dies bewirkt das beim Rücksprung der Inhalt von 'anz' nicht in 'anzahl' zurück- kopiert wird. Probiert das Demoprogramm doch mal aus. Wenn ihr z.B. '5' eingebt werden 5 Sternchen ausgegeben und erneut auf eine Eingabe gewartet! Und dies obwohl in der Prozedur die Anweisung 'anz:=0' steht! Ihr könnt also 'anz' nach Herzenlust verändern ohne das 'anzahl' verändert wird. Fügt nun doch mal 'var' in den Prozedurkopf ein. Dieser müßte nun folgendermaßen aussehen: procedure stern(var anz: integer) Wenn ihr nun das Programm startet, und wiederum '5' eingebt, werden 5 Sternchen ausgegeben und das Programm anschließend beendet. 'anz' muß also beim Rücksprung zum Hauptprogramm seinen Inhalt nach 'anzahl' zurückkopiert haben. Man spricht nun nicht mehr von Wertepara- metern, sondern von Referenzparametern (call by referenz). Diese zwei Übergabearten müssen unbe- dingt auseinander gehalten werden deshalb noch einmal zusammengefaßt: Werteparam. (call by value, kein var!): Eine Kopie des Wertes wird an die Prozedur übergeben. Er kann in ihr verändert werden, ohne daß er im aufrufenden Programm verändert wird. Referenzp. (call by reference, var!): An die Prozedur wird nicht die Kopie des Wertes übergeben, sondern die Adresse wo der Wert steht. Somit wird der Wert im aufrufenden Programm verändert! Das heißt, daß im letzten Demo, mit 'var', 'anz' und 'anzahl' auf ein und denselben Wert zugegriffen haben. Wer sich mit Zeigerprogrammierung aus- kennt, dem sei gesagt, daß die Adresse des Zeigers von 'anzahl' kopiert wird. Aber keine Angst wer sich mit Zeigern (noch) nicht auskennt, auch diese werden noch im Rahmen dieses Kurses erklärt! Natürlich kann man nicht nur einen Parameter übergeben. Auch die Mischung von Werte- und Referenzparametern ist möglich. Wichtig ist aber, daß die Anzahl der Parameter im Hauptprogramm und im Prozedurkopf übereinstimmen. Es wird jeweils der erste Wert im aufrufenden Programm in den ersten Wert im Prozedurkopf kopiert. Die Reihenfolge der Werte muß also stimmen und, wie schon erwähnt, der Typ der Variablen. Diese Punkte sind deshalb so wichtig da der Compiler Fehler bei der Parameter- übergabe nur sehr selten erkennt, und das Programm sonst beim Start gnadenlos abstürzt oder wirre Ergebnisse liefert. Wie oben schon erwähnt dürfen im Prozedurkopf nur einfache Datentypen oder indirekt vereinbarte Typen ver- wendet werden. Soll also zum Beipiel ein boolean-Wert sowie ein Array der Länge 4 (of char) übergeben werden, so muß das folgendermaßen geschehen: program proc3 type feld = array(+1..4)+ of char var error: boolean liste: feld procedure unter(var block : feld fehler: boolean) begin ... end begin ... unter(liste,error) ... end. Damit will ich für diese Ausgabe zum Schluß kommen. Leider war er diesmal doch sehr theoretisch, aber die Programmierung von Prozeduren ist einfach zu wichtig um sie einfach mal kurz abzuhandeln. Nun aber noch ein Ausblick auf den Pascal-Kurs #9: Ich werde mich mit dem 'Namenskonfikt' beschäftigen, also der Frage was, und warum was passiert, wenn man einen Variablennamen z.B. im Hauptprogramm und in einer Prozedur doppelt benutzt. Außerdem führe noch einen wichtigen Teil von Pascal ein, die Funktion.