Funktionen schreiben
Medium · 40 min · Kostenlos
Ziel & Lerninhalt
Schreibe wiederverwendbare Funktionen, die deinen Code übersichtlich halten und Fehler reduzieren.
- Eigene Funktionen mit Parametern definieren und aufrufen.
- Verstehen, was Scope (Gültigkeitsbereich) bedeutet.
- Code-Duplikation durch Funktionen eliminieren.
Erklärung & Wissen
Basic
Stell dir vor, du schreibst in jedem loop()-Durchlauf 10 Zeilen Code, um einen Sensor zu lesen und den Motor zu steuern. Kommt eine zweite Motorsteuerung dazu, kopierst du 10 Zeilen – und hast nun zwei Stellen, die du bei Änderungen beide updaten musst. Funktionen lösen das: Du schreibt den Code einmal, gibst ihm einen Namen (z.B. `steuerMotor(geschwindigkeit)`) und rufst ihn einfach auf. Änderungen passieren nur noch an einer Stelle.
Medium
Eine Funktion hat vier Teile: Rückgabetyp, Name, Parameter, Körper. Beispiel: `void bewegeServo(int winkel)` – gibt nichts zurück (void), heißt bewegeServo, nimmt einen int-Parameter. Im Körper steht `servo.write(winkel)`. Scope: Variablen, die innerhalb einer Funktion definiert werden, existieren nur dort. Globale Variablen (außerhalb aller Funktionen) sind überall sichtbar – sparsam verwenden! Zu viele Globale machen Code schwer wartbar.
Expert
Beim Funktionsaufruf legt der Compiler einen Stack-Frame an: Rücksprungadresse, Parameter-Kopien (bei Call-by-Value), lokale Variablen. Auf einem ATmega328 (Arduino Uno) ist der Stack nur 2 KB – tiefe Rekursionen können ihn überlaufen (Stack Overflow). In zeitkritischen ISR-Funktionen sollte man keine teuren Funktionsaufrufe machen. Inlining (Compiler-Direktive `inline` oder kurze Funktionen automatisch) vermeidet den Overhead. Beim ProtoPeaks-Einsatz: Servo-Steuer-Funktionen in eigene .h-Datei auslagern → modularer Projektaufbau.
Fun Fact
Die erste Programmiersprache mit Unterprogrammen (dem Vorläufer von Funktionen) war FORTRAN in den 1950ern. Davor musste jede Berechnung im Hauptprogramm-Faden stehen – Code-Duplikation war das Normale, nicht die Ausnahme.
- Scope
- Der Bereich im Code, in dem eine Variable sichtbar und verwendbar ist. Lokale Variablen leben auf dem Stack und existieren nur während der Funktionsausführung. Globale Variablen liegen im statischen Datensegment. Fehler durch globale Variablen: Zwei Funktionen ändern denselben Wert zur selben Zeit (Race Condition) oder überschreiben sich versehentlich.
- Argumentübergabe
- Wie Werte in eine Funktion hineingehen – als Kopie oder als Verweis. Call-by-Value: Funktion erhält eine Kopie, Original unverändert. Call-by-Reference (in C mit Zeigern, in C++ mit &): Funktion kann das Original verändern. Für große Datenstrukturen (Arrays) ist Referenz-Übergabe deutlich speichersparender.
- Stack-Frame
- Temporärer Speicher auf dem CPU-Stack für die Variablen einer aktiven Funktion. Beim Aufruf wird der Stack-Pointer verschoben, Rücksprungadresse und lokale Variablen gespeichert. Nach return wird der Pointer zurückgesetzt. Auf ATmega328 sind nur ~2 KB Stack verfügbar – bei Rekursion kann dieser Raum sehr schnell erschöpft sein.
- Void vs. Rückgabetyp
- void bedeutet: diese Funktion liefert kein Ergebnis zurück. Funktionen mit Rückgabewert (int, float, bool…) übergeben ein Ergebnis per return-Anweisung. Der Compiler prüft, ob der Rückgabetyp mit dem empfangenden Variablentyp übereinstimmt. Fehlt return in einer Nicht-void-Funktion, ist das Verhalten undefiniert.
Praktischer Versuch: Servo-Bibliothek mit eigenen Funktionen
Schreibe drei Hilfsfunktionen für Servo-Bewegungen. Der Unterschied: einmal mit Copy-Paste-Code, einmal mit Funktionen – sichtbar im Seriellen Monitor.
Ohne Funktionen: Code-Duplikation erleben
Schreibe loop() ohne Funktionen: Kopiere die servo.write() + delay()-Blöcke dreimal hintereinander für drei verschiedene Positionen (0°, 90°, 180°). Zähle die Codezeilen.
Check: Servo bewegt sich dreimal hintereinander, Code-Datei hat viele Wiederholungen.
Tipp: Bewusst den schlechten Ansatz ausprobieren – dann wird der Vorteil von Funktionen klar.
Funktion 'fahreZu' schreiben
Schreibe `void fahreZu(int winkel, int warteMs)` mit servo.write(winkel) und delay(warteMs) im Körper. Rufe sie in loop() dreimal auf: fahreZu(0,800); fahreZu(90,800); fahreZu(180,800);
Check: Code kompiliert, Funktion ist außerhalb von loop() definiert.
Tipp: Funktionsdefinition muss VOR loop() oder als Vorwärts-Deklaration (Prototyp) stehen.
Funktion mit Ausgabe erweitern
Füge in fahreZu() vor servo.write() folgende Zeile ein: Serial.print("Fahre zu: "); Serial.print(winkel); Serial.println("°");
Check: Serieller Monitor zeigt 'Fahre zu: 90°' bei jedem Aufruf.
Tipp: Serial.print() kann innerhalb jeder Funktion verwendet werden.
Scope testen: lokale vs. globale Variable
Definiere in fahreZu() eine lokale int-Variable `int test = winkel * 2;`. Versuche in loop(), auf `test` zuzugreifen. Lies die Fehlermeldung: 'test was not declared in this scope'.
Check: Lokale Variable innerhalb der Funktion ist außerhalb nicht zugreifbar (Compiler-Fehler).
Tipp: Absichtlich einen Fehler provozieren – den Compiler-Fehler lesen lernen.
Code-Zeilen vergleichen
Vergleiche beide Versionen (mit/ohne Funktion) im Kommentar. Ändere warteMs in fahreZu() auf 500 ms – alle drei Bewegungen ändern sich automatisch.
Check: Funktions-Version hat deutlich weniger Codezeilen, gleiche Funktion.
Tipp: Ändere jetzt die Wartezeit an einer einzigen Stelle – bei Copy-Paste müsstest du 3 Stellen ändern.
Servo an D9, VCC, GND.
// ProtoPeaks - Funktionen Demo
#include <Servo.h>
Servo servo;
void fahreZu(int winkel, int warteMs) {
Serial.print("Fahre zu: ");
Serial.print(winkel);
Serial.println("°");
servo.write(winkel);
delay(warteMs);
}
void setup() {
Serial.begin(9600);
servo.attach(9);
}
void loop() {
fahreZu(0, 800);
fahreZu(90, 800);
fahreZu(180, 800);
}- Compiler: 'fahreZu' was not declared in this scope. — Funktionsdefinition steht NACH loop(). Entweder vor loop() verschieben oder Prototyp `void fahreZu(int,int);` vor setup() einfügen.
- Servo zuckt, nicht smooth. — delay() blockiert alles – das ist normal in diesem Beispiel. Für flüssige Bewegung später millis() verwenden.
Kurzzusammenfassung
Funktionen fassen wiederholenden Code zusammen, reduzieren Fehler und machen Programme lesbar. Scope hält Variablen sicher in ihrem Bereich. Im ProtoPeaks-Code lohnt sich eine eigene Servo-Hilfsfunktion schon ab dem dritten Aufruf.