.PL72 .MT1 .MB3 .FM1 .FO .HE TURBO-Pascal Seite # .PN66 .PO8 15. Pointertypen Die bisher besprochenen Variablen sind statische Variablen, d.h. ihre Form und Groesse sind im Deklarationsteil festgelegt und sie bleiben auch waehrend der gesamten Ausfuehrung des Blockes, indem sie erklaert sind, existent. Programme benoetigen jedoch häufig eine Datenstruktur, die sich in Form und Groesse waehrend der Ausfuehrung des Programmes aendern kann. Aus diesem Grunde wurden dynamische Variable eingefuehrt, die man erst dann generieren kann, wenn sie gebraucht werden, und die man nach ihrer Verwen- dung, wenn sie nicht mehr bebraucht werden, beseitigen kann. Solche dynamischen Variablen werden nicht explizit in der Variablendefinition wie die statischen Variablen erklaert und man kann sich auf sie auch nicht direkt durch einen Bezeichner beziehen. Man verwendet fuer sie spezielle Variable, die nur jeweils die Speicheradresse der entsprechenden Variablen enthalten, die also zu diesen Variablen zeigen (point). Diese speziellen Variablen werden als Pointervariablen bezeichnet. 15.1 Definition einer Pointervariablen Ein Pointertyp wird durch das Symbol ^ definiert. Diesem Symbol folgt der Typbezeichner der dynamischen Variablen, auf die sich die Pointervariablen dieses Typs beziehen. Das folgende Beispiel zeigt, wie ein Satz und die auf ihn sich beziehenden Pointer definiert werden: type PersonPointer = ^PersonRecord; PersonRecord = Record Name : String[50]; Job : String[50]; Next : PersonPointer; end; var FirstPerson,LastPerson,NewPerson : PersonPointer; Die Variablen FirstPerson, LastPerson und NewPerson sind Pointer- variablen, die den Zugriff zu Saetzen vom Typ PersonRecord ge- statten. Aus dem Beispiel ist auch ersichtlich, dass sich der Typbezeichner in einer Pointertypdefinition auf einen Bezeichner beziehen kann, der bis dahin noch nicht erklaert wurde. 15.2 Zuweisungsvariable (New) Bevor es ueberhaupt einen Sinn hat, eine von diesen Pointervari- ablen zu verwenden, benoetigen wir natuerlich einige neue Variab- le, auf die sie zeigen. Die Generierung und Zuweisung zu solchen neuen Variablen von irgendeinem Typ erfolgt mit der Standardpro- zedur New. Die Prozedur hat einen Parameter, der eine Pointerva- riable von dem Typ ist, den wir generieren wollen. Beispielsweise generiert New(FirstPerson) eine neue Variable vom Typ PersonRecord. Damit zeigt FirstPerson auf einen dynamisch erzeugten Satz von Typ PersonRecord. .pa Zuweisungen zwischen Pointervariablen kann man solange durchfueh- ren, solange sie vom gleichen Typ sind. Pointervariablen vom gleichen Typ koennen ebenso durch die Vergleichsoperatoren = und <> verglichen werden. Diese Operationen geben ein Boolesches Ergebnis (True oder False) zurueck. Der Pointerwert nil gehoert jedem Pointertyp an. Dieser Wert verweist auf keine dynamische Variable und kann Pointervariablen zugewiesen werden, um anzuzeigen, dass sie keinen verwertbaren Zeiger enthalten. Natuerlich kann nil auch im Vergleich verwendet werden. Variable, die man mit der Standardprozedur New erzeugt, werden in einer Stack-artigen Struktur aufgebaut. Man bezeichnet sie als Heap. Das TURBO-Pascal-System steuert den Heap durch Verwendung eines Heap-Pointers, der zu Programmbeginn auf die erste freie Adresse des Speichers weist. Bei jedem Aufruf von New wird der Heap-Pointer um soviele Bytes in Richtung Speicherende weiterge- stellt, als die dynamische Variable Bytes enthaelt. 15.3 Mark und Release Wenn eine dynamische Variable nicht mehr im Programm benoetigt wird, kann man durch Verwendung der Standardprozeduren Mark und Release den Speicherplatz zurueckerhalten, der dieser Variablen zugewiesen wurde. Die Prozedur Mark weist den Wert des Heap-Pointers einer Variab- len zu: Syntax: Mark(Var); wobei Var eine Pointervariable ist. Die Prozedur Release setzt den Heap-Pointer auf die Adresse, die in seinem Argument enthalten ist: Syntax: Release(Var); wobei Var eine Pointervariable ist, die vorher durch Mark gesetzt wurde. Release gibt damit den gesamten Speicherplatz zurueck, der ober- halb der im Argument angegebenen Variablen liegt. Es ist natuer- lich nicht moeglich, den Speicherplatz von Variablen zurueckzuer- halten, die in der Mitte des Heap liegen. Mit der Standardfunktion MemAvail ist es moeglich zu einer belie- bigen Zeit, die augenblickliche Groesse des vom Heap benutzten Speicherplatzes zu erhalten. Genaueres darueber im Anhang A. 15.4 Verwendung der Pointer Nehmen wir an, in einem Programm sei die Prozedure New verwendet worden, um eine Reihe von Saetzen des Typs PersonRecord (wie im obigen Beispiel) aufzubauen. Dabei sei das Feld Next so verwendet worden, dass es auf den naechsten PersonRecord weist. Dann wuerden die folgenden Anweisungen durch die Liste dieser Saetze gehen und den Inhalt jedes Satzes ausschreiben (FirstPerson weist auf den ersten Satz in der Liste): while FirstPerson <> nil do begin Writeln(FirstPerson^.Name,'is a ',FirstPerson^.Job); FirstPerson := FirstPerson^.Next; end; Hierbei kann man FirstPerson^.Name lesen als FirstPerson's Name, das ist das Feld Name in dem Satz, auf den FirstPerson zeigt. Das folgende Beispiel demonstriert die Verwendung von Pointern bei einer Liste, die Namen und entsprechende Berufswuensche ent- haelt. Die Namen und Berufswuensche werden nacheinander eingege- ben. Die Eingabe der Liste wird durch Druecken nur der Enter- Taste bei der Eingabe des Namens beendet. Danach wird die gesamte Liste gedruckt. Nach dem Druck wird der Speicherplatz freigege- ben, der von der Liste benutzt wurde. Die Speichervariable HeapTop wird nur fuer den Zweck benutzt, den ersten Wert des Heap-Pointers aufzuheben. Seine Definition als ^Integer (Pointer zu Integer) ist daher voellig willkuerlich. program Jobs; type PersonPointer = ^PersonRecord; PersonRecord = record Name : String[50]; Job : String[50]; Next : PersonPointer; end; var HeapTop : ^Integer; FirstPerson,LastPerson,NewPerson : PersonPointer; Name : String[50]; begin FirstPerson := nil; Mark(HeapTop); repeat Write('Enter Name: '); Readln(Name); if Name <> '' then begin New(NewPerson); NewPerson^.Name := Name; Write('Enter profession: '); Readln(NewPerson^.Job); Writeln; if FirstPerson = nil then FirstPerson := NewPerson else LastPerson^.Next := NewPerson; LastPerson := NewPerson; LastPerson^.Next := nil; end; until Name = ''; Writeln; while FirstPerson <> nil do begin Writeln(FirstPerson^.Name,' is a ',FirstPerson^.Job); FirstPerson := FirstPerson^.Next; end; Release(HeapTop); end. .pa 15.5 Speicherplatzzuordnung Mit der Standardprozedur GetMem ist es moeglich Speicherplatz beliebiger Groesse aus dem Heap zuzuweisen. Waehrend New nur soviel Speicherplatz zuweist, wie der Typ der im Argument verwen- deten Pointervariablen benoetigt, erlaubt GetMem dem Programmie- rer die Groesse des zugewiesenen Speicherplatzes selbst zu be- stimmen. GetMem wird mit zwei Parametern aufgerufen: Syntax: GetMem(Pvar,I); wobei Pvar irgendeine Pointervariable und I in Integerausdruck ist, der die Anzahl der zugewiesenen Bytes angibt. .pa 16. Prozeduren und Funktionen Ein Pascal-Programm besteht aus einem oder mehreren Bloecken. Jeder von ihnen kann wiederum aus Bloecken bestehen etc. Eine Prozedur, wie eine Funktion, ist einer von diesen Bloecken. Im allgemeinen kann man sie als Unterprogramme auffassen. Eine Pro- zedur ist also ein separater Teil eines Programmes, das von irgendwelchen anderen Stellen im Programm aus durch eine Proze- duranweisung aktiviert wird (s.7.1.2). Eine Funktion ist ganz aehnlich, nur berechnet sie etwas und gibt diesen berechneten Wert durch eine Variable zurueck, wenn ihr Bezeichner (der Funk- tionsaufruf) waehrend der Ausfuehrung des Programmes benutzt wird (s.6.2). 16.1 Parameter Werte koennen den Prozeduren oder Funktionen durch Parameter uebergeben werden. Durch diese Parameter wird ein Substitutions- mechanismus bereitgestellt, der erlaubt, die Logik des Unterpro- grammes mit verschiedenen Anfangswerten zuversehen, sodass es entsprechend verschiedene Ergebnisse produziert. Die Prozeduranweisung oder der Funktionsbezeichner, die das ent- sprechende Unterprogramm aufrufen, koennen eine Liste von Parame- tern enthalten, die man als die aktuellen Parameter bezeichnet. Diese werden den formalen Paramtern uebergeben, die im Kopf des Unterprogrammes spezifiziert sind. Die Zuordnung der Parameter bei der Uebergabe erfolgt in der Reihenfolge ihres Auftretens in der Parameterliste. Pascal unterstuetzt zwei unterschiedliche Methoden der Parameteruebergabe: Uebergabe der Parameter durch Uebergabe eines Wertes (Wertuebergabe) und Uebergabe der Parame- ter durch Austausch der formalen Parameter durch die aktuellen Parameter (Referenz). Werden Parameter durch Wertuebergabe uebertragen, stellt der formale Parameter eine selbstaendige logische Variable im Unter- programm dar und alle Wertveraenderungen an diesem formalen Para- meter im Unterprogramm haben keinen Einfluss auf den Wert des aktuellen Parameters nach Ausfuehrung des Unterprogrammes. Der aktuelle Parameter kann ein beliebiger Ausdruck, auch eine Variable, sein, muss aber den gleichen Typ wie der formale Para- meter haben. Solche Parameter werden als Wert-Parameter bezeich- net und im Kopf des Unterprogrammes, wie im folgenden Beispiel, definiert. (Die folgenden Beispiele zeigen, dass die Prozedurver- einbarungen gegenueber den Funktionsvereinbarungen, die in 16.3.1 erklaert sind, ganz aehnlich sind, der Funktionskopf definiert nur noch zusaetzlich den Typ des Ergebnisses). Beispiel Wertparameter: procedure Example(Num1,Num2:Number; Str1,Str2:Txt); Hierbei sind Number und Txt vordeklarierte Typen (z.B. Integer und String[255]) und Num1 , Num2, Str1 und Str2 sind formale Parameter, denen die Werte der aktuellen Parameter uebergeben werden. Die Typen der formalen und aktuellen Parameter muessen uebereinstimmen. .pa Bei der Definition des Kopfes ist zu beachten, dass der Typ der Parameter durch einen Bezeichner eines vordeklarierten Typs spe- zifiziert werden muss. Die folgende Konstruktion ist deshalb nicht erlaubt: procedure Select(Model: array[1..500] of Integer); Es ist notwendig den Typ von Model in der Typdefinition des Blockes zu erklaeren. Dann kann der Bezeichner des Typs in der Parametererklaerung des Kopfes verwendet werden: type Range = array[1..500] of Integer; procedure Select(Model: Range); Wird ein Parameter durch Referenz ubergeben, stellt in der Tat der aktuelle Parameter den formalen Parameter waehrend der Ausfuehrung des Unterprogrammes dar. Jede im Unterprogramm vorgesehene Veraenderung des Inhaltes des formalen Parameters aendert waehrend der Ausfuehrung des Unterprogrammes in Wirklichkeit den Inhalt des aktuellen Parameters. Aus diesem Grunde muss der aktuelle Parameter stets eine Variable sein. Solche durch Referenz uebergebenen Parameter werden als Variablen-Parameter bezeichnet. Die Definition erfolgt wie im folgenden Beispiel: Beispiel Variablenparameter: procedure Example(var Num1,Num2:Number; Str1,Str2:Txt); Hierbei sind Num1 und Num2 Variablenparameter und Str1 und Str2 Wertparameter. Alle Adressrechnungen werden erst zur Zeit des Prozeduraufrufes ausgefuehrt. Ist eine Variable beispielsweise die Komponente eines Array, wird ihr Index erst zum Zeitpunkt des Unterprogramm- aufrufes berechnet. Beachte, File-Parameter muessen immer als Variablenparameter erklaert werden. Wird eine grosse Datenstruktur, wie ein Array, einem Unterpro- gramm als Parameter uebergeben, dann spart die Verwendung eines Variablenparameters Abarbeitungszeit und Speicherplatz, da als einzige Information zwei Bytes an das Unterprogramm uebergeben werden, die die Adresse des aktuellen Parameters enthalten. Ein Wertparameter wuerde Speicherplatz fuer eine extra Kopie der gesamten Datenstruktur verlangen und ausserdem Zeit fuer das Kopieren benoetigen. 16.1.1 Reduzierung der Parametertyppruefung Normalerweise muessen bei Verwendung von Variablenparametern die formalen und aktuellen Parameter exakt vom gleichen Typ sein. Dies bedeutet, wenn ein Variablenparameter vom Typ String verwen- det wird, duerfen als aktuelle Parameter nur Strings mit exakt der im Unterprogramm definierten Laenge zugewiesen werden. Diese Einschraenkung kann man durch Verwendung der V-Compiler-Direktive beseitigen. Fuer diese Direktive erzeugt der Standard-aktiv- Status {$V+} eine genaue Typpruefung, waehrend der passive Status {$V-} die Typpruefung soweit vermindert, dass aktuelle Parameter mit beliebiger Stringlaenge, unabhaengig von der definierten Laenge des formalen Parameters, uebertragen werden duerfen. .pa Beispiel: program NSA; {This program must be compiled with the $V-Directive} {$V-} type WorkString = string[255]; var Line1 : string[80]; Line2 : string[100]; procedure Encode(var LineToEncode : WorkString); var I : Integer; begin for I := 1 to Length(LineToEncode) do LineToEncode[I] := Chr(Ord(LineToEncode[i]) - 30); end; begin Line1 := 'This is s secret message'; Encode(Line1); Line2 := 'Here is another (longer) secret message'; Encode(Line2); end. 16.1.2 Nichttypisierte Variablenparameter Ist der Typ eines formalen Parameters nicht definiert. d.h. enthaelt der Parameterteil im Kopf des Unterprogrammes keine Typdefinition, dann wird der Parameter als nichttypisiert be- zeichnet. Der aktuelle Parameter kann dann ein beliebiger Typ sein. Andererseits ist der nichttypisierte Parameter mit allen Typen inkompatibel. Aus diesem Grunde kann man nichttypisierte formale Parameter nur dort verwenden, wo der Datentyp ohne Bedeu- tung ist. Dies ist beispielsweise bei den Parametern von Addr, BlockRead, BlockWrite, FillChar oder Move und bei Adress- spezifikationen von absoluten Variablen der Fall. Im folgenden Beispiel wird bei der Prozedur SwitchVar die Verwen- dung nichttypisierter Parameter demonstriert. Sie uebertraegt den Inhalt der Variablen A1 nach A2 und von A2 nach A1. procedure SwitchVar(var A1p,A2p; Size : Integer); type A = array[1..MaxInt] of Byte; var A1 : A absolute A1p; A2 : A absolute A2p; Tmp : Byte; Count : Integer; begin for Count := 1 to Size do begin Tmp := A1[Count]; A1[Count] := A2[Count]; A2[Count] := Tmp; end; end; .pa Definiert man: type Matrix = array[1..50,1..25] of Real; var TestMatrix,BestMatrix : Matrix; dann kann man SwitchVar zum Austauschen des Inhaltes der beiden Matrizen verwenden. Der Prozeduraufruf lautet dann: SwitchVar(TestMatrix,BestMatrix,SizeOf(TestMatrix)); 16.2 Prozeduren Eine Prozedur kann entweder vordeklariert (Standard) oder nutzerdeklariert (durch den Programmierer vereinbart) sein. Vordeklarierte Proceduren sind Teile des TURBO-Pascal-Systems und koennen ohne weitere Vereinbarungen verwendet werden. Einer nutzerdeklarierten Prozedur kann der Name einer Standardprozedur gegeben werden, aber dann ist die Standardprozedur innerhalb des Gueltigkeitsbereiches der nutzerdeklarierten Prozedur nicht aufrufbar. 16.2.1 Prozedurvereinbarung Eine Prozedurvereinbarung besteht aus einem Prozedurkopf und einem ihm folgenden Block. Dieser Block besteht aus einem Deklarationsteil und einem Anweisungsteil. Der Prozedurkopf besteht aus dem reservierten Wort procedure, dem ein Bezeichner folgt, der als Name der Prozedur bezeichnet wird. Gewoehnlich folgt ihm eine formale Parameterliste, wie in 16.1. beschrieben. Beispiele: procedure LogOn; procedure Position(X,Y : Integer); procedure Compute(var Data : Matrix; Scale : Real); Der Deklarationsteil einer Prozedur hat die gleiche Form wie bei einem Programm. Alle in der formalen Parameterliste im Deklarationsteil erklaerten Bezeichner sind lokal zur Prozedur und zu jeder Prozedur in ihr. Dieser Bereich heisst Gueltigkeitsbereich der Bezeichner. Ausserhalb dieses Bereiches sind sie nicht bekannt. Eine Prozedur kann jede in einem zu ihr äusseren Block definierte Konstante, Type, Variable, Prozedur oder Funktion verwenden. Der Anweisungteil spezifiziert die Aktionen, die ausgefuehrt werden sollen, wenn die Prozedur aufgerufen wird. Er hat die Form einer Verbundanweisung (s.7.2.1). Wird der Prozedurbezeichner selbst innerhalb des Anweisungsteiles verwendet, wird die Proze- dur rekursiv ausgefuehrt (bei CP/M80 muss dann beachtet werden, dass zu diesem Zeitpunkt bei der Compilierung die A-Compiler- Direktive passiv {$A-} gesetzt ist s.Anhang E). .pa Das naechste Beispiel zeigt ein Programm, das eine Prozedur verwendet und dieser Prozedur Parameter uebergibt. Da die aktuellen Parameter, die an die Prozedur uebergeben werden, Konstante (oder einfache Ausdruecke) sind, muss der formale Parameter als Wertparameter definiert werden: program Box; var I :Integer; procedure DrawBox(X1,Y1,X2,Y2 : Integer); var I : Integer; begin GotoXY(X1,Y1); for I := X1 to X2 do Write('-'); for I := Y1+1 to Y2 do begin GotoXY(X1,I); Write('!'); GotoXY(X2,I); Write('!'); end; GotoXY(X1,Y2); for I := X1 to X2 do Write('-'); end; begin ClrScr; for I := 1 to 5 do DrawBox(I*4,I*2,10*I,4*I); DrawBox(1,1,80,23); end. Häufig sollen Veraenderungen des formalen Parameters in der Pro- zedur direkt auch den aktuellen Parameter betreffen. In diesen Faellen sind natuerlich Variablenparameter anzuwenden, wie im folgenden Beispiel: procedure Switch(var A,B : Integer); var Tmp : Integer; begin Tmp := A; A := B; B := Tmp; end; Wenn diese Prozedur durch die Anweisung Switch( I,J ); aufgerufen wird, werden die Werte von I und J ausgetauscht. Wuerde stattdessen faelschlicherweise der Prozedurkopf durch procedure Swap( A,B : Integer); d.h. mit einem Wertparameter erklaert, dann werden durch die Anweisung Swap( I,J ); die Werte von I und J nicht veraendert. .pa 16.2.2 Standardprozeduren TURBO-Pascal enthaelt eine Anzahl von Standardprozeduren: 1) Stringbehandlungsprozeduren (s. 9.5), 2) Filebehandlungsprozeduren (s. 14.2, 14.5.1, 14.7.1), 3) Zuordnungsprozeduren fuer dynamische Variable (s. 15.2, 15.5) 4) Input- und Output-Prozeduren (s. 14.6). Zusaetzlich werden die folgenden Standardprozeduren bereitge- stellt, vorausgesetzt, die entsprechenden Terminalkommandos sind installiert. 16.2.2.1 Delay Syntax: Delay(Time) Diese Prozedur erzeugt eine Warteschleife, die in ungefaehr so- viel Millisekunden durchlaufen wird, wie im Argument angegeben ist. Die exakte Zeit kann wegen der unteschiedlichen Hardware etwas davon abweichen. 16.2.2.2 ClrEol Syntax: ClrEol Diese Prozedur loescht alle Zeichen ab Cursorposition bis zum Ende der Zeile, ohne die Cursorposition zu veraendern. 16.2.2.3 ClrScr Syntax: ClrScr Diese Prozedur loescht den Bildschirm und setzt den Cursor in die linke obere Ecke. (Bei einigen Bildschirmtypen koennen dabei auch eventuell vorhandene Videoattribute bzw. vom Nutzer gesetzte Attribute veraendert werden). 16.2.2.4 DelLine Syntax: DelLine Diese Prozedur loescht die Zeile, in der der Cursor steht und schiebt alle darunter stehenden Zeilen um eine Zeile nach oben. 16.2.2.5 InsLine Syntax: InsLine Diese Prozedur fuegt an der Cursorposition eine leere Zeile ein und schiebt alle Zeilen unterhalb um eine Zeile nach unten, die letzte Zeile wird weggerollt. 16.2.2.6 Init Syntax: Init Diese Prozedur sendet die Terminal-Initialisierungszeichenkette, die bei der Installierung von TURBO-Pascal definiert wurde, an den Bildschirm. 16.2.2.7 Exit Syntax: Exit Diese Prozedur sendet die Terminal-Reset-Zeichenkette, die bei der Installierung definiert wurde, an den Bildschirm. .pa 16.2.2.8 GotoXY Syntax: GotoXY(Xpos,Ypos) Diese Prozedur setzt den Cursor an die Position auf dem Bild- schirm, die durch die Integerausdruecke Xpos (horizontaler Wert oder Zeile) und Ypos (vertikaler Wert oder Spalte) angegeben werden. Die linke obere Ecke (Home-Position) ist (1,1). 16.2.2.9 LowVideo Syntax: LowVideo Diese Prozedur setzt im Bildschirm das Attribut, das bei der Installation al "Ende der Hellsteuerung" festgelegt wurde. 16.2.2.10 HighVideo Syntax: HighVideo Diese Prozedur setzt im Bildschirm das Attribut, das bei der Installation als "Hellsteuerung" definiert wurde. 16.2.2.11 Randomize Syntax: Randomize Diese Prozedur erzeugt mittels Zufallszahlgenerator eine Zufalls- zahl. 16.2.2.12 Move Syntax: Move(var1,var2,num) Diese Prozedur kopiert im Speicher eine bestimmte Anzahl von Bytes. Hierbei sind var1 und var2 zwei Variable von beliebigem Typ und num ist ein Integerausdruck. Die Prozedur kopiert einen Block von num Bytes von der Stelle des ersten Bytes von var1 zur Stelle des ersten Bytes von var2. Move behandelt automatisch bei der Uebertragung auftretende Ueberlappungen, sodass "moveright" und "moveleft" Prozeduren nicht benoetigt werden. 16.2.2.13 FillChar Syntax: FillChar(var,num,value) Diese Prozedur fuellt einen Bereich im Speicher mit eine, gegebe- nen Wert. Hierbei ist var eine Variable eines beliebigen Typs, num ist ein Integerausdruck und Value ist ein Ausdruck vom Typ Byte oder Char. Es werden durch die Prozedur num Bytes beginnend ab dem ersten Byte von var mit dem Wert von value gefuellt. .pa 16.3 Funktionen Funktionen sind wie Prozeduren entweder (vordeklarierte) Standardfunktionen oder sie sind vom Programmierer definiert. 16.3.1 Funktionsvereinbarung Eine Funktionsvereinbarung besteht aus einem Funktionskopf und einem Block, der aus einem Deklarationsteil und einem Anweisungs- teil besteht. Der Funktionskopf ist mit dem Prozedurkopf equivalent, ausser dass der Funktionskopf mit dem reservierten Wort function eroeff- net wird und auch den Typ des Ergebnisses mit definieren muss. Dies wird durch Anfuegung eines Doppelpunktes und eines Types an den Funktionskopf erreicht. Beispiele: funktion KeyHit : Boolean; function Comput(var Value : Sample) : Real; function Power(X,Y : Real) : Real; Der Ergebnistyp einer Funktion muss ein Skalartyp (d.h. Integer, Real, Boolean, Char deklariert als Skalar- oder Teilbereich), ein Stringtyp oder ein Pointertyp sein. Der Deklarationsteil einer Funktion ist der gleiche wie bei einer Prozedur. Der Anweisungsteil einer Funktion ist eine Verbundanweisung, so wie in 7.2.1 beschrieben. Innerhalb des Anweisungsteiles muss wenigstens eine Ergibtanweisung auftreten, die dem Funktionsbe- zeichner einen Wert zuweist. Die allerletzte dieser Ergibtanwei- sungen zum Funktionbezeichner ergibt den Wert der Funktion. Wenn der Funktionsbezeichner selbst als Funktionsaufruf im Anweisungs- teil der Funktion auftritt, dann wird die Funktion rekursiv aufgerufen. In diesem Falle muss zu diesem Zeitpunkt die A- Compiler-Direktive {$A-} passiv sein (s. Anhang E). Das folgende Beispiel zeigt die Verwendung einer Funktion zur Berechnung der Summe eine Reihe ganzer Zahlen von I bis J: function RowSum(I,J : Integer) : Integer; function SimpleRowSum(S : Integer) : Integer; begin SimpleRowSum := S * (S+1) div 2; end; begin RowSum := SimpleRowSum(J) - SimpleRowSum(I-1); end; Die Funktion SimpleRowSum ist in die Funktion RowSum eingebettet. SimpleRowSum ist deshalb nur im Gueltigkeitsbereich von RowSum zulaessig. .pa Das folgende Beispiel zeigt die Verwendung rekursiver Funktionen: program Fact; var number : Integer; function factorial(value : Integer) : Real; begin if value = 0 then factorial := 1 else factorial := value * factorial(value-1); end; begin Write('input number: '); Readln(number); if number < 0 then Writeln('non valid input!') else Writeln(number,'! = ',factorial(number):8:0); end. Bei der Definition eines Funktiontyps ist zu beachten, dass der in der Definition verwendete Typ vorher als Typbezeichner erklaert sein muss. Aus diesem Grunde ist die folgende Konstruktion nicht erlaubt: function LowCase(Line : UserLine) : string[80]; Man muss stattdessen vorher den Typ string[80] durch einen Bezeichner erklaeren und mit diesem dann den Typ des Funktionsergebnisses definieren: type str80 = string[80]; function LowCase(Line : UserLine) : str80; Wegen der Art der Implementation der Prozeduren Write und Writeln darf eine Funktion, die irgendeine der Standardprozeduren Read, Readln, Write oder Writeln verwendet, niemals durch einen Ausdruck in einer Write oder Writeln Anweisung aufgerufen werden. 16.3.2 Standardfunktionen Die folgenden (vordeklarierten) Standardfunktionen sind in TURBO- Pascal implementiert: 1) Stringbehandlungsfunktionen (s. 9.5), 2) Filebehandlungsfunktionen (s. 14.2, 14.5.1), 3) Pointerbezogene Funktionen )s. 15.2, 15.3, 15.5) und darueber hinaus folgende Funktionen: 16.3.2.1 Arithmetische Funktionen 16.3.2.1.1 Abs Syntax: Abs(num) Gibt den absoluten Wert von num zurueck. Das Argument num muss entweder Real oder Integer sein und das Ergebnis ist vom gleichen Typ, wie das Argument. 16.3.2.1.2 Arctan Syntax: Arctan(num) Gibt den Winkel im Bogenmass zurueck, dessen Tangens gleich num ist. Das Argument num muss entweder Real oder Integer sein, das Ergebnis ist Real. 16.3.2.1.3 Cos Syntax: Cos(num) Gibt den Cosinus von num zurueck. Das Argument num wird im Bogen- mass ausgedrueckt und muss entweder Real oder Integer sein. Das Ergebnis ist Real. 16.3.2.1.4 Exp Syntax: Exp(num) Gibt die Exponentialfunktion von num zurueck, d.h. e^x. Das Argument num muss entweder Real oder Integer sein. Das Ergebnis ist Real. 16.3.2.1.5 Frac Syntax: Frac(num) Gibt den gebrochenen Teil von num zurueck, d.h. Frac(num) = num - Int(num). Das Argument num muss entweder Real oder Integer sein. Das Ergebnis ist Real. 16.3.2.1.6 Int Syntax: Int(num) Gibt den ganzen Teil von num zurueck, d.h. die groesste ganze Zahl, die kleiner oder gleich num ist, fall num >= 0 ist, oder die kleinste ganze Zahl, die groesser oder gleich num ist, falls num < 0 ist . Das Argument num muss entweder Real oder Integer sein. Das Ergebnis ist Real. 16.3.2.1.7 Ln Syntax: Ln(num) Gibt den natuerlichen Logarithmus von num zurueck. Das Argument num muss entweder Real oder Integer sein. Das Ergebnis ist Real. 16.3.2.1.8 Sin Syntax: Sin(num) Gibt den Sinus von num zurueck. Das Argument num muss im Bogen- mass ausgedrueckt sein und sein Typ ist entweder Real oder Inte- ger. Das Ergebnis ist Real. 16.3.2.1.9 Sqr Syntax: Sqr(num) Gibt das Quadrat von num zurueck, d.h. num*num. Das Argument num muss entweder Real oder Integer sein. Das Ergebnis ist vom glei- chen Typ wie das Argument. 16.3.2.1.10 Sqrt Syntax: Sqrt(num) Gibt die Quadratwurzel von num zurueck. Das Argument num muss entweder Real oder Integer sein. Das Ergebnis ist Real. .pa 16.3.2.2 Skalarfunktionen 16.3.2.2.1 Pred Syntax: Pred(num) Gibt den Vorgaenger von num zurueck (falls er existiert). num ist ein beliebiger Skalartyp. 16.3.2.2.2 Suc Syntax: Suc(num) Gibt den Nacgfolger von num zurueck (falls er existiert). num ist ein beliebiger Skalartyp. 16.3.2.2.3 Odd Syntax: Odd(num) Gibt den Booleschen Wert True zurueck, wenn num eine ungerade Zahl ist und False, wenn num eine gerade Zahl ist. num muss vom Typ Integer sein. 16.3.2.3 Konvertierungsfunktionen Die Konvertierungsfunktionen werden verwendet, um den Wert eines Skalartyps in den eines anderen Skalartyps umzuwandeln. Zusaetz- lich zu den folgenden Funktionen kann man "Retyping" (s.8.3) fuer diese Zwecke verwenden. 16.3.2.3.1 Ord Syntax: Ord(num) Gibt die Ordnungszahl des Wertes von num in der durch den Typ von num definierten Menge zurueck. Ord(num) ist mit Integer(num) equivalent. num kann ein beliebiger Skalartyp sein. Das Ergebnis ist vom Typ Integer., 16.3.2.3.2 Round Syntax: Round(num) Gibt den Wert von num gerundet zur naechsten ganzen Zahl wie folgt zurueck: wenn num >= 0, dann ist Round(num) = Trunc(num+0.5), wenn num < 0 , dann ist Round(num) = Trunc(num-0.5). num muss vom Typ Real sein. Das Ergebnis ist vom Typ Integer. 16.3.2.3.3 Trunc Syntax: Trunc(num) Gibt fuer num >= 0 die groesste ganze Zahl zurueck, die kleiner oder gleich num ist. Wenn num < 0 ist, dann gibt diese Funktion die kleinste ganze Zahl zurueck, die groesser oder gleich num ist. num muss vom Typ Real sein und das Ergebnis ist vom Typ Integer. .pa 16.3.2.4 Verschiedene Standarfunktionen 16.3.2.4.1 Hi Syntax: Hi(I) Das niederwertige Byte des Ergebnisses enthaelt das hoeherwertige Byte des Wertes vom Integerausdruckes I. Das hoeherwertige Byte des Ergebnisses ist Null. Das Ergebnis ist vom Typ Integer. 16.3.2.4.2 KeyPressed Syntax: Keypressed Gibt den Wert True zurueck, wenn eine Taste auf der Console gedrueckt wurde. Das Ergebnis wird durch Aufruf der Consol- Status-Routine des BIOS realisiert. 16.3.2.4.3 Lo Syntax: Lo(I) Gibt das niederwertige Byte des Wertes vom Integerausdruck I zurueck, wobei das hoeherwertige Byte auf Null gesetzt wird. Der Typ des Ergebnisses ist Integer. 16.3.2.4.4 Random Syntax: Random Gibt eine Zufallszahl zurueck, die groesser oder gleich Null und kleiner als Eins ist. Der Typ ist Real. 16.3.2.4.5 Random(I) Syntax: Random(num) Gibt eine Zufallszahl zurueck, die groesser oder gleich Null und kleiner als num ist. num und die Zufallszahl sind beide vom Typ Integer. 16.3.2.4.6 SizeOf Syntax: SizeOf(name) Gibt die Anzahl von Bytes zurueck, die von der Variablen oder dem Typ name belegt werden. Das Ergebnis ist vom Typ Integer. 16.3.2.4.7 Swap Syntax: Swap(I) Die Swapfunktion vertauscht vom Wert des Integerausdruckes I das hoeher- und niederwertige Byte und gibt das Ergebnis als Integer- zahl aus. Beispiel Swap($1234) gibt $3412 zurueck (Werte zur Verdeutlichung in hexadezimaler Schreibweise ). .pa 16.4 Vorwaerts Referenz Ein Unterprogramm wird vorwaerts deklariert, indem man seinen Kopf separat von seinem Block spezifiziert. Dieser separate Unterprogrammkopf ist exakt der gleiche, wie der eines normalen Unterprogrammes, ausser dass er mit dem reservierten Wort forward endet. Der Block selbst folgt spaeter innerhalb des gleichen Deklarationsteiles. Der Block beginnt mit einer Kopie des vorher definierten Kopfes ohne Parameter, Typen etc, d.h. nur mit dem Namen. Beispiel: program Catch22; var X : Integer; function Up(var I : Integer) : Integer; forward; function Down(var I : Integer) : Integer; begin I := I div 2; Writeln(I); if I <> 1 then I := Up(I); end; function Up; begin while I mod 2 <> 0 do begin i := I*3+1; Writeln(I); end; I := Down(I); end; begin Write('Enter any integer: '); Readln(X); X := Up(X); Write('Ok Program stopped again.'); end. Wird das Programm gestartet und eine 6 eingegeben, dann ergibt sich folgendes Bild: Enter any integer : 6 3 10 5 16 8 4 2 1 Ok Program stopped again. .pa Das obige Programm ist eine kompliziertere Version des folgenden Programmes: program Catch222; var X : Integer; begin Write('Enter any integer: '); Readln(X); while X <> 1 do begin if X mod 2 = 0 then X := X div 2 else X := X*3+1; Writeln(X); end; Write('Ok Program stopped again'); end. Sie sind sicher ueberrascht, dass man bei diesem kleinen und sehr einfachen Progamm im Voraus nicht einschaetzen kann, wie lange es bei Eingabe einer beliebigen ganzen Zahl läuft. .pa 17. Einfuegen von Programmteilen Die Tatsache, dass der TURBO-Editor den gesamten Quelltext im Speicher bearbeitet, schraenkt die Groesse des Quelltextes ein. Diese Beschraenkung kann man durch Verwendung der I-Compiler- Direktive umgehen. Dazu teilt man den gesamten Quelltext in kleinere Einheiten. Eine Einheit, das Rahmenprogramm, bildet den Kern des Programmes und in diese werden die anderen Teile zur Zeit der Uebersetzung mittels I-Compiler-Direktive eingefuegt. Diese Include-Option gestattet ein uebersichtliches Programmie- ren. Es eroeffnet auch die Moeglichkeit der Verwendung einzelner Programmteile in anderen Programmen und damit kann man Bibilio- theken von Dateien schaffen, die fuer den flexiblen Aufbau ver- schiedener Programme zur Verfuegung stehen. Die Syntax fuer die I-Compiler-Direktive ist {$I filename} wobei filename ein beliebiger erlaubter kompletter Dateiname ist. Blanks werden ignoriert und kleine Buchstaben in grosse umgewan- delt. Wurde kein Typ spezifiziert, wird .PAS angehaengt. Diese Direktive muss allein in einer Zeile des Rahmenprogrammes defi- niert werden. Beispiele: {$I first.pas} {$I STDPROC} {$I COMPUTE.MOD} Zur Demonstration der Include-Option nehmen wir an, in unserer Bibliothek exisitiere die Datei STUPCASE.FUN. Sie enthalte die Funktion StUpCase, die mit einem Zeichen oder String als Parameter aufgerufen wird und die den Wert des Parameters unter Umwandlung aller Kleinbuchstaben in Grossbuchstaben zurueckgibt. Datei STUPCASE.FUN: function StUpCase(St : ALlStrings) : AllStrings; var I : Integer; begin for I := 1 to Length(St) do St[I] := UpCase(St[I]); StUpCase := St; end; In einem anderen Programm kann man dann diese Funktion zur Umwandlung von Klein- in Grossbuchstaben verwenden, indem man diese Datei mittels der Include-Option einfuegt, anstatt sie in das Programm einzukopieren: program STUPCASE; type InData = string[80]; AllStrings = string[255]; var Answer : InData; I : Integer; {$I F:STUPCASE.FUN} begin Writeln('Enter Name: '); Readln(Answer); Writeln(StUpCase(Answer)); end. .pa Diese Methode ist nicht nur einfacher und Speicherplatz sparen- der, sie ermoeglicht auch einen sicheren und einfachen Aende- rungsdienst. Eine Verbesserung in einer solchen Routine wirkt dann sofort automatisch auf alle Programme, die diese Routine mittels Include einfuegen. Man sollte auch beachten, dass TURBO-Pascal fuer die einzelnen Bestandteile des Deklarationsteiles keine feste Ordnung fordert und diese auch mehrfach auftreten koennen. Damit besteht die Moeglichkeit, bestimmte häufig verwendete Typdefinitionen in einer Bibliothek aufzunehmen und sie von dort aus in die einzel- nen Programme einzufuegen. Alle Compiler-Direktiven, ausser B und C, sind lokal zu dem File, in dem sie auftreten. Das heisst, wenn eine Compiler-Direktive in einem Include-File veraendert wird, wird sie nach Verlassen dieses Include-Files auf den urspruenglichen Wert zurueckgesetzt. Die Compiler-Direktiven B und C sind immer global. Eine Beschrei- bung aller Compiler-Direktiven steht im Anhang E. Include-Files koennen nicht geschachtelt werden, d.h. ein Include-File kann immer nur von einem "Rahmenprogramm" aus aufgerufen und eingefuegt werden, niemals von einem eingefuegten Programm aus.