Go 4: Funktionen definieren und aufrufen

Funktionen werden in Go mit dem Schlüsselwort func (Spec) definiert.

Eine mathematische Funktion wie y=f(x)=2x kann in Go folgendermassen implementiert werden:

func f(x int) int {
    y := 2 * x
    return y
}

Die Funktion f nimmt einen Parameter namens x vom Typ int entgegen und gibt einen Wert vom Typ int zurück.

Diese Funktion kann folgendermassen aufgerufen werden:

y := f(2)
fmt.Println(y) // gibt "4" aus

Funktionen ohne Parameter und ohne Rückgabewert

Die Funktion main() ist ein Spezialfall: Sie wird automatisch beim Programmstart aufgerufen. Sie hat weder Parameter noch einen Rückgabewert:

func main() {
    fmt.Println("Hello, World!")
}

Man kann auch eigene Funktionen ohne Parameter und ohne Rückgabewert definieren:

func sayHello() {
    fmt.Println("Hello")
}
sayHello()

Ausgabe:

Hello

Funktionen mit Parameter und ohne Rückgabewert

Eine Funktion kann auch Parameter erwarten und keinen Rückgabewert haben:

func sayHelloTo(whom string) {
	fmt.Println("Hello,", whom)
}
sayHelloTo("Alice")

Hier wird der Parameter whom vom Typ string erwartet, um die jeweilige Person zu begrüssen.

Ausgabe:

Hello, Alice

Funktionen mit mehreren Parametern und ohne Rückgabewert

Eine Funktion kann beliebig viele Parameter erwarten. Die Parameter werden durch Kommas voneinander getrennt:

func outputCurrency(amount float32, currency rune) {
	fmt.Printf("%8.2f %c\n", amount, currency)
}
outputCurrency(2.5, '$')
outputCurrency(10.0/3.0, '€')
outputCurrency(1234.567, '¥')

Hier wird ein Betrag (amount) und ein Währungssymbol (currency) erwartet, um einen Geldbetrag auf zwei Nachkommastellen formatiert auszugeben.

Ausgabe:

    2.50 $
    3.33 €
 1234.57 ¥

Funktionen mit mehreren Parametern und Rückgabewert

Die folgende Funktion ist fast identisch zur vorherigen, gibt aber den formatierten String nicht auf die Konsole aus, sondern als String zurück. (Die Funktion hat den Rückgabetyp string):

func formatCurrency(amount float32, currency rune) string {
	return fmt.Sprintf("%8.2f %c", amount, currency)
}
dollars := formatCurrency(2.5, '$')
euros := formatCurrency(10.0/3.0, '€')
fmt.Println(dollars)
fmt.Println(euros)

Die Ausgabe mit fmt.Println wurde hier eigens ausprogrammiert, um die Rückgabewerte auszugeben.

Ausgabe:

	2.50 $
	3.33 €

Funktionen ohne Parameter mit Rückgabewert

Die folgende Funktion generiert eine Zufallszahl zwischen 1 und 6, womit das Würfeln simuliert werden soll:

func rollDice() int {
    return rand.Intn(6) + 1
}
rand.Seed(time.Now().Unix)
fmt.Println(rollDice())
fmt.Println(rollDice())
fmt.Println(rollDice())

Mithilfe von rand.Seed wird der Zufallszahlengenerator mit dem jeweils aktuellen Timestamp (time.Now().Unix) initialisiert, sodass die Ausgabe zufällig und nicht deterministisch wird:

1
2
5

Funktionen mit Parametern und mehreren Rückgabewerten

In vielen Programmiersprachen können Funktionen nur einen einzelnen Wert zurückgeben. (Natürlich können auch Datenstrukturen bestehend aus mehreren Werten zurückgegeben werden, aber eben nicht mehrere eigenständige Werte.)

Eine Funktion, die mehrere Werte zurückgibt, hat eine Liste von Rückgabetypen (in runden Klammern). Die folgende Funktion dividiert den gegebenen Dividenden (dividend) durch den gegebenen Divisor (divisor).

Bei der Division gibt es einen Sonderfall: Die Division durch 0 (bzw. durch 0.0) ist nicht erlaubt. In diesem Fall soll auch ein Fehler (error, Spec) zurückgegeben werden. Bei einer regulären Division wird ein Ergebnis und kein Fehler (bzw. nil) zurückgegeben:

func divide(dividend, divisor float32) (float32, error) {
    if divisor == 0.0 {
        return 0.0, errors.New("divide by 0")
    }
    return dividend / divisor, nil
}
fmt.Println(divide(10.0, 3.0))
fmt.Println(divide(10.0, 0.0))

Fehler haben den Typ error und können mit der Funktion errors.New erzeugt werden, indem man einen String mitgibt, der den Fehler beschreibt.

Ausgabe:

3.3333333 <nil>
0 divide by 0

Fehlerbehandlung

Die Funktion computeAverage berechnet den Durchschnitt des gegebenen Slices values. Ein Durchschnitt kann aber nur berechnet werden, wenn effektiv Werte da sind. Bei einem leeren Slice soll darum ein error zurückgegeben werden:

func computeAverage(values []float32) (float32, error) {
	if len(values) == 0 {
		return 0.0, fmt.Errorf("cannot compute average of %v", values)
	}
	var sum float32
	for _, value := range values {
		sum += float32(value)
	}
	return sum / float32(len(values)), nil
}

Hier wird der Fehler mithilfe der Funktion fmt.Errorf erzeugt, was analog zu fmt.Printf erfolgt, aber einen Wert vom Typ error zurückgibt.

Wird eine Funktion aufgerufen, die möglicherweise einen Fehler zurückgibt, muss der Aufrufer diesen behandeln. Wird kein Fehler zurückgegeben (err == nil), ist kein Fehler passiert, und das Ergebnis (average) kann verwendet werden. Wird hingegen ein Fehler zurückgegeben (err == nil), muss darauf reagiert werden. Das Ergebnis (average) wird dann keinen sinnvollen Wert haben und muss ignoriert werden:

grades := makeRandomGrades() // returns 0..2 grades
average, err := computeAverage(grades)
if err != nil {
	fmt.Fprintf(os.Stderr, "compute average of %v: %v\n", grades, err)
} else {
	fmt.Printf("the average of %v is %.2f\n", grades, average)
}

Ausgabe:

compute average of []: cannot compute average of []
the average of [2.41 5.07] is 3.74

Methoden

Handelt es sich bei Go um eine objektorientierte Programmiersprache?

Question: Is Go an object-oriented language?

Answer: Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. […]

Volle Antwort: Go FAQ

Go hat keine Klassen und Vererbung, Funktionen können aber als Methoden (Spec) implementiert werden.

Funktionen, die auf Typen arbeiten

Betrachten wir den Typ Celsius (ein Alias für float32):

type Celsius float32

Für diesen wird eine Funktion outputCelsius geschrieben. Die Funktion erwartet einen Wert vom Typ Celsius und gibt ihn formatiert aus:

func outputCelsius(c Celsius) {
	fmt.Printf("%.2f°C\n", c)
}

var coldest Celsius = -273.15
var warm Celsius = 32.5

outputCelsius(coldest) // -273.15°C
outputCelsius(warm) // 32.50°C

Funktionen als Methoden implementieren

Da die Funktion outputCelsius nur im Zusammenhang mit dem Typ Celsius verwendet werden kann, wäre es sinnvoll, wenn man die Funktion mit dem Typ verbinden könnte.

Go bietet diese Möglichkeit, indem Funktionen an genau einen Typen “angehängt” werden können:

type Celsius float32

func (c Celsius) Output() {
	fmt.Printf("%.2f°C\n", c)
}

Der Parameter c wird neu als receiver argument dem Funktionsnamen vorangestellt. Die Funktion kann neu folgendermassen als Methode aufgerufen werden:

var coldest Celsius = -273.15
var warm Celsius = 32.5

coldest.Output() // -273.15°C
warm.Output() // 32.50°C