Programmierkurs: Delphi: Pascal: Prozeduren und Funktionen
Aus Wikibooks
[Bearbeiten] Prozeduren und Funktionen
[Bearbeiten] Prozeduren ohne Parameter
Oft benötigt man ein und den selben Code mehrere Male in einem Programm, - was liegt also näher als ihn nur ein mal zu schreiben, unter einem Namen zu speichern und diesen dann im Programm aufzurufen? Wohl nichts, und deshalb leisten Prozeduren genau dies. Will man etwa die Anweisungen aus dem Eingangsbeispiel
var Vorname: string; begin Writeln('Wie heisst du?'); Readln(Vorname); Writeln('Hallo '+Vorname+'! Schoen, dass du mal vorbeischaust.'); Readln; end.
gleich 2 mal aufrufen und dafür eine Prozedur verwenden, so sähe das Ergebnis so aus:
var Vorname: string; procedure Beispielprozedur; begin Writeln('Wie heisst du?'); Readln(Vorname); Writeln('Hallo '+Vorname+'! Schoen, dass du mal vorbeischaust.'); Readln; end; // Ende procedure Beispielprozedur begin // Beginn des Hauptprogramms Beispielprozedur; Beispielprozedur; end. // Ende Hauptprogramm
Das Schlüsselwort procedure leitet dabei den Prozedurkopf ein, der im Beispiel nur aus dem Prozedurnamen besteht. Der Anfang des Codes der Prozedur wird durch begin gekennzeichnet, und im Anschluss daran folgt end; um das Prozedurende zu kennzeichnen. Der String Vorname ist hierbei global, also am Programmanfang außerhalb der Prozedur deklariert, wodurch er im gesamten Programm samt aller Prozeduren Gültigkeit besitzt, also an jeder Stelle gelesen und beschrieben werden kann. Dies hat jedoch Nachteile und birgt Gefahren, da verschiedene Prozeduren eventuell ungewollt gegenseitig den Inhalt solcher globaler Variablen verändern. Die Folge wären Fehler im Programm, die schwer zu erkennen, aber glücklicherweise leicht zu vermeiden sind. Man kann nämlich auch schreiben:
procedure Beispielprozedur; var Vorname: string; begin Writeln('Wie heisst du?'); Readln(Vorname); Writeln('Hallo '+Vorname+'! Schoen, dass du mal vorbeischaust.'); Readln; end; // Ende procedure Beispielprozedur begin // Beginn des Hauptprogramms Beispielprozedur; Beispielprozedur; end. // Ende Hauptprogramm
Jetzt ist die Variable innerhalb der Prozedur deklariert, und sie besitzt auch nur dort Gültigkeit, kann also außerhalb weder gelesen noch beschrieben werden. Man nennt sie daher eine lokale Variable. Mit Erreichen der Stelle end; // Ende procedure Beispielprozedur verliert sie ihren Wert, sie ist also auch temporär.
[Bearbeiten] Prozeduren mit Wertübergabe (call by value)
Oft ist es jedoch nötig, der Prozedur beim Aufruf Werte zu übergeben, um Berechnungen auf unterschiedliche Daten anzuwenden oder individuelle Nachrichten auszugeben. Ein Beispiel:
var MusterName: string; GeschoentesAlter: Integer; procedure NameUndAlterAusgeben(Vorname: string; AlterMonate: Integer); var AlterJahre: Integer; begin AlterJahre := AlterMonate div 12; AlterMonate := AlterMonate mod 12; Writeln(Vorname+ ' ist '+ IntToStr(AlterJahre)+' Jahre und '+ IntToStr(AlterMonate)+ ' Monate alt'); Readln; end; begin NameUndAlterAusgeben('Konstantin', 1197); MusterName := 'Max Mustermann'; GeschoentesAlter := 386; NameUndAlterAusgeben(MusterName, GeschoentesAlter) end.
Der Prozedur NameUndAlterAusgeben werden in runden Klammern zwei durch Semikolon getrennte typisierte Variablen (Vorname: string; AlterMonate: Integer) bereitgestellt, die beim Prozeduraufruf mit Werten belegt werden müssen. Dies kann wie im Beispiel demonstriert mittels Konstanten oder auch Variablen des geforderten Typs geschehen. Diese Variablen werden genau wie die unter var deklarierten lokalen Variablen behandelt, nur dass sie eben mit Werten vorbelegt sind. Sie verlieren also am Ende der Prozedur ihre Gültigkeit und sind außerhalb der Prozedur nicht lesbar. Veränderungen an ihnen haben auch keinen Einfluss auf die Variablen mit deren Werten sie belegt wurden, im Beispiel verändert also AlterMonate := AlterMonate mod 12; den Wert von GeschoentesAlter nicht.
[Bearbeiten] Prozeduren mit Variablenübergabe (call by reference)
Was aber, wenn man der Prozedur nicht nur Werte mitteilen will, sondern sie dem Hauptprogramm auch ihre Ergebnisse übermitteln soll? In diesem Fall benötigt man im Hauptprogramm eine Variable, deren Werte beim Prozeduraufruf übergeben werden und in die am Ende das Ergebnis geschrieben wird. Außerdem muss man im Prozedurkopf einen Platzhalter definieren, der die Variable aufnehmen kann (eine Referenz darauf bildet). Dies geschieht indem man genau wie bei der Wertübergabe hinter dem Prozedurnamen in runden Klammern eine typisierte Variable angibt, ihr aber das Schlüsselwort var voranstellt. Ein Beispiel:
var eingabe: Double; procedure kubik(var zahl: Double); begin zahl := zahl * zahl * zahl; end; begin eingabe := 2.5; kubik(eingabe); ... end.
Als Platzhalter dient hier die Variable zahl, ihr wird beim Aufruf der Wert von eingabe übergeben, am Ende wird der berechnete Wert zurückgeliefert, im Beispiel erhält eingabe also den Wert 15.625. Bei der Variablenübergabe darf keine Konstante angegeben werden, da diese ja das Ergebnis nicht aufnehmen könnte, es muss immer eine Variable übergeben werden.
Objekte werden in Delphi immer als Zeiger übergeben. Man kann also auf Eigenschaften und Methoden eines Objekts direkt zugreifen:
procedure ClearLabel(label: TLabel); begin label.Caption := '' end;
[Bearbeiten] Prozeduren/Funktionen mit Const Parameter
Parameter können mit Const gekennzeichnet sein, um zu verhindern, dass ein Parameter innerhalb einer Prozedur oder Funktion geändert werden kann.
function StrSame(const S1, S2: AnsiString): Boolean; begin Result := StrCompare(S1, S2) = 0; end;
Bei String-Parametern ergibt sich ein Geschwindigkeitsvorteil, wenn diese mit Const gekennzeichnet werden.
[Bearbeiten] Erste Zusammenfassung
Prozeduren können ohne Ein- und Ausgabe, mit einer oder mehreren Eingaben und ohne Ausgabe, oder mit Ein- und Ausgabe beliebig vieler Variablen deklariert werden. Wertübergaben und Variablenübergaben können selbstverständlich auch beliebig kombiniert werden. Bei Variablenübergaben ist zu beachten, dass die Prozedur immer den Wert der Variable einliest. Will man sie allein zur Ausgabe von Daten verwenden, darf deren Wert nicht verwendet werden, bevor er nicht innerhalb der Prozedur überschrieben wurde, beispielsweise mit einer Konstanten oder dem Ergebnis einer Rechnung.
Prozeduren können natürlich auch aus anderen Prozeduren oder Funktionen heraus aufgerufen werden.
Werden Variablen im Hauptprogramm deklariert, so nennt man sie global, und ihre Gültigkeit erstreckt sich demnach über das gesamte Programm (genauer: die gesamte Unit); somit besitzen auch Prozeduren Zugriff darauf.
Innerhalb von Prozeduren deklarierte Variablen besitzten nur dort Gültigkeit, man nennt sie daher lokale Variablen.
Aber Vorsicht: Konnte man sich bei Zahlen (vom Typ Integer, Int64, Single, Double, ...) in globalen Variablen noch darauf verlassen, dass sie zu Anfang den Wert null haben, so ist dies in lokalen Variablen nicht mehr der Fall, sie tragen Zufallswerte. Außerdem kann man keine typisierten Konstanten innerhalb von Prozeduren deklarieren.
[Bearbeiten] Funktionen
Funktionen liefern gegenüber Prozeduren immer genau ein Ergebnis. Dieses wird sozusagen an Ort und Stelle geliefert, genau dort wo der Funktionsaufruf im Programm war, steht nach dem Ende ihr Ergebnis. Das hat den Vorteil, dass man mit den Funktionsausdrücken rechnen kann als ob man es bereits mit ihrem Ergebnis zu tun hätte, welches erst zur Laufzeit des Programms berechnet wird.
Sie werden durch das Schlüsselwort function eingeleitet, darauf folgt der Funktionsname, in runden Klammern dahinter ggf. typisierte Variablen zur Wertübergabe, gefolgt von einem Doppelpunkt und dem Ergebnis-Typ. Innerhalb der Funktion dient ihr Name als Ergebnisvariable. Ein Beispiel:
var ergebnis: Double; function Kehrwert(zahl: Double): Double; begin Kehrwert := 1/zahl; // oder: Result := 1/zahl; end; begin ergebnis := Kehrwert(100)*10; // wird zu ergebnis := 0.01*10; ... Kehrwert(100)*10; end.
Der Funktion wird also der (Konstanten-) Wert 100 übergeben, Sie liefert ihr Ergebnis, es wird mit 10 multipliziert, und in ergebnis gespeichert. Der nächste Aufruf bleibt ohne Wirkung, und verdeutlicht, dass man mit Funktionsausdrücken zwar beliebig weiterrechnen kann, aber am Ende das Ergebnis immer speichern oder ausgeben muss, da es sonst verloren geht.
Funktionen erfordern nicht notwendigerweise Wertübergaben. Z.B. wäre es möglich, dass eine Funktion mit Zufallszahlen arbeitet oder ihre Werte aus globalen Variablen oder anderen Funktionen bezieht. Eine weitere Anwendung wäre eine Funktion ähnlich einer normalen Prozedur, die jedoch einen Wert als Fehlermeldung zurückgibt. Tatsächlich gibt es z.B. in der Windows-Programmierung die Funktion GetForegroundWindow, die einen Zeiger auf das aktive Fenster zurückgibt und dafür keine Werte vom Benutzer benötigt.
[Bearbeiten] Unterprozeduren / Unterfunktionen
Prozeduren und ebenso Funktionen können bei ihrer Deklaration auch in einander verschachtelt werden (im Folgenden wird nur noch von Prozeduren gesprochen, alle Aussagen treffen aber auch auf Funktionen zu). Aus dem Hauptprogramm oder aus anderen Prozeduren kann dabei nur die Elternprozedur aufgerufen werden, die Unterprozeduren sind nicht zu sehen. Eltern- und Unterprozeduren können sich jedoch gegenseitig aufrufen.
Die Deklaration dazu an einem Beispiel veranschaulicht sieht folgendermaßen aus:
procedure Elternelement; var Test: string; procedure Subprozedur; begin Writeln(Test); Readln(Test); end; ... Beliebig viel weitere Prozeduren ... begin // Beginn von Elternelement Test := 'Ein langweiliger Standard'; Subprozedur; Writeln(Test); Readln; end;
Nach dem Prozedurkopf folgen optional die Variablen der Elternprozedur, dann der Prozedurkopf der Unterprozedur, ihr Rumpf, wenn noch nicht geschehen die Variablen der Elternprozedur und schließlich der Rumpf der Elternprozedur. Sofern die Variablen der Elternprozedur vor den Unterprozeduren deklariert werden, können diese darauf ähnlich einer globalen Variable zugreifen, wodurch deren Einsatz sich dadurch noch weiter reduzieren lässt. Im Zusammenhang mit Rekursionen wird ein solcher Einsatz manchmal angebracht sein.
Unterprozeduren können auch selbst wieder Unterprozeduren haben, die dann nur von ihrem Elternelement aufgerufen werden können, es sind beliebige Verschachtelungen möglich.
[Bearbeiten] forward Deklaration
Alle folgenden Aussagen treffen auch auf Funktionen zu: Eigentlich muss der Code einer Prozedur vor ihrem ersten Aufruf im Programm stehen, manchmal ist dies jedoch eher unpraktisch, etwa wenn man viele Prozeduren alphabetisch anordnen will, oder es ist gar unmöglich, nämlich wenn Prozeduren sich gegenseitig aufrufen. In diesen Fällen hilft die forward Deklaration, die dem Compiler am Programmanfang mitteilt, dass später eine Prozedur unter angegebenen Namen definiert wird. Man schreibt dazu den gesamten Prozedurkopf ganz an den Anfang des Programms, gefolgt von der Anweisung forward; . Der Prozedur-Rumpf folgt dann im Implementation-Teil der Unit. Hierbei kann der vollständige Prozedurkopf angegeben werden oder man lässt die Parameter weg. Am Beispiel der Kehrwertfunktion sähe dies so aus:
function Kehrwert(zahl: Double): Double; forward; var ergebnis: Double; function Kehrwert; begin Kehrwert := 1/zahl; end; ...
[Bearbeiten] Überladene Prozeduren / Funktionen
Alle Bisher betrachteten Prozeduren / Funktionen verweigern ihren Aufruf wenn man ihnen nicht genau die Zahl an Werten und Variablen wie in ihrer Deklaration gefordert übergibt. Auch deren Reihenfolge muss beachtet werden.
Will man beispielsweise eine Prozedur mit ein und demselben Verhalten auf unterschiedliche Datentypen anwenden, so wird dies durch die relativ strikte Typisierung von Pascal verhindert. Versucht man also, einer Prozedur, die Integer-Zahlen in Strings wandelt, eine Gleitkommazahl als Eingabe zu übergeben, so erhält man eine Fehlermeldung. Da dies aber eigentlich ganz praktisch wäre, gibt es eine Möglichkeit, dies doch zu tun. Man muss jedoch die Prozedur in allen benötigten Varianten verfassen und diesen den gleichen Namen geben. Damit der Compiler von dieser Mehrfachbelegung des Namens weiß, wird jedem der Prozedurköpfe das Schlüsselwort overload; angefügt. Wichtig ist es zu beachten, dass die Parameter tatsächlich in der Reihenfolge ihrer Datentypen unterschiedlich sind. Es reicht nicht, unterschiedliche Parameternamen zu verwenden.
Ein Beispiel:
procedure NumberToString(zahl: Int64; var Ausgabe: string); overload; begin Ausgabe := IntToStr(zahl); end; procedure NumberToString(zahl: Double; var Ausgabe: string); overload; begin Ausgabe := FloatToStr(zahl); end; procedure NumberToString(zahl: Double; var Ausgabe: TEdit); overload; begin Ausgabe.Text := FloatToStr(zahl); end;
Die erste Version kann Integer-Werte in Strings wandeln, die zweite wandelt Fließkommawerte, die dritte ebenso, speichert sie dann jedoch in einer Delphi-Edit-Komponenten.
Wenn Typen zuweisungskompatibel sind, braucht man keine separate Version zu schreiben, so nimmt z.B. Int64 auch Integer (Longint)-Werte auf, genau wie Double auch Single-Werte aufnehmen kann.
Der Begriff Überladen (overload) beschreibt das mehrmalige Einführen einer Funktion oder Prozedur mit gleichem Namen, aber unterschiedlichen Parametern. Der Compiler erkennt hierbei an den Datentypen der Parameter, welche Version er nutzen soll. Das Überladen ist sowohl in Klassen, für die dort definierten Methoden, als auch in globalen Prozeduren und Funktionen möglich.
Noch weitergehende Fähigkeiten kann man mit dem Typ Variant realisieren.
[Bearbeiten] Vorzeitiges Beenden
In einigen Situationen kann es sinnvoll sein, den Programmcode einer Prozedur oder Funktion nicht vollständig bis zum Ende durchlaufen zu lassen. Hierfür gibt es die Möglichkeit, mittels der Anweisung Exit vorzeitig hieraus auszusteigen. Exit kann an jeder Stelle im Programmablauf vorkommen, bei Funktionen ist jedoch zusätzlich zu beachten, dass vor dem Verlassen ein gültiges Funktionsergebnis zugewiesen wird.
Stellen Sie sich vor, Sie haben eine Liste von Namen und möchten wissen, an welcher Stelle dieser Liste sich ein Name befindet. Die entsprechende Funktion könnte wie folgt aussehen:
type TNamensListe = array[0..99] of string; function HolePosition(Name: string; Liste: TNamensListe): Integer; var i: Integer; begin Result := -1; // Funktionsergebnis bei Fehler oder "nicht gefunden" if Name = '' then Exit; // Funktion verlassen, wenn kein Name angegeben wurde for i := 0 to 99 do // komplette Liste durchsuchen if Liste[i] = Name then begin Result := i; // gefundene Position als Funktionsergebnis zurückgeben Break; // verlässt die Zählschleife (oder Exit, verlässt die Funktion) end; end;
In diesem Beispiel wird als erstes das Funktionsergebnis -1 festgelegt. In diesem Falle soll es die Bedeutung "Fehler" oder "Name nicht gefunden" besitzen. Da der Index der Liste erst bei 0 beginnt, ist eine Stelle unter 0 als gültiges Ergebnis nicht möglich. Dadurch können wir diesen Wert als so genannte "Magic number" gebrauchen, sprich, als Ergebnis mit besonderer Bedeutung.
Als nächstes wird getestet, ob überhaupt ein Name angegeben wurde. Wenn die Zeichenkette leer ist, erfolgt der sofortige Ausstieg aus der Funktion. Die anschließende Prüfschleife wird nicht durchlaufen. Als Ergebnis wird der zuvor zugewiesene Wert -1 zurückgegeben. Nur wenn Name nicht leer ist, beginnt die eigentliche Suche. Hierbei werden alle Elemente der Liste geprüft, ob sie dem angegebenen Namen entsprechen. Wenn dies der Fall ist, wird die Position dem Funktionsergebnis zugewiesen und die Suche beendet.
Für den besonderen Fall, dass zwar ein Name angegeben wurde, dieser aber in der Liste nicht enthalten ist, wird ebenfalls -1 zurückgegeben. Da die Bedingung Liste[i] = Name niemals zutrifft, bekommt das Funktionsergebnis in der Schleife keinen neuen Wert zugewiesen. Nach 99 wird die Schleife verlassen und Result ist immer noch -1.
Das folgende Programm verdeutlicht den Ablauf.
var Liste: TNamensListe; begin Liste[0] := 'Alfons'; Liste[1] := 'Dieter'; Liste[2] := 'Gustav'; Liste[3] := 'Detlef'; WriteLn(HolePosition('Gustav', Liste)); // Ergibt: 2 WriteLn(HolePosition('Peter', Liste)); // Ergibt: -1 end.
| Tipp: |
Ab Delphi 2009 ist es möglich, das Funktionsergebnis als Parameter von Exit anzugeben. Man kann die obige Beispielfunktion ab dieser Version mit |
|---|
| Inhaltsverzeichnis | Pascal: Typdefinition |
