CodeGym /Kurse /Kotlin SELF /Deklaration und Aufruf von Funktionen

Deklaration und Aufruf von Funktionen

Kotlin SELF
Level 8, Lektion 1
Verfügbar

1. Funktionsdeklaration

Wenn man gerade erst mit dem Programmieren anfaengt, scheint die einfachste Variante zu sein, den gesamten Code in main zu schreiben, von oben nach unten. Und das ist in den ersten Schritten auch wirklich in Ordnung: Das Gehirn ist mit Syntax beschaeftigt, nicht mit Architektur.

Aber ziemlich schnell tauchen zwei Symptome auf: Man kopiert die gleichen Code-Stuecke (und behebt spaeter Bugs an fuenf Stellen), und main verwandelt sich in eine riesige Textwurst, die man kaum anzufassen wagt. Funktionen sind eine Moeglichkeit, einer wiederkehrenden Aktion oder Berechnung einen Namen zu geben, damit sich der Code wie ein Skript liest: „zuerst Eingabe abgefragt, dann berechnet, dann ausgegeben“.

Stell dir vor, das Programm ist ein Rezept. main ist „was wir Schritt fuer Schritt tun“, und Funktionen sind „wie genau man Zwiebeln schneidet“ und „wie man Nudeln kocht“.

Minimale Funktion: fun name() { ... }

Zuerst schauen wir uns die einfachste Form einer Funktion an: ohne Parameter und ohne Ergebnis. In Kotlin wird eine Funktion mit dem Schluesselwort fun erstellt, dann folgt der Name und runde Klammern mit Parametern (hier noch leer), danach der Funktionskoerper in geschweiften Klammern.

Wichtig: Funktionen (genauso wie main) werden normalerweise auf Dateiebene deklariert, also nicht „innerhalb von main“. Lokale Funktionen behandeln wir spaeter; jetzt halten wir uns an den Klassiker: Funktionen separat, main separat.

fun printWelcome() {
    println("Willkommen im Programm!") // Willkommen im Programm!
}

fun main() {
    printWelcome()
    println("Wir beginnen jetzt...")      // Wir beginnen jetzt...
}

Hier ist printWelcome() einfach ein „Befehl“. Wir haben ihn aufgerufen, und er wurde ausgefuehrt. Achte auf zwei Dinge.

Erstens: Die Deklaration einer Funktion startet von sich aus nichts. Solange du printWelcome() nicht aus main aufrufst, muss sich das Programm nicht einmal daran „erinnern“, dass diese Funktion existiert.

Zweitens: Der Funktionsname ist kein Ort fuer Fantasie. Gute Namen beginnen mit einer Aktion: printWelcome, readAge, calculateTotal. Damit liest sich der Code fast wie normaler Text.

Parameter und Argumente

Fast sofort moechte man eine Funktion so gestalten, dass sie mit unterschiedlichen Daten arbeiten kann. Dafuer gibt es Parameter.

Parameter sind Variablen, die die Funktion als Eingabe erhaelt. Sie werden in runden Klammern deklariert und muessen einen Typ haben. Argumente sind konkrete Werte, die du beim Aufruf uebergibst.

Ich fasse diese Fakten in einer kleinen Tabelle zusammen:

Begriff Wo er vorkommt Beispiel Wie zu verstehen
Parameter in der Funktionsdeklaration
name: String
„was die Funktion als Eingabe erwartet“
Argument im Funktionsaufruf
"Kotlin"
„was tatsaechlich uebergeben wurde“

Jetzt ein Beispiel. Wir bauen eine Funktion, die eine Begruessung fuer einen bestimmten Benutzer ausgibt:

fun greet(name: String) {
    println("Hallo, $name!") // Hallo, Kotlin!
}

fun main() {
    greet("Kotlin")
    greet("Welt")
}

Hier ist name: String ein Parameter, und "Kotlin" sowie "Welt" sind Argumente. Beachte: Der Parameter name ist nur innerhalb des Funktionskoerpers verfuegbar. Die Funktion endet — der Parameter ist weg.

Funktionen mit mehreren Parametern

Wenn es mehrere Parameter gibt, werden sie durch Kommas getrennt. Und hier tauchen die ersten „menschlichen“ Fehler auf: Die Reihenfolge der Argumente zu vertauschen ist leichter, als man denkt (besonders wenn der Typ gleich ist, z. B. zweimal Int).

Wir bauen eine Funktion, die einen „Rahmen“ fuer eine Ueberschrift ausgibt. Es wirkt wie eine Kleinigkeit, aber solche Kleinigkeiten trainieren das Gehirn hervorragend: Du siehst sofort den Nutzen davon, „einem wiederkehrenden Code-Stueck einen Namen zu geben“.

fun printTitle(title: String, width: Int) {
    println("=".repeat(width))
    println(title)
    println("=".repeat(width))
}

fun main() {
    printTitle("Bericht", 10)
}

Wenn du aus Versehen printTitle(10, "Bericht") aufrufen wuerdest, wuerde der Compiler das Programm nicht kompilieren lassen, weil die Typen nicht passen (und das ist gut). Wenn es aber printTitle(width: Int, height: Int) waere — dann wuerde der Compiler es durchlassen, aber die Logik waere kaputt. Deshalb sind sprechende Parameternamen nicht nur Aesthetik, sondern auch Schutz vor Fehlern.

2. Rueckgabewerte und Signaturen

Rueckgabetyp: Funktion als „Ausdruck“ und nicht nur als „Aktion“

Bis hierhin haben wir Funktionen gebaut, die etwas ausgeben. Sehr oft soll eine Funktion aber berechnen und das Ergebnis zurueckgeben. Dann sagt man, die Funktion „gibt einen Wert zurueck“.

In Kotlin wird der Rueckgabetyp nach der Parameterliste mit einem Doppelpunkt angegeben: : Int, : String, : Boolean usw. Ein Beispiel aus der Kotlin-Basissyntax zeigt die Funktion sum(a: Int, b: Int): Int, die die Summe zurueckgibt.

fun sum(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    val total = sum(10, 20)
    println(total) // 30
}

Versuche, dich nicht vom Wort return abschrecken zu lassen: In der naechsten Vorlesung schauen wir uns an, wie genau eine Funktion „endet“ und was es bedeutet, „einen Wert zurueckzugeben“. Halten wir fest: Wenn bei einer Funktion ein Rueckgabetyp : Int angegeben ist, kann man das Aufrufergebnis einer Variablen zuweisen und spaeter weiterverwenden.

Wichtige Beobachtung: Ein Funktionsaufruf, der einen Wert zurueckgibt, ist ein Ausdruck. Das heisst, man kann ihn in einen String oder in eine andere Berechnung einsetzen.

fun sum(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    println("2 + 3 = ${sum(2, 3)}") // 2 + 3 = 5
}

Wir haben das Ergebnis der Funktion wortwoertlich in ein String-Template eingesetzt. Das kannst du bereits mit Variablen; jetzt lernst du es mit Funktionen.

Die Funktionssignatur als Vertrag

Wenn du eine Funktion siehst, ist es nuetzlich, ihren Kopf als Vertrag lesen zu koennen. Das hilft sehr, wenn der Code groesser wird und du nicht mehr weisst, was wo steht.

Nehmen wir ein Beispiel:

fun average(total: Int, days: Int): Double {
    return total.toDouble() / days
}

Ihre Signatur ist:

  • Name: average
  • Parameter: (total: Int, days: Int)
  • Rueckgabetyp: Double

Und das ist schon fast alles, was du brauchst, um die Funktion zu benutzen. Selbst ohne den Koerper zu lesen, kann man verstehen: „gib mir total und days, ich liefere ein Double“.

Man kann sich das als kleine Skizze vorstellen:

flowchart LR
    A["Argumente im Aufruf<br/>average(7000, 7)"] --> B["Funktionsparameter<br/>total: Int, days: Int"]
    B --> C["Funktionskoerper<br/>Berechnungen"]
    C --> D["Ergebnis des Aufrufs<br/>Double"]

Wenn du Signaturen lesen kannst, programmierst du schneller: Du springst weniger mit den Augen durch Dateien, verlaesst dich weniger auf Intuition und mehr auf den Vertrag.

Parameter innerhalb einer Funktion sind nur zum Lesen

Anfaenger haben oft den Impuls: „Ich bekomme einen Parameter, ich passe ihn jetzt an und gebe ihn zurueck.“ Kotlin erlaubt aber keine Neuzuweisung von Parametern. Das ist absichtlich so: Parameter sind Eingabedaten, keine Arbeitsvariablen. Betrachte sie wie val-Variablen. Genau so sieht sie der Kotlin-Compiler.

Das heisst, so geht es nicht (der Compiler meckert):

fun makeLouder(text: String): String {
    text = text.uppercase() // Compilerfehler: Val cannot be reassigned
    return text.uppercase()
}

Wenn du etwas veraendern musst, erstelle eine lokale Variable:

fun normalizeName(name: String): String {
    var result = name.trim()
    result = result.replaceFirstChar { it.uppercase() }
    return result
}

fun main() {
    println(normalizeName("  kotlin  ")) // Kotlin
}

Hier sind name die Eingabedaten und result der Arbeitsbereich.

3. Mini-App „Schrittzaehler“

Jetzt bauen wir ein kleines, aber in sich geschlossenes Beispiel, das dir hilft, alles Gelernte zusammenzufuehren. Es wird ein Konsolen-„Schrittzaehler fuer eine Woche“: Das Programm fragt die Schritte fuer jeden Tag ab, speichert sie in einem Array und gibt die Summe aus.

Frueher haettest du alles in main geschrieben; jetzt beginnen wir, wiederkehrende Schritte in Funktionen auszulagern. Wir halten den Code bewusst einfach, damit der Fokus auf Funktionen liegt und nicht auf cleveren Tricks.

Hilfsfunktion zum Ausgeben einer Ueberschrift

Wenn ein Programm waechst, ist ein einheitlicher Ausgabestil angenehm.

fun printSection(title: String) {
    println()
    println("=== $title ===")
}

fun main() {
    printSection("Schrittzaehler")
}

Funktion zum Einlesen einer Zahl

Eingabevalidierung machen wir spaeter; aber schon jetzt kann man „auffordern und einlesen“ in eine eigene Funktion auslagern.

fun readInt(prompt: String): Int {
    print(prompt)
    val raw = readln().trim()
    val number = raw.toIntOrNull()

    if (number == null) return 0
    return number
}

fun main() {
    val x = readInt("Gib eine Zahl ein: ")
    println("Du hast eingegeben: $x") // z.B.: Du hast eingegeben: 12
}

Wie return funktioniert, schauen wir uns etwas spaeter an; nimm das hier erst einmal als „die Funktion muss ein Int zurueckgeben, also gibt sie am Ende eine Zahl zurueck“.

Funktion zum Einlesen der Schritte pro Tag

Jetzt verpacken wir „Schritte fuer einen bestimmten Tag einlesen“ in eine Funktion mit dem Parameter dayName.

fun readStepsForDay(dayName: String): Int {
    return readInt("Schritte fuer $dayName: ")
}

fun main() {
    val mondaySteps = readStepsForDay("Montag")
    println("Montag: $mondaySteps") // z.B.: Montag: 5400
}

Beachte, wie der Code sich fast wie ein normaler Satz liest: readStepsForDay("Montag") — beinahe ein Satz.

Funktion zum Summieren ueber ein Array

Wir haben ein Array IntArray. Wir koennen es bereits per Schleife durchlaufen und eine Summe akkumulieren. Jetzt geben wir dieser Aktion einen Namen.

fun sumSteps(steps: IntArray): Int {
    var total = 0
    for (x in steps) {
        total += x
    }
    return total
}

fun main() {
    val data = intArrayOf(3000, 5000, 4500)
    println(sumSteps(data)) // 12500
}

Diese Funktion ist nicht nur im Schrittzaehler nuetzlich: Sie ist auch ein Beispiel dafuer, wie eine Funktion eine lange Schleife kapselt und das Ergebnis zurueckgibt.

Alles zu einem Szenario in main zusammensetzen

Wir bauen einen minimal funktionsfaehigen „Wochen-Tracker“ aus kleinen Funktionen. Dieser Abschnitt ist laenger als sonst — das ist normal, denn main soll vom Konzept her ein Skript sein, das ein Mensch lesen kann.

fun printSection(title: String) {
    println()
    println("=== $title ===")
}

fun readInt(prompt: String): Int {
    print(prompt)
    val raw = readln().trim()
    val number = raw.toIntOrNull()
    if (number == null) return 0
    return number
}

fun readStepsForDay(dayName: String): Int {
    return readInt("Schritte fuer $dayName: ")
}

fun sumSteps(steps: IntArray): Int {
    var total = 0
    for (x in steps) total += x
    return total
}

fun main() {
    printSection("Schrittzaehler fuer die Woche")

    val days = arrayOf("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So")
    val steps = IntArray(7)

    for (i in days.indices) {
        steps[i] = readStepsForDay(days[i])
    }

    val total = sumSteps(steps)
    println("Schritte insgesamt: $total") // z.B.: Schritte insgesamt: 42000
}

Was hier wichtig und interessant ist:

  • Jede Funktion hat einen klaren Namen und einen kleinen Umfang.
  • Parameter zeigen „was fuer die Arbeit benoetigt wird“: dayName, prompt, steps.
  • Der Rueckgabetyp zeigt „was die Funktion zurueckgibt“: Int.
  • main ist keine Sammelstelle fuer Details mehr, sondern ein Skript: „Ueberschrift anzeigen → Daten sammeln → berechnen → ausgeben“.

4. Typische Fehler beim Deklarieren und Aufrufen von Funktionen

Fehler Nr. 1: Den Parametertyp vergessen.
Anfaenger werden oft dazu verleitet, fun greet(name) { ... } zu schreiben, wie in manchen Sprachen. Kotlin kann das nicht: Ein Parametertyp ist Pflicht. Richtig ist fun greet(name: String) { ... }. Das nervt zuerst, rettet dich aber spaeter vor sehr merkwuerdigen Bugs, wenn „das Falsche uebergeben und am falschen Ort gelandet ist“.

Fehler Nr. 2: Parameter und Argumente verwechselt (und deshalb in Erklaerungen durcheinandergeraet).
Das klingt nach Terminologie um der Terminologie willen, hilft aber in der Praxis. Parameter sind das, was in der Funktion deklariert ist; Argumente sind das, was du beim Aufruf tatsaechlich uebergeben hast. Wenn du alles „Parameter“ nennst, kannst du dir irgendwann selbst nicht mehr erklaeren, was genau nicht gepasst hat.

Fehler Nr. 3: Versuch, einen Parameter innerhalb der Funktion zu veraendern.
Nach Erfahrungen mit var moechte man den „Eingabewert“ direkt im Parameter „nachregeln“. Kotlin erlaubt aber keine Neuzuweisung von Parametern: Sie verhalten sich wie val. Wenn du etwas aendern musst — erstelle ein lokales var und aendere dieses. Der Code wird etwas laenger, aber um ein Vielfaches verstaendlicher.

Fehler Nr. 4: Eine Funktion macht zu viele Dinge auf einmal.
Wenn man gerade erst anfaengt, wirkt es praktisch, fun doAll() zu schreiben — „Eingabe, Berechnung und Ausgabe“. Dann laesst sich die Funktion aber nicht wiederverwenden: Du wolltest „nur berechnen“, sie druckt aber auch; du wolltest „nur abfragen“, sie greift schon ins Array. Eine gute Startregel: Eine Funktion — ein klarer Schritt im Skript oder eine Berechnung.

Fehler Nr. 5: Namen wie f1, calc, doIt, weil „das ist doch klar“.
Solange das Programm 20 Zeilen hat — ja, „das ist klar“. Wenn es 200 sind — nicht mehr. Der Funktionsname sollte die Frage beantworten: „Was macht sie?“ oder „Was gibt sie zurueck?“. sumSteps ist ohne Kommentare lesbar, calc2 dagegen klingt wie ein Raetsel aus einem Escape Room.

1
Aufgabe
Kotlin SELF, Level 8, Lektion 1
Gesperrt
Konsolen-Trennlinien
Konsolen-Trennlinien
1
Aufgabe
Kotlin SELF, Level 8, Lektion 1
Gesperrt
Willkommensbildschirm
Willkommensbildschirm
1
Aufgabe
Kotlin SELF, Level 8, Lektion 1
Gesperrt
Raumplan
Raumplan
1
Aufgabe
Kotlin SELF, Level 8, Lektion 1
Gesperrt
Kalenderfilter
Kalenderfilter
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION