Du hast in Funktionen nur die einfachste Variante einer Funktion gesehen. Wenn du dir den Befehl rect ansiehst, dann bekommt der Befehl noch einige Infos mit auf den Weg, nämlich Eckpunkt, Breite und Höhe:
rect(50, 50, 100, 80);
Diese Zusatzinformationen nennt man Parameter. Parameter geben dir die Möglichkeit, viele Varianten einer Aufgabe ("zeichne ein Rechteck!") mit der selben Funktion (rect) zu erfassen, indem die jeweils spezifische Ausprägung (Position x:50 y:50, Breite 100, Höhe 80) in den Parametern gesetzt wird.
Wenn wir bei dem Beispiel mit dem Roboter bleiben, der ein Parkticket kaufen soll, dann braucht dieser, um den Befehl ausführen zu können, auch Parameter: z.B. Geld, das er einwerfen soll oder ggf. die Information der Parkdauer, wie lange das Ticket gelten soll. Ohne diese Parameter kann der Roboter den Befehl nicht ausführen.
Parameter in der Funktionsdefinition
Damit deine Funktion Parameter entgegennehmen kann, musst du diese bei der Funktionsdefinition mitdefinieren. Zum Beispiel willst du der Gesichtsfunktion (aus Funktionen) die Augenfarbe mitgeben:
void zeichneGesicht(int augenfarbe) {
fill(255); // weiß
ellipse(x, y, 80, 80); // Gesicht
fill(augenfarbe);
ellipse(x-15, y-15, 20, 20); // Auge
ellipse(x+15, y-15, 20, 20); // Auge
}
Du fügst in den Klammern 2 Dinge hinzu: den Typ des Parameters und den Namen. Im Grunde ist das exakt das gleiche wie bei einer Variable, die man im Code deklariert. Und tatsächlich: der Parameter augenfarbe ist eine Variable.
Parameter ist lokale Variable
Ein Parameter ist eine lokale Variable innerhalb der Funktion. Das heißt, dass diese Variable eine begrenzte Lebensdauer hat. Sie beginnt bei der öffnenden geschweiften Klammer und endet bei der schließenden. Außerhalb des Funktionskörpers kann auf Parameter nicht zugegriffen werden.
Du kannst - durch Komma getrennt - mehrere Parameter definieren, z.B. die Farbe von sowohl Gesicht als auch Augen von außen bestimmen lassen:
void zeichneGesicht2(int gesichtsfarbe, int augenfarbe) {
fill(gesichtsfarbe);
ellipse(x, y, 80, 80); // Gesicht
fill(augenfarbe);
ellipse(x-15, y-15, 20, 20); // Auge
ellipse(x+15, y-15, 20, 20); // Auge
}
Die allgemeine Form der Funktionsdefinition mit Parametern ist:
void NAME(TYP1 PARAM1, TYP2 PARAM2, ...) {
CODE
}
Im folgenden Beispiel sind die Parameter a und b nur im Code der Funktion printMittelwert gültig.
void setup() {
printMittelwert(5.0, 12.5);
}
void printMittelwert(float a, float b) {
float summe = a + b;
println(summe / 2);
}
Jeder Versuch, die Parameter a und b z.B. in setup zu verwenden, wird mit einer Fehlermeldung bestraft, denn die Variablen existieren nur innerhalb der Funktionsdefinition.
void setup() {
printlnMittelwert(5.0, 12.5);
println(a); // Fehler! Variable a ist hier nicht gültig.
}
void printMittelwert(float a, float b) {
float summe = a + b;
println(summe / 2);
}
Was befindet sich "in" dem Parameter?
Eine gar nicht mal so unwichtige Frage ist, was sich genau in dem Parameter befindet, wenn die Funktion aufgerufen wird. Stell dir vor, wir wollen eine Funktion schreiben, die den Wert zweier Variablen vertauscht:
int wert1 = 1;
int wert2 = 2;
void setup() {
println("wert1: " + wert1 + " wert2: " + wert2);
swap(wert1, wert2);
println("wert1: " + wert1 + " wert2: " + wert2);
}
void swap (int x, int y) {
int temp = x;
x = y;
y = temp;
}
Was denkst du wird in der Konsole stehen? Die richtige Antwort lautet:
wert1: 1 wert2: 2
wert1: 1 wert2: 2
Zweimal das gleiche! Die Funktion hat also nicht unsere Variablen vertauscht. Warum ist das so?
Das Las-Vegas-Prinzip: "What happens in Vegas, stays in Vegas!"
In dem Moment, in dem wert1 und wert2 an die Funktion swap als Parameter übergeben werden, wird lediglich deren Wert übergeben (also 1 und 2), nicht die Variable selber! Dies wird auch als Call-by-Value (engl. call = Abruf, value = Wert) bezeichnet. Es wird als nur der Wert abgerufen, wodurch das, was wir in der Funktion machen, keine Auswirkungen auf die Variablen außerhalb hat: Was in der Funktion passiert, bleibt in der Funktion!
Das mag für den ein oder anderen nun logisch erscheinen, da innerhalb der Funktion swap die Variablen x und y lokal sind und sogesehen nichts mit wert1 und wert2 zu tun haben. Allerdings geht es auch anders: Das Gegenstück zu Call-by-Value ist Call-by-Reference. (engl. reference = Verweis). In diesem Fall würde nicht der Wert abgerufen werden, sondern der Verweis, auf die eigentliche Variable (Wie so eine Art Link im Internet, der auf eine Webseite verweist). In diesem Fall würde man durch den Verweis auf die Variable den Wert von wert1 und wert2 verändern. Was es damit genau auf sich hat, sehen wir später im Abschnitt Arten von Datentypen.
In Processing verwenden wird nur Call-by-Value. Du musst dir also keine Sorgen machen, diese beiden Dinge ausversehen zu vertauschen. Es ist erstmal nur wichtig, dass du verstanden hast, welcher Mechanismus dahinter steckt.
Parameter nicht verändern
WICHTIG: Verändere niemals einen Parameter innerhalb einer Funktion! Dies ist zwar möglich, aber schlechter Stil, da bei längerem Code unklar ist, ob der Parameter noch den ursprünglichen Wert hat.
// Schlechter Stil!
void zeichneFigur(int x, int y) {
rect(x, y, 20, 40);
x = x + 10; // Parameter x wird verändert!
y = y - 10; // Parameter y wird verändert!
ellipse(x, y, 20, 20);
}
Führe stattdessen jeweils eine neue lokale Variable ein:
// Stattdessen lokale Variablen:
void zeichneFigur(int x, int y) {
rect(x, y, 20, 40);
int x2 = x + 10; // OK
int y2 = y - 10; // OK
ellipse(x2, y2, 20, 20);
}
Aufruf mit Parametern
Beim Aufruf einer Funktion mit Parametern, musst du darauf achten, dass du genau die Information mitgibst, die deine Definition verlangt. Das heißt, die Anzahl, Reihenfolge und Datentypen der Parameter müssen mit den Angaben Ihrer Funktionsdefinition übereinstimmten.
Schauen wir uns nochmal die zwei Funktionen von oben an. Zunächst nochmal die Definitionen:
void zeichneGesicht(int augenfarbe) {
fill(255);
ellipse(x, y, 80, 80);
fill(augenfarbe);
ellipse(x-15, y-15, 20, 20);
ellipse(x+15, y-15, 20, 20);
}
void zeichneGesicht2(int gesichtsfarbe, int augenfarbe) {
fill(gesichtsfarbe);
ellipse(x, y, 80, 80);
fill(augenfarbe);
ellipse(x-15, y-15, 20, 20);
ellipse(x+15, y-15, 20, 20);
}
Diese Aufrufe sind falsch:
zeichneGesicht(36.5); // falscher Typ, muss int sein
zeichneGesicht(50, 100); // falsche Anzahl, nur 1 Parameter
zeichneGesicht2(50, "hallo"); // falsche Anzahl, falscher Typ
So ist es richtig:
zeichneGesicht(0);
zeichneGesicht2(255, 0);
// Du kannst auch Variablen verwenden ...
int farbe1 = 0;
int farbe2 = 255;
zeichneGesicht(farbe1);
zeichneGesicht2(farbe1, farbe2);
Überladung
Es gibt auch die Möglichkeit die Definition eine Funktion zu überladen. Was soll das denn heißen? Eine Funktionsdefintion ist dann überladen, wenn es mehr als eine Definition für eine Funktion gibt. Ein Beispiel ...
Wir möchten eine Funktion, die ein Gesicht zeichnet. Wenn dabei Koordinaten übergeben werden, soll das Gesicht an der Stelle der Koordinaten gezeichnet werden. Werden keine übergeben, soll das Gesicht in der Mitte des Bildschirms gezeichnet werden. Damit wir aber die Funktion zeichneGesicht nicht zweimal schreiben müssen, ruft die Funktion ohne Parameter einfach die Funktion mit Parameter auf und übergibt die Mitte des Bildschirms als Parameter. Der Code hierfür sieht folgendermaßen aus:
void setup() {
// Je nach Parameter wird die jeweilige Version der Funktion verwendet
zeichneGesicht(); // Version 1
zeichneGesicht(20, 20); // Version 2
}
// Version 1: ohne Parameter
void zeichneGesicht(int augenfarbe) {
zeichneGesicht(width/2, height/2);
}
// Version 2: mit Parameter
void zeichneGesicht(int x, int y) {
fill(255);
ellipse(x, y, 80, 80);
fill(0);
ellipse(x-15, y-15, 20, 20);
ellipse(x+15, y-15, 20, 20);
}
Mithilfe von Überladungen können somit verschiedene Versionen einer Funktion erstellt werden. Sie werden häufig eingesetzt, um z.B. Standardwerte einzusetzen, wenn keine Daten vorliegen - also in unserem Fall, wenn keine x-/y-Koordinaten vorliegen.
Zusammenfassung
Du kannst einer Funktion Parameter geben - das sind Informationen, die das Verhalten der Funktion bestimmen, z.B. Farbwerte oder Koordinaten.
In der Definition der Funktion gibst du den Parametern Namen und nennen den Typ. Im Prinzip deklarierst du hier eine Reihe von lokalen Variablen:
void NAME(TYP1 PARAM1, TYP2 PARAM2, ...) {
CODE
}
Beim Aufruf einer Funktion must du die konkreten Parameterwerte angeben (hierbei dürfen natürlich auch Variablen im Spiel sein). Dabei muss die Anzahl, Reihenfolge und Datentypen der Parameter mit den Angaben Ihrer Funktionsdefinition übereinstimmen.
Du kennst Funktionsaufrufe zur Genüge - jede Zeile des folgenden Codes ruft eine Funktion auf, die jeweils von Processing selbst zur Verfügung gestellt wurde.
size(200, 200);
background(0);
fill(255);
ellipse(50, 40, 20, 20);
println("fertig");
Häufiger Fehler ist, dass bei der Definition einer Funktion die Typen vergessen werden oder dass umgekehrt beim Aufruf einer Funktion der Typ mit angegeben wird.
Parameter sind nur innerhalb der Funktion gültig, sie sind lokale Varialben.
Wir eine Variable als Parameter übergeben, so wird nur ihr Wert übergeben. Das bedeutet, dass die Veränderung des Parameters innerhalb der Funktion keine Auswirkung auf die Variable außerhalb hat. Dieses Prinzip wird als Call-By-Value bezeichnet. Das Gegenstück dazu nennt man Call-By-Reference.
Wenn es mehrere Funktionsdefinitionen mit unterschiedlichen Parametern gibt, so bezeichnet man dies als Überladung. Überladungen werden häufig verwendet, um Standartwerte zu setzen.