Programmieren Teil 4 – Funktionsparameter

Im vorletzten Artikel wurde das allgemeine Konzept der Funktionen eingeführt, im letzten das Konzept der Variablen. Heute wollen wir beide Konzepte verbinden und uns anschauen, was unter Funktionsparametern zu verstehen ist.

Unsere bisherigen selbstgeschriebenen Funktionen haben immer nur maximal einen Wert zurückgeliefert; mehr haben sie nicht gemacht, zum Beispiel hier:

int f() { return 42; }

Die Funktion liefert den Wert 42 zurück, und zwar bei jedem Aufruf. Das ist zwar schön und gut, wenn man einen konstanten Wert berechnen und wiederverwenden möchte, aber so richtig weit kommt man damit nicht. Und wenn wir an die Mathematik denken, dann verhalten sich Funktionen dort auch meist ein klein wenig anders, indem sie nämlich ein Ergebnis abhängig von einer Eingabe berechnen. Die mathematische Funktion

sqr(x) = x * x

etwa berechnet das Quadrat der Eingabe. Um Funktionen sinnvoll nutzen zu können, benötigen wir das natürlich auch beim Programmieren – gibt es natürlich auch. Das Schlüsselwort hierfür sind die bereits erwähnten Funktionsparameter. Genutzt haben wir die auch schon indirekt, und zwar hier:

printf( "Hello World!" );

Die Funktion printf verlangt als Funktionsargument die Zeichenkette, die sie auf dem Bildschirm ausgeben soll (und zusätzlich eventuell noch weitere Argumente, aber das ist ein anderes Thema). Wir geben der Funktion damit eine Eingabe, mit der sie etwas macht (in diesem Fall, sie auf dem Bildschirm auszugeben). Die große Frage ist nun nur noch: wie teilen wir einer Funktion mit, dass sie eine Eingabe erwartet, und noch wichtiger: was für eine Eingabe?

Nichts leichter als das. Wir müssen einfach nur die Signatur der Funktion (wir erinnern uns: das war der Kopf, etwa hier: int main()) um die Informationen erweitern, welche Eingabe von einer Funktion erwartet wird. Dazu schreiben wir einfach in die Klammern der Signatur, welche Eingabe wir erwarten, sprich: welche Datentypen erwarten wir als Eingabe und mit welchem Namen wollen wir die Eingabe dann in der Funktion ansprechen. Das erfolgt in Form der bereits bekannten Variablendeklarationen und sieht zum Beispiel so aus:

int sqr( int x )

Hiermit definieren wir den Kopf einer Funktion, die eine einzelne ganze Zahl x als Eingabe erwartet und eine ganze Zahl als Ergebnis zurückliefert. Die Variable x bezeichnen wir als Funktionsparameter. Die vollständige Funktion sieht dann so aus:

int sqr( int x ) { return x * x; }

Wir sehen, dass die im Funktionskopf deklarierte Variable x wie eine normale lokale Variable im Funktionsrumpf genutzt werden kann. Die Nutzung der so definierten Funktion erfolgt dann in der bekannten Weise:

#include <cstdio> int sqr( int x ) { return x * x; } int main() { int x = 42; printf( "%i squared is: %i", x, sqr( x ) ); }

Eine Bemerkung zu den Begrifflichkeiten: bei der Definition einer Funktion spricht man von Funktionsparametern, beim Aufruf der Funktion spricht man bei den übergebenen Werten von Funktionsargumenten. Der Parameter ist also die im Funktionskopf deklarierte Variable, das Argument ist der tatsächlich eingegebene Wert. In int f( int x ) heißt x also Parameter, in f( x ) heißt x Argument.

Und um an dieser Stelle gleich einen bekannten Fallstrick zu erwähnen: Funktionsparameter und -argumente sind – genauso wie gleich benannte Variablen in unterschiedlichen Scopes – vollkommen unabhängig voneinander, sprich, nur weil der Parameter einer Funktion x heißt, muss als Argument nicht auch zwingend eine Variable mit der Bezeichnung x eingegeben werden. Das ergibt sich schon logisch daraus, dass als Argument jeder beliebige Ausdruck eingesetzt werden kann, und nicht nur Variablen. Der folgende Codeausschnitt ist ebenso gültig (in verkürzter Formatierung der sqr-Funktion):

int sqr( int x ) { return x * x; } int main() { int y = 42; printf( "%i squared is: %i", y, sqr( y ) ); }

Eine Funktion kann natürlich auch mehrere Eingabewerte verlangen; die werden dann einfach hintereinander geschrieben, etwa so:

int mul( int x, int y ) { return x * y; }

Der Aufruf erfolgt dann ganz simpel per

mul( 42, 2 )

Worauf man übrigens achten muss: wurde eine Variable bereits als Funktionsparameter deklariert, kann im Rumpf der Funktion keine Variable mit dem gleichen Namen deklariert werden. Der folgende Code ist also ungültig (unabhängig von seiner Sinnhaftigkeit):

int f( int x ) { int x = 1; return x * x; }

Ein letztes Konzept noch: bei manchen Funktionsaufrufen möchte man es sich manchmal ersparen, bestimmte Argumente immer mit angeben zu müssen, da sie sowieso meist (nicht immer!) den gleichen Wert haben sollen. In diesem Fall kommen dann die sogenannten Default-Argumente zum Einsatz. Bei der Deklaration eines Funktionsparameters kann ihm ein Standard- oder Default-Wert zugewiesen werden; wann immer beim Funktionsaufruf kein Argument für diesen Parameter übergeben wird, wird stattdessen automatisch der Default-Wert eingesetzt. Das sieht dann so aus:

int f( int x, int y = 1 ) { return x * y; }

Aufgerufen werden kann diese Funktion sowohl per f( 42, 2 ) als auch per f( 42 ) – im ersten Fall wäre das Ergebnis 84, im zweiten 42; natürlich kann auch mehr als ein Parameter mit Default-Argumenten versehen werden. Um bei einem Funktionsaufruf zu wissen, wann Default-Argumente eingesetzt werden müssen, dürfen diese natürlich nur am Ende der Parameterliste stehen, da sonst nicht klar wäre, wie die Funktion aufgerufen werden soll. Nehmen wir einmal den folgenden Funktionskopf (welches so ungültiger Code ist):

int f( int x = 1, int y, int z = 3 )

und dazu den folgenden Aufruf der Funktion:

f( 42, 84 )

Würde eine beliebige Anordnung der Default-Argumente erlaubt sein, wäre nun gar nicht klar, wie f aufgerufen werden soll. Es könnte die 42 für x und die 84 für y oder die 42 für y und die 84 für z eingesetzt werden. Aus diesem Grund ist das auch gar nicht erst erlaubt und Default-Argumente müssen immer am Ende der Parameterliste stehen.

Kommentare

  1. #1 MartinB
    April 16, 2013

    …wobei es ja wirklich schade ist, dass C++ keinen Aufruf der Art f(x=7, y=a) erlaubt

  2. #2 Marcus Frenkel
    April 16, 2013

    Darüber kann man trefflich streiten. Auf der einen Seite hätte ich mir das auch an der ein oder anderen Stelle gewünscht, weil man sonst (insbesondere bei Konstruktoren) zu abenteuerlichen Parameterlisten und Überladungen genötigt wird.

    Auf der anderen Seite ist diese Art der Notation für ein intuitives Verständnis von Programmcode nicht unbedingt förderlich, da immer erst die Parameterreihenfolge herausgefunden werden muss.

    Zwei weitere Punkte kommen hinzu:

    1.: Was soll man als Basis für die Parameternamen verwenden – die Funktionsdeklaration oder die Funktionsdefinition? Die Parameternamen können bei beiden voneinander abweichen. Noch schlimmer: in der Deklaration muss man gar keinen Namen angeben; die Deklaration würde aber als Basis für den Aufruf herangezogen werden (man kann auch Default-Argumente für namenlose Parameter angeben), so dass hier ein Namensproblem besteht.

    2.: f( x=7, y=a) ist ein gültiger Aufruf einer Funktion f, wenn im Aufruf-Scope die Variablen x und y vorhanden sind; Zuweisungen sind in C++ keine Anweisungen, sondern Ausdrücke mit dem Wert der rechten Seite der Zuweisung – rein syntaktisch ginge das also schon gar nicht.

  3. #3 MartinB
    April 16, 2013

    @marcus
    ” Die Parameternamen können bei beiden voneinander abweichen.”
    Ja, das ist eine der relativ sinnlosen Freiheiten von C++ – oder ist das zu irgendwas gut?

    “Auf der anderen Seite ist diese Art der Notation für ein intuitives Verständnis von Programmcode nicht unbedingt förderlich, da immer erst die Parameterreihenfolge herausgefunden werden muss.”
    ?? Das ist doch gerade der Fall, wenn ich nicht (x=a) im Argument schreiben darf.

    “Zuweisungen sind in C++ keine Anweisungen”
    Aber das müsste ja nicht zwingend so sein, niemand würde es prinzipiell verbieten, die Syntax entsprechend zu ändern oder eine spezielle Sytax wie
    x:=a
    einzuführen. (Sonst baut C++ ja auch für alles einen Spezialfall.)

  4. #4 Marcus Frenkel
    April 16, 2013

    @MartinB

    ”Die Parameternamen können bei beiden voneinander abweichen.”

    Ja, das ist eine der relativ sinnlosen Freiheiten von C++ – oder ist das zu irgendwas gut?

    Die ursprüngliche Intention dahinter kenne ich nicht; technisch ist das dadurch bedingt, dass bei der Zuordnung von Deklaration und Definition lediglich die Signatur eine Rolle spielt und der Variablenname technisch gesehen nicht zur Signatur gehört. Dadurch können beide voneinander abweichen.

    “Auf der anderen Seite ist diese Art der Notation für ein intuitives Verständnis von Programmcode nicht unbedingt förderlich, da immer erst die Parameterreihenfolge herausgefunden werden muss.”

    ?? Das ist doch gerade der Fall, wenn ich nicht (x=a) im Argument schreiben darf.

    Die Reihenfolge ist in C++ immer klar, wenn man die Deklaration der Funktion kennt, nämlich von vorne nach hinten minus eventuell einige Default-Argumente – die aber auch streng von hinten an gezählt. Durch Änderung der Reihenfolge der Argumente reicht ein einfacher Blick bei bekannten Funktionen nicht mehr, wenn man sich z.B. nur für das erste Argument interessiert (insbesondere beim Debuggen von Code) – man muss erst schauen, an welcher Stelle das “erste” nun steht.

    “Zuweisungen sind in C++ keine Anweisungen”

    Aber das müsste ja nicht zwingend so sein, niemand würde es prinzipiell verbieten, die Syntax entsprechend zu ändern oder eine spezielle Sytax wie
    x:=a einzuführen. (Sonst baut C++ ja auch für alles einen Spezialfall.)

    Syntaxänderungen gehen nicht, das macht kaum eine Sprache. Wenn die Syntax einmal ist, wie sie ist, bleibt sie so. Eine Syntaxerweiterung wäre natürlich eine Möglichkeit, wenngleich auch das Komitee zur Erweiterung von C++ davor glaube ich eher zurückschreckt, da jede Syntaxerweiterung die Sprache komplexer macht. Aktuell ist, soweit ich weiß, auch kein Vorschlag zur Angabe der Argumentpositionen in Bearbeitung.

  5. #5 MartinB
    April 16, 2013

    Anscheinend gibt es die Möglichkeit bei boost – benutzt habe ich das bisher aber noch nicht:
    http://www.boost.org/doc/libs/1_37_0/libs/parameter/doc/html/index.html

    Und nein, ändern kann und sollte man den Standard jetzt nicht mehr (es gibt bestimmt irgendwelche programmierer, die so geschichten wie x=a im Aufruf einer Funktion schreiben, um gleich noch irgendwo was zuzuweisen…)

  6. #6 Marcus Frenkel
    April 16, 2013

    Ach je, Boost-Magie. Ich vergaß. Boost kann ja alles.
    Das sieht fürchterlich kompliziert in der Nutzung aus, aber gut zu wissen, dass es so etwas gibt. Vielleicht braucht man es ja doch einmal.

  7. #7 Nestiii
    April 16, 2013

    Noch kein Wort zu Wert- vs. Referenztyp Parametern und Variablen? Wahrscheinlich auch besser, nicht gleich alle mit Pointern abschrecken, oder? Noch ein Tipp an alle: Beim Programmieren mit C++ am besten den Speicherplatz im Hirn “const” deklarieren, sonst wird er möglicherweise später von neuen Wissen (Programmiersprachen) unbeabsichtigt überschrieben ;-)

  8. #8 Marcus Frenkel
    April 16, 2013

    @Nestiii
    Kommt noch, kommt noch. Eins nach dem anderen.

  9. #9 t
    April 16, 2013

    Hallo !
    Ich hab eine Anregung:
    Stell am Ende der Serie eine Versionsverwaltung vor. Programmieren ist kein Vergnügen für Solisten. Da sind viele Menschen beteiligt.
    Gibt es da Systeme die ähnlich wie Wikipedia arbeiten ?

  10. #10 Marcus Frenkel
    April 16, 2013

    Gibt es da Systeme die ähnlich wie Wikipedia arbeiten ?

    Das verstehe ich nicht ganz. Es gibt Systeme für Versionsverwaltung von Quelltext (SVN und Git, um nur mal 2 verbreitete zu nennen) – mit Wikipedia haben die allerdings weniger zu tun.

  11. #11 rolak
    April 16, 2013

    Zu MartinBs benannten Parametern: Da braucht es keine magischen UniversalProblemSolver-Sprachen, das ist mir schon ziemlich oft untergekommen – auf die Schnelle im www gefunden habe ich allerdings nur VBA.

    Zu der return(42)-Funktion gibt es ja eigentlich den Klassiker:

    int random()
    {
      return 3;
      //proofed beeing random by throwing a dice//
    }

  12. #12 Marcus Frenkel
    April 16, 2013

    @rolak

    Zu MartinBs benannten Parametern: Da braucht es keine magischen UniversalProblemSolver-Sprachen, das ist mir schon ziemlich oft untergekommen – auf die Schnelle im www gefunden habe ich allerdings nur VBA.

    C#. ;)

  13. #13 rolak
    April 16, 2013

    C#

    Da siehste mal, Marcus, aus dem c*-Raum bin ich bisher nur mit c und c++ zwangskontaktiert worden.

    Ist ja auch *räusper* erst seit c# 4.0 ;-), wie ich gerade lese, denn ich habe doch noch den Stein der WeisenSprachen gefunden. Lisp, Python, Fortran durfte ich schon ausgiebig…

  14. #14 Doomtrain
    April 16, 2013

    C# 4.0 ist mir auch neu (ich halte mich an die 3.0 spezifikation, ist ja nur ne Erweiterung, keine Änderung ;)).

    Prinzipiell kann mal allerdings sagen, dass man in C# so ziehmlich alles machen kann, was einem einfällt (So krank es auch sein mag). Einiges ist aber auch sehr praktsich. Mir Fallen da gleich ein paar Beispiele ein, die passen nur nicht ganz zum Thema.

  15. #15 Stefan W.
    http://demystifikation.wordpress.com
    April 16, 2013

    Man kann im Funktionsrumpf sehr wohl einen Parameter verdecken, nur nicht unmittelbar.

    Eine gute Idee ist das freilich selten, weil es die Chance der Verwirrung erhöht:

    #include
    using namespace std;
    int f (int x)
    {
    int p = 0;
    for (int a = 0; a < x; ++a) {
    int x = a;
    p += a * 2;
    }
    return p;
    }
    int main (void) {
    cout << "f (3)" << f (3) << "\n";
    }

    Auch ist die Information, dass es sich um C++ handelt, im Artikel gut versteckt. Ich fand es nur in der Tagliste unten. Das #include ist noch verräterrisch – ansonsten könnte das meiste auch Java, Javascript, C und PHP sein.

    Scala hat benannte Parameter und Defaultargumente. Solange man die Parameter bei der Übergabe nicht benennt gilt auch die Von-links-nach-rechts-Auflösung.

    Allerdings kann man bei Scala multiple Parameterlisten verwenden und dann in jeder mit default-Parametern von rechts beginnen.


    def ruggediguh (ist: Int = 4, im: Int = 3) (blut:Int) (schuh: String="Ballettschuh") {
    /** some implementation*/
    }

    ruggediguh (2, 1) (b) ("foo")
    ruggediguh (im=9) (b) ()
    ruggediguh () (b) ()
    ruggediguh () (b) ()

    Allerdings hat diese Beispielfunktion kein Ergebnis, weil es nicht nötig ist um das Verhalten zu zeigen und ist daher besser als Prozedur bezeichnet.

  16. #16 Marcus Frenkel
    April 16, 2013

    @Stefan W.

    Man kann im Funktionsrumpf sehr wohl einen Parameter verdecken, nur nicht unmittelbar.

    Das weiß ich…bei geschachtelten Blöcken sind wir in der Serie aber noch nicht…

    Auch ist die Information, dass es sich um C++ handelt, im Artikel gut versteckt.

    Siehe den ersten Artikel der Serie…

  17. #17 Dr. Webbaer
    April 17, 2013

    Funktionsparameter bezeichnen einen explizit adressierten Scope oder Geltungsbereich innerhalb des Rechenapparats, der “extra” oder implizit bereitgestellt wird, es geht hier letztlich um die Verwaltung der Logik, um das Schaffen von Einheiten im Sinne von Divide et impera.

    Es gibt auch Funktionen oder F.-Parametriesierung außerhalb der Programmiererei, es gibt sozusagen für das Erkenntnissubjekt ein entsprechendes Logikbedürfnis, gerade auch die Verteilung entsprechend.

    MFG
    Dr. W

  18. #18 DeLuRo
    April 17, 2013

    @Druide Webbaer: Und was soll jetzt das Geschwafel?

    Das bringt niemanden weiter. Zuerst konzentriere man sich auf das, was da ist, und das, was damit getan werden muss — sonst hängt man sich an endlosen Denkschleifen auf. Es gibt Variablen, es gibt Funktionen mit oder ohne Parametern bzw. Argumenten, die kann man auch als Variablen betrachten, alles hat irgend einen Typ, alles hat Namen, und fertig.

    Selbstverständlich ist alles in Einheiten (Einzel- / Unter-Elemente) eingeteilt, und es geht immer darum, die mit den Werkzeugen verbundene Selbstverwaltung zu bewältigen.

  19. #19 Dr. Webbaer
    April 18, 2013

    DeLuRo, der lustige Roboter, man hätte hier gleich ein wenig vorbauen können Richtung OOP.
    Programmieren benötigt die Philosophie.

    MFG
    Dr. W

  20. #20 DeLuRo (Der Lustige Robot'--)
    April 19, 2013

    Man sollte vor allem nicht mit zu vielen Dingen gleichzeitig beginnen, sondern sortiert und anschaulich bleiben. Objekt-Orientierte Programmierung (OOP) ist ein komplexeres und höher angesiedeltes Paradigma, das kann später kommen. Und natürlich ist die Didaktik seiner Texte allein Sache von Marcus Frenkel.

    Einzig wichtig scheint mir der Hinweis auf die Vielschichtigkeit von “Programmieren” und die Abstraktionslevel zu sein: man kann auf Prozessorebene mit Maschinencode oder Assembler arbeiten, oder mit abstrahierten prozeduralen “Statements”, oder auch noch höher mit funktionalen oder objektorientierten Konstrukten. Letztlich bewirken dann Spezialsprachen wie solche zur Datenbankabfrage den Abruf noch komplexerer Semantiken.

    ­
    Programmieren ist Handwerk und benötigt daher zuerst keine Philosophie, sondern eine solide Ausbildung mit guten Standbeinen. — Philosophie ist außer für Berufsphilosophen etwas für Politiker, die anders keine Argumente finden, oder für Arbeitslose, die gerade nichts zu tun haben.

    Programmierer sind noch keine Informatiker, und tatsächlich kann man mit Produkten der Programmierung und weiteren Organisationsergebnissen größere schaffende “schöpfende” Tätigkeiten aufbauen. Informatiker sind gleichzeitig und in einer Person –gesprochen in Analogie zum Gebäudebau– Architekten, Bauingenieure, Projektplaner, Gewerkeleiter und (als Programmierer) Baufachgesellen und Hilfsarbeiter — und manchmal sogar die eigenen Finanziers.

    ­
    Trotzdem solltem man immer mit dem Anfang anfangen — den handwerklichen und fachlichen Grundlagen.

  21. #21 Dr. Webbaer
    April 19, 2013

    Trotzdem solltem man immer mit dem Anfang anfangen — den handwerklichen und fachlichen Grundlagen.

    Das klingt solid, lustiger Roboter, oder halbwegs lustiger Roboter.

    Und so wie die Beschaffenheit der Texte dem hiesigen Inhaltegeber obliegt, obliegt das Feedback in der Regel dem jeweiligen Geber, was Ihnen vielleicht entgangen ist.

    Und der findet halt, dass -auch um sich von der x-beliebigen Schilderung abzugrenzen- eine programmier-philosophische Ausrichtung nicht unhilfreich wäre.

    Programmieren ist Handwerk und benötigt daher zuerst keine Philosophie, sondern eine solide Ausbildung mit guten Standbeinen. — Philosophie ist außer für Berufsphilosophen etwas für Politiker, die anders keine Argumente finden, oder für Arbeitslose, die gerade nichts zu tun haben.

    Wobei zu diesem Handwerk bspw. die COBOL-Programmierer, die sich vor mehreren Jahrzehnten noch mit sozusagen nackten Dateien und selbst gebastelter Indizierung quälten, sozusagen philosophisch die indexorientierten Dateien anforderten und nach den ISAMs die RDBMSe, das SQL. Weil man mit den Datentabellen ja immer sehr ähnliches tat.

    Ähnlich ist auch OOP entstanden. Der Schreiber dieser Zeilen kennt noch Code, der sozusagen objektorientiert oder am Geschäftsobjekt orientiert war, als es OOP noch nicht gab.

    MFG
    Dr. W (der Ihre hiesigen kommentarischen Nachrichten ein wenig lasch findet)