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)
}
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: