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.
Aufgabe
EinfachLassen 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 TypparameterTsowohl mitintals auch mitfloat64Typen 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 (
intoderfloat), lesen Sie dann eine Anzahl gefolgt von entsprechend vielen Werten. Parsen Sie die Werte entsprechend dem Typ, rufen Sie sowohlMaxals auchSummit 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 (
intoderfloat) - Zeile 2: Anzahl der Werte (Ganzzahl)
- Folgende Zeilen: Ein Wert pro Zeile
Zum Beispiel bei folgender Eingabe:
int
4
15
8
23
11Sollte Ihre Ausgabe wie folgt aussehen:
Max (int): 23
Sum (int): 57Und bei folgender Eingabe:
float
3
4.5
9.2
6.8Sollte Ihre Ausgabe wie folgt aussehen:
Max (float): 9.2
Sum (float): 20.5Beachten 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 float64Der 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
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool