7 Funktionen:

Zum vorigen Kapitel Zum Inhaltsverzeichnis Zum nächsten Kapitel


Häufig tritt der Fall auf, dass aus einer oder auch mehreren Eingangs-Variablen genau ein Ergebniswert errechnet und zurückgeliefert werden soll. Dann ist es sinnvoll, eine Funktion zu verwenden.

In Delphi wird eine Funktion folgendermaßen deklariert:
     Function MyFunc (x: Double) : Double;
x ist hier das Argument, das an die Funktion übergeben wird. Nach dem Doppelpunkt folgt der Ergebnis-Typ der Funktion, hier Double, genau wie das Argument. Im allgemeinen kann eine Funktion aber auch mehrere Werteparameter haben, deren Typen weder mit dem Ergebnistyp noch untereinander übereinstimmen müssen.

Implementiert wird eine Funktion genau wie eine Prozedur: nach der Wiederholung der Funktions-Deklaration (und eventuell der Deklaration lokaler Variablen) folgt ein "begin...end"-Block, der die auszuführenden Anweisungen enthält. Eine Spezialität ist die Erzeugung des Funktions-Ergebnisses: hierzu dient eine vordefinierte Variable namens Result, die vom Ergebnis-Typ der Funktion ist, also z.B.:
      TForm1.MyFunc (x: Double) : Double;
        begin
        Result := 3*sin(2*x) - x/2;
        end;
In Java wird eine Funktion namens "myFunc" folgendermaßen deklariert:
     public double myFunc (double x)
x ist hier das Argument, das an die Funktion übergeben wird. Vor dem Funktionsnamen steht der Ergebnis-Typ der Funktion, also hier "double", genau wie beim Argument. Im allgemeinen kann eine Funktion aber auch mehrere Werteparameter haben, deren Typen weder mit dem Ergebnistyp noch untereinander übereinstimmen müssen.

Implementiert wird eine Funktion in Java genau wie eine Prozedur: direkt im Anschluss an die Funktions-Deklaration folgt der "{...}"-Block, der die auszuführenden Anweisungen enthält. Eine Spezialität ist die Erzeugung des Funktions-Ergebnisses: hierzu dient die "return" - Anweisung, nach der diejenige Variable (vom gewünschten Ergebnistyp) genannt werden muss, die im Augenblick das Funktionsergebnis enthält. Statt einer Variablen kann hier auch ein Term stehen, dessen Wert kompatibel zum Ergebnistyp der Funktion ist, also z.B.:
     public double myFunc(double x) {
       return 3*Math.sin(2*x) - x/2;
     }

Einer solchen selbstdefinierten Funktion können auch mehrere Argumente übergeben werden. Und man braucht sich bei der Implementierung nicht auf einen Funktionsterm zu beschränken: es dürfen auch kompliziertere Berechnungen programmiert werden. Als Beispiel betrachten wir eine Funktion IntDiv(n, m), die für zwei Integer-Zahlen m und n berechnet, wie oft m in n enthalten ist. Dies könnten wir zum Beispiel so implementieren, dass wir solange m von n abziehen, bis n kleiner als m geworden ist. Die Anzahl der Subtraktionsschritte ist dann das Ergebnis dieser "Ganzzahl-Division":

In Delphi:
    TForm1.IntDiv(n, m: Integer): Integer;
      var i : Integer;
      begin
      i := 0;
      While n >= m do begin
        n := n - m;
        i := i + 1;
        end;
      Result := i;
      end;
In Java:
    public int intDiv(int n, int m) {
      int i = 0;
      while (n >= m) {
        n = n - m;
        i = i + 1;
      }
      return i;
    }

Das Beispiel zeigt, dass Funktionen zusätzliche lokale Variablen enthalten können. Diese Variablen sind grundsätzlich von außerhalb der Funktion nicht sichtbar: sie "leben" nur innerhalb des Blockes der Funktion, d.h. für die Dauer des Funktions-Aufrufes. Dies gilt im übrigen genau so für Prozeduren!

An Funktionen übergebene Argumente sind innerhalb der Funktion ebenfalls lokale Variablen. Dies hat zur Folge, dass die Werte der Parameter in der Funktion auch geändert werden können. Solche Werteänderungen haben jedoch keine Rückwirkung außerhalb der Funktion: in dem Programmteil, der den Funktionsaufruf enthält, hat der Parameter nach wie vor den beim Aufruf zugewiesenen Wert. Auch dies gilt genau so für Prozeduren!

Der wesentliche Unterschied zwischen Funktionen und Prozeduren ist, dass Funktionen stets ein Ergebnis "zurückgeben" (d.h. einen Wert an das aufrufende Programm liefern), Prozeduren hingegen nicht. Der Aufruf einer Prozedur ist daher schon für sich alleine eine vollständige Anweisung, wohingegen ein Funktions-Aufruf immer eingebettet ist in einen Term, dessen Wert dann im aufrufenden Programm weiterverarbeitet wird.


Bei der Bearbeitung den folgenden Aufgaben sollen Sie die Erstellung eigener Funktionen und den Umgang mit ihnen üben:


  1. Der Ganzzahl-Divisionsrest:

    Man kann die obige Ganzzahl-Divisions-Funktion "IntDiv" leicht so abändern, dass sie als Ergebnis den Rest liefert, den n bei der Division durch m liefert. Schreiben Sie ein Programm mit zwei Eingabefeldern für n und m, zwei Ausgabefeldern und zwei Funktionen "IntDiv" (wie oben) und "IntRest" (als modifizierte Kopie von "IntDiv"). Auf Knopfdruck sollen für die aktuellen Werte von n und m der Ganzzahlquotient q und der Rest r bei dieser Division ausgegeben werden. (Der mathematische Zusammenhang dieser 4 Zahlen ist also gegeben durch: n = q * m + r.)
    Der Quelltext der Funktion IntRest kann so optimiert werden, dass keine Deklaration lokaler Variablen benötigt wird und der Programmtext nur 3 kurze Zeilen lang ist. Schaffen Sie das?
    Lösungsvorschlag [Delphi] [Java]


    Auch wenn Sie die vorgeschlagenen Verbesserungen erfolgreich realisiert haben, bleiben die Implementierungen von IntDiv und IntRest alles andere als effizient, speziell für große Argumente. Wir brauchen uns allerdings nicht allzu lange damit aufhalten, sie weiter zu optimieren (obwohl das eine durchaus reizvolle Aufgabe wäre!), weil die Ganzzahl-Division und die Rest-Berechnung zu den elementaren Funktionen jedes Computers gehören: sie sind schon im Prozessor festverdrahtet!
    In Delphi können wir sie in Termen mit Hilfe der Operatoren "DIV" und "MOD" verwenden: statt
         q := IntDiv(n, m)   bzw.  r := IntRest(n, m) 
    können wir einfacher schreiben:
         q := n DIV m        bzw.  r := n MOD m 
    In Java können wir sie in Termen mit Hilfe der Operatoren "/" und "%" verwenden: sind n und m zwei Integer-Variablen, dann können wir statt
         q = IntDiv(n, m)   bzw.  r = IntRest(n, m) 
    einfacher schreiben:
         q = n / m          bzw.  r = n % m 


  2. Primzahl-Test:

    In der Kryptographie spielen Primzahlen eine große Rolle: sie sind die Grundlage vieler sicherer Verschlüsselungsalgorithmen. Es ist aber bis zum heutigen Tag kein Algorithmus bekannt, mit dem man auf einfache und schnelle Art ermitteln kann, ob eine gegebene natürliche Zahl n eine Primzahl ist oder nicht. Es bleibt einem im Wesentlichen nichts anderes übrig, als systematisch zu probieren, ob n einen echten Teiler hat oder nicht.

    Schreiben Sie ein Programm, das für eine einzugebende natürliche Zahl n auf Knopfdruck ermittelt, ob dies eine Primzahl ist oder nicht. Die Antwort soll im Klartext in einem Edit-Feld erscheinen.

    Implementieren Sie dazu eine Funktion IstPrim(), der die zu untersuchende natürliche Zahl übergeben wird und die die Zahl 1 zurückgibt, wenn n eine Primzahl ist, und die Zahl 0, wenn n einen Teiler (>1) hat. Testen Sie dabei alle möglichen Teiler k durch!

    Zwei Tips dazu:
    1. Eine natürliche Zahl k ist genau dann ein Teiler von n, wenn n ohne Rest durch k teilbar ist, also wenn n MOD k = 0 ist.
    2. Die Menge der möglichen Teiler k besteht durchaus nicht aus allen Zahlen {2, 3, 4, 5,....., n-1}. Sie können die Menge der zu testenden möglichen Werte für k stark verkleinern, wenn Sie ein bisschen genauer nachdenken...

    Lösungsvorschlag [Delphi] [Java]



  3. Die Werte-Tabelle:

    Schreiben Sie ein Programm, das eine selbstdefinierte Funktion namens "f" enthält. Auf Knopfdruck soll eine Wertetabelle dieser Funktion erstellt werden.

    In Delphi können Sie dazu eine StringGrid-Komponente verwenden sowie einen Button, in dessen Klick-Prozedur eine Schleife die laufenden x-Werte und die zugehörigen Funktionswerte in die Tabelle einfügt. Arbeiten Sie in dieser Schleife zunächst mit den privaten Variablen start, ende und schrittweite, deren Werte Sie in der OnCreate-Prozedur des Formulars setzen. Vergessen Sie auch nicht die Spaltenüberschriften in den beiden Feldern der obersten "FixedRow".

    Einträge von Strings in die Tabelle können Laufzeit mit Hilfe der Eigenschaft "cells[i, k]" vorgenommen werden: z.B. schreibt "sg.cells[2, 5] := 'Butter'" den String 'Butter' in die 6. Zeile der 3. Spalte der StringGrid-Komponente "sg" - die Nummerierung beginnt hier mit der "0"!
    In Java können Sie dazu eine JTable-Komponente verwenden sowie einen Button, in dessen Klick-Prozedur eine Schleife die laufenden x-Werte und die zugehörigen Funktionswerte in die Tabelle einfügt. Arbeiten Sie in dieser Schleife zunächst mit den lokalen Variablen start, ende und schrittweite, deren Werte Sie in der Klick-Prozedur des Buttons setzen. Vergessen Sie auch nicht die Spaltenüberschriften in den beiden obersten Feldern der Tabelle: diese Strings werden in der Eigenschaft "Columns" gesetzt.

    Zur Laufzeit kann man dann mit der Methode "setValueAt()" der JTable-Komponente Strings an bestimmten Stellen der Tabelle ausgeben: z.B. schreibt "jt.setValueAt(\"Honig\", 2, 5)" den String "Honig" in die 6. Zeile der 3. Spalte - die Nummerierung beginnt hier mit der "0"!

    Wenn Ihr Programm soweit korrekt arbeitet, können Sie Eingabefelder für start, ende und schrittweite hinzufügen. Nun muss die Zeilenzahl des StringGrid zur Laufzeit geändert werden:

    In Delphi setzen Sie dazu einfach die Variable StringGrid.RowCount! In Java ist eine Neudimensionierung der Tabelle zur Laufzeit ziemlich kompliziert. Zur Vermeidung dieses Problems ist es am einfachsten, zur Designzeit im Objektinspektor die Eigenschaft "RowCount" auf einen hinreichend hohen Wert zu setzen.

    Achten Sie auch darauf, dass schon bei der Eingabe stets ende > start bleibt.

    Selbst wenn das nun alles ganz toll funktioniert, haben Sie sicherlich noch offene Wünsche. Besonders in einem Punkt ist das Programm noch nicht richtig veröffentlichungsreif. Formulieren Sie einen entsprechenden Auftrag zur Behebung des größten Handikaps, und machen Sie sich die zu erwartenden Schwierigkeiten klar!

    Lösungsvorschlag [Delphi] [Java]




  4. Das Plot-Programm:

    Ergänzen Sie das obige Wertetabellen-Programm durch eine Turtle-Komponente zur Darstellung des Schaubilds Ihrer Funktion. 
    (Koordinatenachsen zeichnen, Skalierungsstrichl' anbringen und beschriften, dann Kurve einzeichnen...)

    (So etwa beginnt die Entwicklung eines Funktionen-Plotters.....)


Zum vorigen Kapitel Zum Inhaltsverzeichnis Zum nächsten Kapitel