Kapitel 10 - Subroutinen (Funktionen)

Unterprogramme (Funktionen)

Was sind Unterprogramme und wozu benötigen Sie diese? Unterprogramme sind wie schon in vielen anderen Programmiersprachen, nichts anderes als ein mit Namen und mit dem vorangestellten Schlüsselwort sub, versehener Anweisungsblock. Also einfach Funktionen.

    sub ICH_BIN_EINE_FUNKTION
    {
        #Anweisung(en) was die Funktion machen soll
    }

Hierzu ein kurzes Beispiel ...

    #!/usr/bin/perl -w

    sub function            #Beginn der Funktion function
    {
        print "Ausgabe in der Funkion \"function\"\n";  #Was die Funktion macht
    }  #Ende der Funktion function

    print "Wir rufen nun die Funktion \"function\" auf\n";
    function();           #Wir rufen die Funktion function auf
    print "Jetzt nach dem Funktionsaufruf\n";

Mit dem Schlüsselwort sub weis unser Perl-Interpreter, hier ist also eine Funktion. sub heißt soviel wie subfunction (Unterfunktion), was für den Perl-Interpreter nichts anderes heißt, dass er diese Funktion nur ausführt, wenn Sie auch aufgerufen wird. Und wie wir eine Funktion aufrufen haben Sie eben gesehen ...

    function();

In Perl gibt es außer dieser Methode Funktionen aufzurufen, noch folgende Schreibweisen ...

    &function;
    &function();
    function;

Alle Möglichkeiten liefern das selbe Ergebnis. In diesem Fall macht die Funktion eine Textausgabe mittels print auf dem Bildschirm.

Wozu sollen Funktionen jetzt gut sein? Ich zähle folgende hervorhebende Punkte dazu auf ...

  • Der Code kann immer wieder verwendet werden. Anstatt ein und die selbe Routine immer wieder zu schreiben kann man diesen einfach per Copy und Paste in anderen Programmen wiedereinbauen. Oder gar eigene Module erstellen.
  • Fehler werden schneller gefunden, da das Programm übersichtlicher ist.
  • Bei Veränderung eines Programms muss nicht der ganze Code umgeschrieben werden, sondern meist nur eine einzelnen Funktion.

Dies sind nur einige Punkte welchen Vorteil Unterprogramme haben. Weitere werden Sie im Laufe dieser Kapitel von selbst bemerken.

Sie haben übrigens schon seit dem "Hallo Welt" - Perl-Programm, Funktionen verwendet. Beispielsweise print ist auch nichts anderes als eine Funktion.

Parameter, Argumente und Standardvariable @_

Wenn Sie sich die Schreibweise von Funktionen wie z.B. print ansehen, können Sie erkennen, dass die meisten Funktionen mit einem Parameter eingesetzt werden. Beispiel ...

    print "Ich bin der Parameter von print\n";

Wollen wir uns doch mal ansehen, wie wir dies in Perl bewerkstelligen können ...

    #!/usr/bin/perl -w

    sub lese_einen_wert       #Beginn der Funktion lese_einen_wert
    {
        print "Sie haben folgendes eingegeben : " . $var . "\n";
    }  #Ende

    $var=0;

    print "Bitte machen Sie eine Eingabe : ";
    chomp ($var=<STDIN>);

    lese_einen_wert();     #Funktionsaufruf

Ein weiteres Beispiel mit einem Array ...

    #!/usr/bin/perl -w

    sub gib_sortiert_aus       #Beginn
    {
        foreach (sort{$a <=> $b} @werte)
        {
            print $_ , "\t";
        }
        print "\n";
    }  #Ende

    @werte = (2,9,12,43,2,3,1);

    gib_sortiert_aus();

Das sieht doch schon gut aus oder? Aber was machen Sie jetzt, wenn Sie das folgende Array ...

    @mehr_werte=(4,66,33,64,34,6,3,65);

... auch noch sortiert ausgeben wollen? Programmiererfahrene wissen es natürlich. Wir rufen die Funktion mit einem Argument auf ...

    gib_sortiert_aus(@werte);
    gib_sortiert_aus(@mehr_texte);

Nun benötigen Sie nur noch das Wissen, wie Sie mit der Funktion dieses Argument empfangen und bearbeiten können. Hierzu gibt es wieder eine weitere Standardvariable, nämlich @_. Wenn also eine Funktion aufgerufen wird, übernimmt Perl diese Argumente in die Standardvariable @_ und steht dann somit der Funktion zur Verfügung. Dazu folgt wieder ein Skript ...

    #!/usr/bin/perl -w

    sub gib_sortiert_aus          #Beginn
    {
        foreach (sort{$a <=> $b} @_)
        {
            print $_ , "\t";
        }
        print "\n";
    }  #Ende

    @werte = (2,9,12,43,2,3,1);
    @mehr_werte=(4,66,33,64,34,6,3,65);

    gib_sortiert_aus(@werte);
    gib_sortiert_aus(@mehr_werte);

Was wurde hier geändert? Richtig die Standardvariable @_. In dieser stehen jetzt bei jedem Funktionsaufruf die einzelnen Argumente, mit entsprechenden Parametern. Dazu ein weiteres Beispiel ...

    #!/usr/bin/perl -w

    sub verdopple  #Beginn
    {
        @temp= map {$_* 2} @_ ;
        print "@temp\n";
    } #Ende

    @temp=0;
    @werte = (2,9,12,43,2,3,1);
    @mehr_werte=(4,66,33,64,34,6,3,65);
    $skalare=100;

    verdopple(@werte);
    verdopple(@mehr_werte);
    verdopple($skalare);     #So gehts auch

Bei diesem Beispiel verdoppeln wir unseren Wert, der einzelnen Argumente, die wir als Parameter an der Funktion übergeben haben.

Mehrere Parameter und Argumente

Eben haben Sie gesehen, wie es möglich ist mit Hilfe der Standardvariablen @_, Parameter, die Sie an eine Funktion übergeben, weiterverarbeiten können. Was ist aber, wenn Sie folgendes von unserer Funktion berechnen lassen wollen ...

    multipliziere($werta, $wertb);

Dafür gibt es in Perl eine weitere Standardvariable für Funktionen. Nämlich ...

    $_[INDEX_NUMMER];

Der erste Parameter sieht somit so aus ...

    $_[0];

Der zweite ... $_[1], der dritte ... $_[2] usw. Folglich können Sie diese Standardvariablen in der Funktionen wie folgt einsetzten ...

    #!/usr/bin/perl -w

    sub mehr_parameter
    {
        print "1.Parameter : " . $_[0] . "\n";
        print "2.Parameter : " . $_[1] . "\n";
        print "3.Parameter : " . $_[2]. "\n";
    }

    $var=666;
    $name=0;

    print "Bitte geben Sie Ihren Namen ein : ";
    chomp ($name = <STDIN>);

    mehr_parameter($var, $name, 999);
    print "\n";
    mehr_parameter($name, "Hallo", "Welt");

Bei Array, Hashes und Listen wird dies nun wieder nicht funktionieren. Das Programm wird zwar ablaufen, aber durch die Parameterübergabe $_[0] werden die einzelnen Argumente eines Arrays gebrochen. Somit würde immer nur das erste Element eine Arrays ausgegeben werden. Hier ein Beispiel dazu ...

    #!/usr/bin/perl -w

    sub lese_array
    {
        @tmp = $_[0];
        print "@tmp\n";
    }

    @tmp=0;
    @array = ("Feuer", "Eis", "Flamme", "Ball");

    lese_array(@array);

Hier wird nur das Wort Feuer ausgegeben. Sie können gerne folgendes in der Funktion hinzufügen ...

    print $_[1] , "\n";

Somit wird Feuer und Eis ausgegeben. Was können Sie also tun, damit Arrays, Hashes oder Listen nicht gebrochen werde?

In diesem Fall könnten Sie die Standardvariable @_ verwenden, aber sobald Sie mehrer Parameter übergeben und vor allem Unterschiedliche funktioniert dies auch wieder nicht mehr damit ...

    #!/usr/bin/perl -w

    sub lese_verschiedenes
    {
        @tmp = @_ ;
        print "@tmp\n";
    }

    @tmp=0;
    @array = ("Feuer", "Eis", "Flamme", "Ball");
    $var=99;
    %hash = ("Name" => "user",
         "Vorname" => "superuser");

    lese_verschiedenes(@array, $var, %hash);

In diesem Beispiel werden alle drei Parameter in die Standardvariable @_ gelegt und sind somit nicht mehr voneinander zu unterscheiden.

Wir benötigen also etwas, womit Sie die Adressen der Variablen an der Funktion übergeben können. Ein Schelm wer da jetzt an Zeigern denkt wink Und doch haben Sie Recht, sollten Sie an diese Gedacht haben. In Perl gibt es auch so etwas in der Art, wie die Zeiger. Aber keine Angst, in Perl ist es nicht möglich auf irgendeine Adresse zu zeigen und damit ein Chaos zu verursachen wie etwa in C. Referenzen in Perl werden nur verwendet um auf Variablen zu verweisen.

Referenzen in Perl werden einfach dem Array oder dem Hash mit dem Präfix '\' (Backslash) vorangestellt, wie folgendes Skript demonstrieren soll ...

    #!/usr/bin/perl -w

    sub lese_array
    {
        $tmp = $_[0];
        print "@{$tmp}" , "\n";
    }

    $tmp=0;
    @array = ("Feuer", "Eis", "Flamme", "Ball");

    lese_array(\@array);

Machen Sie sich jetzt noch keinen Gedanken zu dem Syntax. Zu den Referenzen kommt noch ein extra Kapitel. Wichtig ist hier nur, dass Sie wissen, wie man Arrays, Listen und Hashes an eine Funktion (Unterprogramm) übergeben kann. Hier nun ein Beispiel wie schon im Beispiel oben, wo mehrere Fälle aufeinandertreffen ...

    #!/usr/bin/perl -w

    sub lese_verschiedenes
    {
        $tmp = $_[0] ;
        print "@{$tmp}\n";
        $tmp = $_[1];
        print $tmp , "\n";
        $tmp = $_[2];
        print %{$tmp} , "\n";
    }

    $tmp=0;
    @array = ("Feuer", "Eis", "Flamme", "Ball");
    $var=99;
    %hash = ("Name" => "user",
         "Vorname" => "superuser");

    lese_verschiedenes(\@array, $var, \%hash);

Was bisher an den Funktionen ein wenig störend war, ist das immer Globale Variablen eingesetzt wurden. Das sind Variablen die für das gesamte Programm sichtbar sind. Wie im folgendem Beispiel ...

    #!/usr/bin/perl -w

    sub global
    {
        print $var , "\n";
    }

    $var=99;

    print $var , "\n";
    global();

Hier wird zweimal 99 ausgeben. Nun was ist aber wenn sich in der Funktion die Zahl verändert, obwohl Sie den Wert noch für weiteren Berechnungen benötigt hätten? Beispielweise ...

    #!/usr/bin/perl -w

    sub global
    {
        $var*=2;
        print $var , "\n";
    }

    $var=99;

    print $var , "\n";
    global();
    printf "Hier präsentieren wir den Wert 99 : " . $var . "\n";

Das war wohl nichts mit dem Wert 99. Damit eine Variable nur für Ihre Funktion gültig ist, benötigen Sie das Schlüsselwort my, welches der Variable vorangestellt wird. Hier nun unser Beispiel mit my ...

    #!/usr/bin/perl -w

    sub global
    {
        my $var= $_[0];     #lokale Variable
        $var*=2;
        print $var , "\n";
    }

    $var=99;

    print $var , "\n";
    global($var);
    printf "Hier präsentieren wir den Wert 99 : " . $var . "\n";

Natürlich könnten Sie auch in der Hauptfunktion das Wort my vor $var stellen und in der Funktion global entfernen. Es würde genauso funktionieren. Sie haben in diesem Beispiel die Variable $var als Parameter an unsere Funktion global übergeben. Denn durch die Definition my $var haben wir zwar zwei identisch lautende Variablen, aber belegen diese beiden einen völlig anderen Adressplatz im Speicher.

Eine weitere Möglichkeit eine Variable lokal zu definieren wäre mit dem Schlüsselwort local. Mit diesem Schlüsselwort ist es möglich, dass eine Variable in der Funktion als lokal definiert wird aber für andere Unterfunktionen, damit sind weitere Funktionen gemeint, global bzw. sichtbar bleiben. Einfaches Beispiel ...

    #!/usr/bin/perl -w

    sub func1
    {
        local $var= $_[0];      #$var ist local aber für func2 sichtbar
        $var*=2;
        print $var , "\n";
        func2();
    }

    sub func2
    {
        $var*=2;        #ist local $var von func1
        print $var , "\n";
    }

    $var=99;

    print $var , "\n";
    func1($var);
    printf "Hier präsentieren wir den Wert 99 : " . $var . "\n";

Hier wurde durch das Schlüsselwort local die Variable $var als lokal definiert. Diese ist somit durch das Schlüsselwort local in der Funktion func2 sowie für weitere Unterfunktionen sichtbar. Nur nicht für die Hauptfunktion. Was uns die Ausgabe auch bestätigen wird.

Die Parameterübergabe in @_ kann auch durch Iteratives zerstörendes Auslesen der Parameterliste mittels shift geschehen ...

    #!/usr/bin/perl -w

    sub func{
        my $var1=shift;
        my $var2=shift;
        my $var3=shift;
        my $var4=shift;

        return $var1+$var2+$var3+$var4;
    }

    @array = (10,30,13,11);

    $ergebnis=func(@array);
    print $ergebnis , "\n";

Mit dieser Methode wird jeder Wert im Array pro shift-Aufruf an eine Variable übergeben. Der Inhalt von @_ wird dabei aber Zerstört.

Um in Perl eine statische Variable wie in C zu realisieren, können Sie sich mit einem *BEGIN*-Block behelfen ...

    #!/usr/bin/perl -w

    BEGIN{
        my $stat=0; #Statische Variable
        sub func{
            print $stat++ ,"\n";
        }
    }

    foreach(0..10){
        func(); #Funktionsaufruf
    }

Die Funktion selbst befindet sich dabei im BEGIN-Block. Statische Variablen sind Variablen, die Ihren Wert nach Beenden der Funktion behalten und auf die man beim nächsten Funktionsaufruf wieder zugreifen kann. Besser noch als in C können auf diese Variable gleich mehrere Funktionen auf einmal zugreifen ...

    #!/usr/bin/perl -w

    BEGIN{
        my $stat=0; #Statische Variable
        sub func{
            print $stat++ ,"\n";
        }
        sub func2{
            print $stat * $stat ,"\n";
        }
    }

    foreach(0..10){
        func();
        func2();  #Funktionsaufruf
    }

Rückgabewert

Damit eine Funktion einen Wert zurückliefern kann, benötigen Sie das Schlüsselwort return. Hier ein Beispiel wie Sie einen Wert aus einer Funktion zurückgeben können ...

    #!/usr/bin/perl -w

    sub fakul
    {
        my $x=$y=$_[0];
        while(--$x)          #Solang $x-1 bis 0
        {
            $y*=$x;
        }
        return $y;  #Rückgabewert der Funktion fakul
    }

    print "Fakultätberechnung! Von welcher Zahl : ";
    chomp ($var=<STDIN>);

    $ergebnis=fakul($var);
    #Funktion fakul gibt das Ergebnis mittels return zurück

    print $ergebnis , "\n";

Die berühmte Berechnung der Fakultät, die in fast jeder Programmiersprache demonstriert wird. Hier sehen Sie also wie mittels return $y der Wert an den Funktionsaufruf ...

    $ergebnis=fakul($var);

... an die Variable $ergebnis übergeben wird. Jetzt werden Sie sich sicherlich fragen, ob es in Perl auch möglich ist, wie in anderen Programmiersprachen, die Fakultät, Rekursiv zu berechnen. Hier das Beispiel Fakultät rekursiv ...

    #!/usr/bin/perl -w

    sub fakul
    {
        my $x=$_[0];
        if($x)    #Abbruchbedienung der Rekursion
        {
            return $x * fakul($x-1);   #Rekursiver Aufruf
        }
        return 1;
    }

    print "Fakultätberechnung! Von welcher Zahl : ";
    chomp ($var=<STDIN>);

    $ergebnis=fakul($var);
    #Funktion fakul gibt das Ergebnis mittels return zurück

    print $ergebnis , "\n";

Ich möchte jetzt nicht so genau darauf eingehen. Doch für Anfänger sei kurz zur Rekursion gesagt: Eine Rekursion ist eine Funktion die sich immer wieder selbst aufruft bis eine bestimmte Bedingung erfüllt ist. Diese Bedingung stellt gleichzeitig die Abbruchbedienung der Rekursion dar.

Mit dem Schlüsselwort return wird auch eine Funktion beendet. Beispiel ...

    #!/usr/bin/perl -w

    sub func
    {
        print "Ich werde ausgegeben\n";
        return 1;
        print "Ich leider nicht mehr\n";
    }

    func();

In diesem Beispiel wird nur die erste print-Anweisung ausgeführt. Die zweite nicht mehr, da die Funktion an den Aufrufer den Wert 1 zurückgibt und sich somit beendet. Nicht wegen des Wertes 1 sondern wegen dem Schlüsselwort return. Sie können diese Beispiel ja auch mal ohne den Wert 1 schreiben.

eval

In Perl ist es möglich, während der Laufzeit Programmcode dynamisch zu erzeugen und Auszuführen. eval() ist eine sehr mächtige Funktion. Ich werde daher nur auf die Grundlegenden Funktionen von eval() eingehen.

eval() können Sie auf zwei Verschiedenen Arten verwenden. Zum einen als Codeangabe in Strings ...

    #!/usr/bin/perl -w

    print "Bitte geben Sie einen Code ein : ";
    chomp($code=<>);

    eval $code; #Code wird ausgeführt

Sie können ja Beispielsweise folgendes eingeben ...

    Bitte geben Sie einen Code ein : print $value=20*10 , "\n";

Die Ausgabe ist anschließend 200. Vorraussetzung ist natürlich, dass Sie auch ausführbaren Code eingegeben haben.

Von der Stringmethode, Code zu evaluieren, wird aber aus Sicherheitsgründen abgeraten. Da man nicht davon ausgehen kann, dass alle Benutzer einfache Anwender sind, kann ein Übelgesinnter schnell dazu Übergehen, gefährlichen Code einzugeben.

Die zweite Möglichkeit ist es, denn Code in geschweiften Klammern zu Schreiben ...

    #!/usr/bin/perl -w

    while(1){
        eval { $wert1=<>; $wert2=<>; $erg=$wert1/$wert2 };
        print "Ergebnis : $erg \n" unless $@;
    }

Der Vorteil an dieser Schreibweise ist, dass der Code, zur Kompilierzeit nur einmal geparst wird. Geben Sie in diesem Beispiel als Wert ein ungültiges Zeichen ein, wird anstatt einem Programmabbruch, ein Fehlercode ausgegeben. Dieser Fehlercode befindet sich in der reservierten Variablen $@. Tritt kein Fehler auf, ist diese Variable leer.

Eine Sinnvolle Anwendung der Funktion eval() wäre die Auswertung einer Ausnahmebehandlung mit try() und catch() wie dies in C++ oder Java verwendet wird ...

    try{ /*Code der Ausnahme auslösen könnte*/ }
    catch { /*Code der die Ausnahme behandelt*/ }

Und hier das ganze in der Praxis ...

    #!/usr/bin/perl -w

    $wert1=<>;
    $wert2=<>;
    chomp $wert1;
    chomp $wert2;

    eval { $ergebnis=$wert1/$wert2; }
    or do{ print "FEHLER_HANDLER : $@\n" if $@;
        $ergebnis=0;
    };

    print "$ergebnis \n";

Mit diesem Programm können Sie zum Beispiel verhindern, dass das Programm bei einer Division durch 0, nicht abrupt abgebrochen wird. In eval{...} evaluieren wir den Code. Falls eine reguläre Division erfolgt, wird dies als Ergebnis ausgegeben. Falls Sie aber zum Beispiel 5/0 rechnen wollen, geht es in den or do Anweisungsblock. In diesem wird die reservierte eval-Fehler-Variable ($@) überprüft, ob sich darin etwas befindet. Falls ja, wird der Fehlerhandler ausgeführt und die Variable $ergebnis auf 0 gesetzt.

Wie gesagt das Thema eval() ist sehr Umfangreich und es wurde nur kurz behandelt.

wantarray

Wenn Sie eine kontexintesive Funktion schreiben wollen, ist auch dies in Perl kein Problem. Mit der Funktion wantarray() können Sie überprüfen, ob Ihre Funktion im Listenkontex aufgerufen wurde (true) oder im skalaren Kontex (false). Dabei gehen Sie wie folgt vor ...

    sub func{
        if(wantarray){
            #Rückgabewert ist @array
        }
        else{
            #Rückgabewert $skalar
        }
    }

Und so sieht eine Kontexintesive Funktion in der Praxis aus ...

    #!/usr/bin/perl -w

    sub kontex{
        if(wantarray){
            my @add = @_;
            my $tmp;

            foreach $add(@add){
            $add=$tmp+=$add;
            }
            return @add;
        }
        else{
            my($tmp, $add);
            foreach $add(@_){
                $tmp+=$add;
            }
            return $tmp;
        }
    }

    @liste = (1..10);

    $tot = kontex(@liste);
    print $tot , "\n";

    @res = kontex(@liste);
    print "@res\n"

Memoizing

Memoizing ist eine Technik die viele aufwendige Funktionen in Perl beschleunigen kann. Die Prinzip funktioniert mit einer Cache in der bereits gerechnete Ergebnisse zwischengespeichert werden. Bei einer erneuten Berechnung mit der Funktion wird also erst mal in die Cache gesehen, ob dieser bereits berechnete Werte dazu beinhaltet. Falls ja, wird die Cache verwendet und es kann auf eine erneute aufwendige Berechnungen verzichtet werden. Befindet sich kein bereits berechneter Wert in der Cache, muss eben eine aufwendige Berechnung durchgeführt werden.

Am besten können Sie das ganze Anhand der Berechnung der Fibonaccizahlen sehen ...

    #!/usr/bin/perl -w

    {
        local %cache;
        sub fibonacci_cache{
            return 1 if $_[0] == 0 || $_[0] == 1;
            return $cache{$_[0]} if exists $cache{$_[0]};
            return $cache{$_[0]} = fibonacci_cache($_[0]-1) + fibonacci_cache($_[0]-2);
        }
    }

    sub fibonacci_nocache{
        return 1 if $_[0] == 0 || $_[0] == 1;
        return fibonacci_nocache($_[0]-1) + fibonacci_nocache($_[0]-2);
    }

    print "Fibonacci mit Cache........\n";
    $fib1 = fibonacci_cache(30);
    print $fib1 , "\n";

    print "Fibonacci ohne Cache.........\n";
    $fib2 = fibonacci_nocache(30);
    print $fib2 , "\n";

Die Berechnung mit Cache (fibonacci_cache) ist von der Zeit her nicht Erwähnenswert. Ohne Cache dagegen warten Sie hier schon ein paar Sekunden.

Die Unterbringung des Caches mussten Sie in einem Block mit einer dynamischen lokalen Variablen verwenden.

Obwohl hier ein Hash verwendet wurde, muss dies nicht unbedingt so gemacht werden. Im Gegenteil, mit Arrays könnten Sie die Perfomance noch mehr Beschleunigen, da Array weniger Speicher benötigen.

Memoizing würde sich auch sehr gut für eine aufwendige Sortierung eignen.

Zurück zu Kapitel 9 | Zur Übersicht | Weiter zu Kapitel 11

-- EnricoEhrhardt - 19 Aug 2008
Topic revision: r2 - 2008-08-19 - 17:11:49 - EnricoEhrhardt
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.