Funktionen

Die zweite Säule einer Programmiersprache neben den Datentypen sind Funktionen (oder auch Methoden genannt). Strenggenommen gehören Funktionen auch zu den Datentypen, weil sie sich wie alle anderen Objekte in Variablen speichern lassen und als Parameter oder Rückgabewerte verwenden lassen. Da Funktionen aber eine so herausragende Rolle haben, bekommen sie einen extra Abschnitt.

Einige Funktionen haben wir bisher schon kennengelernt: main(), Println() und Printf(), die letzten beiden aus dem Paket fmt. Alle Funktionsdefinitionen haben denselben Aufbau:

func funktionsname() {
    // Hier kommen die Anweisungen
}

und der Aufruf einer Funktion wird auch immer nach demselben Prinzip gemacht: funktionsname().

Einfache Funktionen

Funktionen können Parameter erwarten und zurückgeben. In diesem Beispiel sollte das hoffentlich klar werden:

package main

import "fmt"

func addiere(a int, b int) int {
    var summe int
    summe = a + b
    return summe
}

func main() {
    ergebnis := addiere(17, 25)
    fmt.Println("Das Ergebnis ist", ergebnis)
}

Hier wird eine Funktion mit dem Namen addiere definiert, die zwei Werte erwartet (a und b, beide vom Typ int) und einen Wert vom Typ int zurückgibt (das ist die Angabe zwischen der schließenden runden Klammer und der öffnenden geschweiften Klammer in der ersten Zeile der Funktionsdefinition). Da die Funktion einen Wert zurück gibt, muss sie mit der Anweisung return abschließen. Der Rückgabewert muss von dem Typ sein, der in der Funktionsdefinition deklariert wurde.

Die Funktion wird aufgerufen mit den beiden Parametern 17 und 25, die in den Variablen a und b gespeichert werden. Auch hier müssen die Typen übereinstimmen, sonst gibt der Compiler eine Fehlermeldung (Welche? Probiere es aus!).

Hier sei noch ein kleiner Hinweis angebracht. Die Funktion addiere() kann man auch kürzer und meines Erachtens lesbarer schreiben. Die beiden Zeilen

var summe int
summe = a + b

kann man zu einer Zeile zusammenfassen: summe := a + b durch die abkürzende Variablendeklaration. Außerdem kann man sich die Variable summe vollständig sparen, weil die return-Anweisung auch wie folgt schreiben kann:

func addiere(a int, b int) int {
    return a + b
}

Funktionen können auch mehrere Rückgabewerte haben:

package main

import "fmt"

func divisionMitRest(dividend, divisor int) (int, int) {
    quotient := dividend / divisor
    rest := dividend % divisor
    return quotient, rest
}
func main() {
    q, r := divisionMitRest(5, 2)
    fmt.Println("Das Ergebnis ist", q, "mit Rest", r)
}

Hier ist, wie in vielen anderen Programmiersprachen auch, % der Modulo Operator, der den Rest einer Division bestimmt. Der Schrägstrich / teilt neben Integer-Werten auch andere Zahlenwerte wie Gleitkommazahlen, beide Typen müssen aber dieselben sein.

Rückgabewerte mit Namen

Auch hier ist eine kleine Verbesserung möglich: wenn wir uns die Funktionsdefinition anschauen, ist nicht auf den ersten Blick ersichtlich, wofür die beiden Rückgabewerte stehen. Beide sind vom Typ int, aber welche ist nun der Quotient und welche ist der Rest? Das ist besonders dann wichtig, wenn wir später nur noch die Funktionsdefinition sehen (z.B. mit dem Tool godoc) und nicht den Funktionskörper. Wir können die Rückgabewerte benennen:

func divisionMitRest(dividend, divisor int) (quotient int, rest int) {
    quotient = dividend / divisor
    rest = dividend % divisor
    return
}

Zu beachten ist, dass die Variablen quotient und rest schon deklariert sind (als Typ int) und im Funktionskörper nicht erneut definiert werden dürfen (mit := wie in dem Beispiel oben). Dadurch würden nämlich die Variablen quotient und rest von der neuen Definition verdeckt.

Da ja schon in der Funktionsdeklaration beschrieben werden, welche Variablen zurückgegeben werden, brauchen wir die nicht mehr bei der return-Anweisung zu benennen.

Die Funktionsdefinition ist vom Prinzip her identisch mit der vorherigen. Da wir aber die Rückgabewerte mit sinnvollen Namen versehen haben, ist sie für den Anwender leichter zu verstehen. Man muss nicht mehr die Funktion selbst durchlesen und verstehen, um die Rückgabewerte zuzuordnen.

Funktionen mit mehreren Parametern

package main

import (
    "fmt"
)

func beispiel(name ...string) {
    for i, v := range name {
        fmt.Println(i, v)
    }
}

func main() {
    beispiel("Hallo", "schöne", "Welt")
}

Funktionen mit Zeigern (pointer)

Wichtig ist zu wissen, dass die Parameter bei einem Funktionsaufruf immer als Kopie übergeben werden. Probiere dieses Beispiel mal aus:

package main

import "fmt"

// Macht nicht das, was man denken könnte!!
func erhöheUmEins(wert int) {
    wert++
}

func main() {
    zahl := 7
    erhöheUmEins(zahl)
    fmt.Println("Das Ergebnis ist", zahl)
}

Die Anweisung wert++ ist dasselbe wie wert = wert + 1 oder abgekürzt wert += 1. Bei dem Funktionsaufruf wird eine Kopie des Inhalts der Variable zahl übergeben (also der Wert 7) und die Funktion hat keine Kenntnis von der Variable aus dem Aufruf und kann sie auch nicht verändern.

Die Lösung für das Problem ist, dass man eben keine Kopien der Werte macht sondern nur die Speicherstellen der Werte übergibt:

package main

import (
	"fmt"
)

type koordinate struct {
	x int
	y int
}

// Es wird nur der Zeiger auf die Koordinate übergeben,
// damit kann der Inhalt verändert werden
func verdoppelnZeiger(k *koordinate) {
	k.x = k.x * 2
	k.y = k.y * 2
}

// Es wird eine Kopie erzeugt und verändert.
// Das Original wird nicht verändert.
func verdoppeln(k koordinate) {
	k.x = k.x * 2
	k.y = k.y * 2
}

func main() {
	// Es werden zwei Koordinaten erzeugt, eine »normal«,
	// eine als Zeiger (pointer)
	p1 := koordinate{1, 2}
	p2 := &koordinate{3, 4}
	fmt.Printf("%p %p\n", &p1, p2)
	fmt.Printf("Neu: (%d,%d),(%d,%d)\n", p1.x, p1.y, p2.x, p2.y)
	verdoppeln(p1)
	verdoppeln(*p2)
	fmt.Printf("verdoppeln: (%d,%d),(%d,%d)\n", p1.x, p1.y, p2.x, p2.y)
	verdoppelnZeiger(&p1)
	verdoppelnZeiger(p2)
	fmt.Printf("verdoppelnZeiger: (%d,%d),(%d,%d)\n", p1.x, p1.y, p2.x, p2.y)
}
Technisch gesehen werden bei Funktionsaufrufen mit Zeigern auch Kopien gemacht, aber eben nur Kopien der Zeiger. Letztendlich ist ein Zeiger eben auch ein normaler Datentyp, der sich wie die anderen Datentypen verhält.