Graphic Coding — Modularität — Woche 6

Zurück zur Übersicht, Aufgaben

Modularität & Wiederverwendbarkeit

Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch. Appropriate functions hide details of operation from parts of the program that don't need to know about them, thus clarifying the whole, and easing the pain of making changes. (Kernighan und Ritchie)

Um diese Vorteile zu erlangen, so dass Programme modular und wieder verwendbar werden, ist eine Aufteilung von Programmabschnitten mit Hilfe von Funktionen sinnvoll. Neben dem, dass uns Modularität dazu bringt, weniger Code schreiben zu müssen, hilft es

Funktionen

Funktionen sind ein wichtiges Mittel zur Modularisierung, indem oft benötigter Code in einer Funktion zusammengefasst wird und über den Funktionsnamen wiederholt aufgerufen werden kann. So kann die Funktion beliebig oft wieder eingesetzt werden.
Wir haben bereits gesehen, dass eine Funktion aus vier Teilen besteht:

Nun können wir nicht nur Processings interne Funktionen benutzen, sondern auch eigene schreiben. Dazu muss eine Funktion definiert werden — ähnlich wie eine Variable. Dies geschieht durch die Angabe der genannten vier Teile; in folgender Reihenfolge: Rückgabewert Name(Parameter) { Code }, also beispielsweise:

	void sayHello() {
		println("Hello"); // Gibt den Text Hello im Nachrichtenfeld aus.
	}

In der ersten Zeile steht zunächst void. Wenn eine Funktion keinen Wert zurückgeben soll, dann muss statt dessen void (englisch für "nichts, leer") angegeben werden. Als nächstes steht dort sayHello, der Name der Funktion. Wir können hier beliebige Namen verwenden, sollten aber — wie auch bei Variablen — "sprechende", d.h. sinnvoll beschreibende Namen einsetzen. In den runden Klammern steht nichts, was bedeutet, dass diese Funktion parameterlos ist.
Und diese Art einer Funktion haben wir bereits häufig angewendet, nämlich wenn wir die beiden (Spezial-)Funktionen setup() und draw() definierten.

Alle Anweisungen innerhalb der geschweiften Klammern gehören zu dieser Funktionen und werden ausgeführt, wenn die Funktion aufgerufen wird. Ein Funktionsaufruf geschieht, in dem der Funktionsname mit Klammern im Code geschrieben wird. Alle Anweisungen, wie etwa point(100, 200); sind solche Aufrufe. Die oben stehende Funktion ruft man also mit sayHello(); auf.

Parameter

Um die konkrete Ausführung einer Funktion zu beeinflussen, können wir Parameter verwenden. So wie wir durch Parameter beeinflussen, wo eine Linie gezeichnet wird, so können wir auch unsere eigenen Funktionen parametrisieren. Beim Aufrufen der Linienfunktion line(10, 10, 200, 200); geben wir die vier Werte als Parameter mit, die die Position der Linie bestimmen. (vgl. nochmal line())

	void drawEyes(int x, int y) {
		ellipse(x - 15, y, 30, 30);
		ellipse(x - 15, y, 10, 10);
		ellipse(x + 15, y, 30, 30);
		ellipse(x + 15, y, 10, 10);
	}

Wieder sagen wir mit void als Erstes, dass es keinen Rückgabewert hat. Dann steht dort der Funktionsname drawEyes. Und schließlich haben wir etwas zwischen den Klammern stehen. Dies sind die Parameter (auch Argumente genannt). Von den Parametern können wir beliebig viele für eine Funktion definieren; wichtig ist, dass diese Parameter innerhalb der Funktion ganz normale lokale Variablen mit dem angegebenen Datentyp sind. (Wir deklarieren die Parameter-Variablen also direkt in der ersten Funktionszeile.)

Rückgabewert

Funktionen können Rückgabe zurück geben, damit in der Funktion berechnete Werte weiter verwendet werden können (siehe auch Verwendung von Rückgabewerten). Dies geschieht, in dem wir zum Einen in der Funktionsdefinition statt void einen Datentyp (wie float) angeben, und zum Anderen mit der Anweisung return einen Wert vom definierten Datentyp zurück geben.

	int add(int a, int b) {
		int sum = a + b;
		return sum;
	}

In diesem Beispiel wird nun ein Rückgabewert definiert, und zwar vom Typ int. Die Funktion heisst add und nimmt die beiden int-Parameter a und b entgegen. Diese werden in dem Ausdruck addiert und in der lokalen Variable sum gespeichert, deren Wert durch return sum zurück gegeben wird.

Genau so sind auch die bisher bekannten Processing-Funktionen intern programmiert, zum Beispiel random(), sqr(), oder min(). Um dies zu verdeutlichen folgt hier die Funktionsdefinition der (bereits vorhandenen) Funktion min().

	// Diese Funktion gibt den kleineren der beiden Werte zurück
	int min(int value1, int value2) {
		int minValue;
		if (value1 < value2) {
			minValue = value1;
		}
		else {
			minValue = value2;
		}
		return minValue;
	}

mousePressed() & keyPressed()

Neben den beiden boolean-Variablen mousePressed und keyPressed gibt es in Processing auch noch zwei gleichlautende Funktionen. Während die Variablen von Processing — wie mouseX und mouseY ständig den aktuellen Wert besitzen, also ob eine Taste gedrückt ist, werden die Funktionen genau in dem Moment aufgerufen, in dem der Nutzer eine Taste drückt.

	void setup() {
		size(400, 400);
	}
	
	void draw() {
		line(width/2, height/2, random(width), random(height));
	}
	
	void mousePressed() {
		stroke(random(255));
	}

Diese Funktionen werden von Processing einmal aufgerufen. In dem obigen Beispiel wird die Strichfarbe also nur einmal pro Mausklick geändert, im Gegensatz zu dem folgenden, bei dem sich die Farbe solange ändert, solange man die Maustaste gedrückt hält.

	void draw() {
		if (mousePressed) {
			stroke(random(255));
		}
		line(width/2, height/2, random(width), random(height));
	}

Desweiteren gibt es die beiden Funktionen mouseReleased() und keyReleased(), die aufgerufen werden, wenn eine Taste losgelassen wird. Mit Hilfe der Funktion mousePressed() können wir also erkennen, wann der Nutzer eine Maustaste drückt, und mit mouseReleased(), wann er sie wieder loslässt.