Menu
Coddy logo textTech

Einführung in Generics

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 67 von 107.

Vor Go 1.18 bedeutete das Schreiben von wiederverwendbarem Code für verschiedene Typen entweder das Duplizieren von Funktionen oder die Verwendung des leeren Interfaces any mit Typzusicherungen (Type Assertions). Generics lösen dieses Problem, indem sie es ermöglichen, Funktionen und Typen zu schreiben, die mit mehreren Typen funktionieren, während die volle Typsicherheit erhalten bleibt.

Stellen Sie sich vor, Sie möchten den Minimalwert in einem Slice finden. Ohne Generics bräuchten Sie separate Funktionen für jeden Typ:

func MinInt(values []int) int {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

func MinFloat64(values []float64) float64 {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

Mit Generics schreiben Sie die Logik einmal unter Verwendung eines Typparameters in eckigen Klammern:

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

Das [T int | float64] deklariert einen Typparameter T, der entweder int oder float64 sein kann. Jetzt können Sie Min([]int{3, 1, 2}) oder Min([]float64{3.5, 1.2}) mit derselben Funktion aufrufen. Der Compiler prüft die Typen zur Kompilierzeit, sodass Sie Sicherheit ohne Laufzeit-Overhead erhalten.

In den kommenden Lektionen werden wir Typparameter, Constraints und generische Structs im Detail untersuchen.

challenge icon

Aufgabe

Einfach

Lassen Sie uns einen Score-Analysator bauen, der mithilfe von Generics sowohl mit ganzzahligen als auch mit Fließkomma-Scores arbeitet! Sie werden eine einzige generische Funktion erstellen, die den Maximalwert in einem Slice finden kann, wodurch doppelter Code für verschiedene numerische Typen vermieden wird.

Sie werden Ihren Code auf zwei Dateien aufteilen:

  • analyzer.go: Definieren Sie Ihre generische Analysefunktion.

    Erstellen Sie eine generische Funktion Max[T int | float64](values []T) T, die den Maximalwert in einem Slice findet und zurückgibt. Die Funktion sollte über den Typparameter T sowohl mit int als auch mit float64 Typen funktionieren.

    Erstellen Sie außerdem eine generische Funktion Sum[T int | float64](values []T) T, die die Summe aller Werte in einem Slice berechnet und zurückgibt.

  • main.go: Lesen Sie die Eingabe ein und demonstrieren Sie Ihre generischen Funktionen mit verschiedenen Typen.

    Lesen Sie einen Typindikator (int oder float), lesen Sie dann eine Anzahl gefolgt von entsprechend vielen Werten. Parsen Sie die Werte entsprechend dem Typ, rufen Sie sowohl Max als auch Sum mit dem entsprechenden Typ auf und geben Sie die Ergebnisse aus.

    Geben Sie für Ganzzahl-Eingaben (integer) Folgendes aus:

    Max (int): [value]
    Sum (int): [value]

    Geben Sie für Fließkomma-Eingaben (float) das Ergebnis mit einer Dezimalstelle aus:

    Max (float): [value]
    Sum (float): [value]

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Typindikator (int oder float)
  • Zeile 2: Anzahl der Werte (Ganzzahl)
  • Folgende Zeilen: Ein Wert pro Zeile

Zum Beispiel bei folgender Eingabe:

int
4
15
8
23
11

Sollte Ihre Ausgabe wie folgt aussehen:

Max (int): 23
Sum (int): 57

Und bei folgender Eingabe:

float
3
4.5
9.2
6.8

Sollte Ihre Ausgabe wie folgt aussehen:

Max (float): 9.2
Sum (float): 20.5

Beachten Sie, wie dieselben generischen Funktionen sowohl Ganzzahl- als auch Fließkommadaten verarbeiten – der Typparameter [T int | float64] ermöglicht es dem Compiler, typsicheren Code für jede Verwendung zu generieren, während Sie die Logik nur einmal schreiben.

Spickzettel

Generics ermöglichen es Ihnen, wiederverwendbaren Code zu schreiben, der mit mehreren Typen funktioniert und gleichzeitig die Typsicherheit gewährleistet. Vor Go 1.18 benötigten Sie separate Funktionen für jeden Typ oder verwendeten das leere Interface mit Typzusicherungen (Type Assertions).

Typparameter werden in eckigen Klammern deklariert und geben an, welche Typen eine generische Funktion akzeptieren kann:

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

Die Syntax [T int | float64] deklariert einen Typparameter T, der entweder int oder float64 sein kann. Der Pipe-Operator | listet die zulässigen Typen auf.

Sie können generische Funktionen mit verschiedenen Typen aufrufen:

Min([]int{3, 1, 2})           // Funktioniert mit int
Min([]float64{3.5, 1.2})      // Funktioniert mit float64

Der Compiler führt die Typprüfung zur Kompilierzeit durch und bietet so Sicherheit ohne Laufzeit-Overhead.

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Typ-Indikator lesen
	typeStr, _ := reader.ReadString('\n')
	typeStr = strings.TrimSpace(typeStr)

	// Anzahl lesen
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	if typeStr == "int" {
		// Ganzzahlwerte lesen
		values := make([]int, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.Atoi(strings.TrimSpace(line))
		}

		// TODO: Max und Sum mit integer Slice aufrufen
		// TODO: Ergebnisse im Format "Max (int): [value]" und "Sum (int): [value]" ausgeben

	} else if typeStr == "float" {
		// Fließkommawerte lesen
		values := make([]float64, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
		}

		// TODO: Max und Sum mit float64 Slice aufrufen
		// TODO: Ergebnisse mit einer Nachkommastelle im Format "Max (float): [value]" und "Sum (float): [value]" ausgeben
	}
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung