Tracks
/
Go
Go
/
Übungen
/
Lasagne-Meister
Lasagne-Meister

Lasagne-Meister

Lernübung

Einführung

Mit einer Funktion kannst du Code zu einer wiederverwendbaren Einheit zusammenfassen. Sie besteht aus dem Schlüsselwort func, dem Namen der Funktion und einer durch Kommas getrennten Liste von null oder mehr Parametern und Typen in runden Klammern.

Funktionsparameter

Alle Parameter müssen explizit typisiert werden; für Parameter gibt es keine Typinferenz. Es gibt keine Standardwerte für Parameter, deshalb sind alle Funktionsparameter erforderlich.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// Two parameters
func PrintGreetingName(greeting string, name string) {
  fmt.Println(greeting + " " + name)
}

Parameter desselben Typs kannst du gemeinsam angeben, gefolgt von einer einzigen Typangabe.

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

Parameter vs. Argumente

Schauen wir uns kurz zwei Begriffe an, die oft verwechselt werden: parameters und arguments. Funktionsparameter sind die Namen, die in der Signatur der Funktion definiert sind, wie greeting und name in der Funktion PrintGreetingName oben. Funktionsargumente sind die konkreten Werte, die an die Funktionsparameter übergeben werden, wenn wir die Funktion aufrufen. Im folgenden Beispiel sind "Hello" und "Katrina" zum Beispiel die Argumente, die an die Parameter greeting und name übergeben werden:

PrintGreetingName("Hello", "Katrina")

Rückgabewerte

Auf die Funktionsparameter folgen null oder mehr Rückgabewerte, die ebenfalls explizit typisiert werden müssen. Einzelne Rückgabewerte stehen ohne Klammern, mehrere Rückgabewerte werden in runde Klammern gesetzt. Werte werden mit dem Schlüsselwort return an den aufrufenden Code zurückgegeben. Eine Funktion kann mehrere return-Anweisungen enthalten. Die Ausführung der Funktion endet, sobald sie auf eine dieser return-Anweisungen trifft. Wenn eine Funktion mehrere Werte zurückgeben soll, werden sie durch Kommas getrennt.

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

Funktionen aufrufen

Um eine Funktion aufzurufen, gibst du den Funktionsnamen an und übergibst in runden Klammern Argumente für jeden Parameter der Funktion.

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

Benannte Rückgabewerte und nacktes return

Rückgabewerte kannst du, genau wie Parameter, optional benennen. Wenn benannte Rückgabewerte verwendet werden, gibt eine return-Anweisung ohne Argumente diese Werte zurück. Man nennt das ein „nacktes“ return.

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

Wertübergabe vs. Referenzübergabe

Es ist auch wichtig, das Konzept der Wertübergabe und der Referenzübergabe zu klären.

Klären wir zuerst die Wertübergabe. Wenn wir im folgenden Beispiel die Variable val an die Funktion MultiplyByTwo übergeben, übergeben wir eine Kopie von val. Deshalb hat newVal den aktualisierten Wert 4, aber die ursprüngliche Variable val ist immer noch 2. Im Hintergrund erstellt Go im Wesentlichen eine Kopie des ursprünglichen Werts, sodass die Funktion nur diese Kopie, also v, verändert.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

Streng genommen werden in Go alle Argumente als Wert übergeben, das heißt, es wird eine Kopie des Werts oder der Daten erstellt, die an die Funktion übergeben werden. Wenn du aber keine Kopie der Daten erstellen möchtest, die an eine Funktion übergeben werden, und die Daten in der Funktion ändern willst, dann solltest du pointers als Argumente verwenden, also die Referenzübergabe.

Pointer

Wir verwenden einen pointer, um die Referenzübergabe zu erreichen. Indem wir pointer-Argumente an eine Funktion übergeben, können wir die zugrunde liegenden Daten verändern, die an die Funktion übergeben wurden, statt nur mit einer Kopie der Daten zu arbeiten.

Für den Moment reicht es zu wissen, dass Pointer-Typen am * vor dem Typ in der Funktionssignatur zu erkennen sind.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

Wenn dir das Konzept pointer schwerfällt, keine Sorge. Später im Lehrplan gibt es einen eigenen Abschnitt, der dir hilft, Pointer zu meistern.

Ausnahmen

Beachte, dass slices und maps Ausnahmen von der oben genannten Regel sind. Wenn wir einen slice oder eine map als Argumente an eine Funktion übergeben, werden sie wie Pointer-Typen behandelt, obwohl im Typ kein explizites * steht. Das bedeutet: Wenn wir einen Slice oder eine Map an eine Funktion übergeben und ihre zugrunde liegenden Daten ändern, wirken sich die Änderungen auf den ursprünglichen Slice bzw. die ursprüngliche Map aus.

Anleitung

In dieser Übung schreibst du noch etwas mehr Code rund um die Vorbereitung und das Kochen deiner großartigen Lasagne aus deinem Lieblingskochbuch.

Du hast vier Aufgaben. Die erste dreht sich ums Kochen selbst, die anderen drei um die perfekte Vorbereitung.

1. Schätze die Vorbereitungszeit ein

Für die nächste Lasagne, die du zubereiten wirst, möchtest du sicherstellen, dass du genug Zeit eingeplant hast, damit du das Kochen genießen kannst. Du hast bereits geplant, welche Schichten deine Lasagne haben wird. Jetzt möchtest du auf dieser Grundlage einschätzen, wie lange die Vorbereitung dauern wird.

Implementiere eine Funktion PreparationTime, die einen Slice von Schichten als []string und die durchschnittliche Vorbereitungszeit pro Schicht in Minuten als int entgegennimmt. Die Funktion sollte die Schätzung für die gesamte Vorbereitungszeit basierend auf der Anzahl der Schichten als int zurückgeben. Go hat keine Standardwerte für Funktionen. Wenn die durchschnittliche Vorbereitungszeit als 0 übergeben wird (der Standardinitialwert für ein int), dann sollte der Standardwert 2 verwendet werden.

layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12

2. Berechne die benötigten Mengen an Nudeln und Soße

Neben der Zeit möchtest du auch sicherstellen, dass du genug Soße und Nudeln hast, um die Lasagne deiner Träume zu kochen. Für jede Nudelschicht in deiner Lasagne brauchst du 50 Gramm Nudeln. Für jede Soßenschicht in deiner Lasagne brauchst du 0,2 Liter Soße.

Definiere die Funktion Quantities, die einen Slice von Schichten als Parameter vom Typ []string entgegennimmt. Die Funktion ermittelt dann die Menge an Nudeln und Soße, die du für dein Essen brauchst. Das Ergebnis sollte als zwei Werte zurückgegeben werden: noodles als int und sauce als float64.

Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4

3. Füge die geheime Zutat hinzu

Vor einer Weile hast du einen Freund besucht und dort Lasagne gegessen. Sie war fantastisch und hatte etwas Besonderes an sich. Der Freund hat dir die Zutatenliste geschickt und gesagt, dass der letzte Eintrag auf der Liste die „geheime Zutat“ ist, die das Essen so besonders gemacht hat. Jetzt möchtest du diese geheime Zutat auch zu deinem Rezept hinzufügen.

Schreibe eine Funktion AddSecretIngredient, die zwei Slices von Zutaten vom Typ []string als Parameter entgegennimmt. Der erste Parameter ist die Liste, die dir dein Freund geschickt hat, der zweite ist die Zutatenliste deines eigenen Rezepts. Das letzte Element in deiner Zutatenliste ist immer "?". Die Funktion sollte es durch den letzten Eintrag aus der Liste deines Freundes ersetzen. Hinweis: AddSecretIngredient gibt nichts zurück, du solltest die Liste deiner Zutaten direkt ändern. Die Liste mit den Zutaten deines Freundes sollte nicht geändert werden. Außerdem wird slice als Pointer an eine Funktion übergeben, daher werden Änderungen an den beiden []string-Argumenten, die an AddSecretIngredient übergeben werden, direkt übernommen.

friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}

AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. Skaliere das Rezept

Die Mengen in deinem Kochbuch ergeben nur genug Lasagne für zwei Portionen. Da du nächstes Mal für mehr Personen kochen möchtest, willst du die Mengen für unterschiedliche Portionszahlen berechnen.

Implementiere eine Funktion ScaleRecipe, die zwei Parameter entgegennimmt.

  • Ein Slice von float64-Mengen, die für 2 Portionen benötigt werden.
  • Die Anzahl der Portionen, die du kochen möchtest.

Die Funktion sollte ein Slice von float64 mit den Mengen zurückgeben, die für die gewünschte Anzahl an Portionen benötigt werden. Das ursprüngliche Rezept möchtest du aber beibehalten. Das bedeutet, dass das Argument quantities in dieser Funktion nicht geändert werden sollte.

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Go Exercism

Bereit, mit Lasagne-Meister zu starten?

Melde dich bei Exercism an, um Go mit 34 Konzepte166 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.