Generische Structs
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 70 von 107.
Genau wie Funktionen können auch Structs Typparameter haben. Ein generisches Struct ermöglicht es Ihnen, Datenstrukturen zu erstellen, die mit jedem Typ funktionieren und gleichzeitig die Typsicherheit gewährleisten. Dies ist ideal für den Aufbau wiederverwendbarer Container wie Stacks, Queues oder Key-Value-Paare.
Um ein generisches Struct zu definieren, platzieren Sie den Typparameter nach dem Namen des Structs:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // hello
}Im Gegensatz zu generischen Funktionen müssen Sie den Typ explizit angeben, wenn Sie eine Instanz einer generischen Struktur (Struct) erstellen. Go kann den Typ nicht allein aus dem Kontext ableiten.
Generische Structs können mehrere Typparameter haben, was sie ideal für Strukturen wie Paare oder Maps macht:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25Sie können auch Einschränkungen anwenden, um sicherzustellen, dass die gespeicherten Typen bestimmte Operationen unterstützen:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Generische Structs bilden die Grundlage für den Aufbau typsicherer, wiederverwendbarer Datenstrukturen in Go. In der nächsten Lektion werden Sie lernen, wie Sie Methoden für diese generischen Typen definieren.
Aufgabe
EinfachLass uns ein generisches Inventarsystem bauen, das verschiedene Arten von Artikeln verfolgen kann! Du wirst wiederverwendbare Container-Strukturen erstellen, die mit jedem Typ funktionieren und dabei die volle Typsicherheit gewährleisten.
Du wirst deinen Code auf zwei Dateien aufteilen:
inventory.go: Definiere deine generischen Container-Strukturen.Erstelle ein generisches Struct
Item[T any]mit zwei Feldern:Name(string) undData(vom Typ T). Dies repräsentiert einen beliebigen Artikel mit zugehörigen Daten eines flexiblen Typs.Erstelle ein generisches Struct
Container[T any]mit einem einzigen FeldItems, das ein Slice vonItem[T]enthält. Dieser Container kann mehrere Artikel desselben Datentyps speichern.Erstelle ein eingeschränktes generisches Struct
PricedItem[T int | float64]mit drei Feldern:Name(string),Quantity(int) undPrice(vom Typ T). Die Einschränkung stellt sicher, dass Preise immer numerisch sind.Implementiere eine Funktion
NewContainer[T any]() *Container[T], die einen Pointer auf einen leeren Container erstellt und zurückgibt.Implementiere eine Methode
Addfür*Container[T], die einen Namen (string) und Daten (T) entgegennimmt, ein Item erstellt und es an das Items-Slice des Containers anhängt.Implementiere eine Methode
CountfürContainer[T], die die Anzahl der Artikel im Container zurückgibt.main.go: Demonstriere deine generischen Strukturen mit verschiedenen Typen.Lies einen Artikeltyp ein (
string,intoderpriced), lies dann eine Anzahl gefolgt von den Artikeldetails ein.Für den Typ
string: Lies Paare aus Name und String-Daten. Erstelle einenContainer[string], füge alle Artikel hinzu und gib dann jeden Artikel als[Name]: [Data]aus, gefolgt von der Gesamtzahl.Für den Typ
int: Lies Paare aus Name und Integer-Daten. Erstelle einenContainer[int], füge alle Artikel hinzu und gib dann jeden Artikel als[Name]: [Data]aus, gefolgt von der Gesamtzahl.Für den Typ
priced: Lies Tripletts aus Name, Menge und Preis (als float). ErstellePricedItem[float64]-Instanzen direkt und gib jede als[Name] x[Quantity] @ [Price]aus, wobei der Preis mit einer Dezimalstelle angezeigt wird.Gib die Zeile mit der Anzahl wie folgt aus:
Total items: [count]
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Artikeltyp (
string,intoderpriced) - Zeile 2: Anzahl der Artikel (Integer)
- Folgende Zeilen: Artikeldetails basierend auf dem Typ
Zum Beispiel, gegeben:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined PaperDeine Ausgabe sollte sein:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3Und gegeben:
int
2
Apples
50
Oranges
30Deine Ausgabe sollte sein:
Apples: 50
Oranges: 30
Total items: 2Und gegeben:
priced
2
Widget
10
19.99
Gadget
5
49.50Deine Ausgabe sollte sein:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2Beachte, wie dieselbe Container-Struktur nahtlos mit sowohl Strings als auch Integern funktioniert, während PricedItem eine Einschränkung verwendet, um sicherzustellen, dass nur numerische Typen für Preise verwendet werden können. Du musst den Typparameter explizit angeben, wenn du Instanzen dieser generischen Structs erstellst.
Spickzettel
Eine generische Struktur ermöglicht es Ihnen, Datenstrukturen zu erstellen, die mit jedem Typ funktionieren, während die Typsicherheit gewahrt bleibt.
Um eine generische Struktur zu definieren, platzieren Sie den Typparameter nach dem Namen der Struktur:
type Box[T any] struct {
Value T
}Wenn Sie eine Instanz einer generischen Struktur erstellen, müssen Sie den Typ explizit angeben (Go kann diesen nicht ableiten):
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}Generische Strukturen können mehrere Typparameter haben:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}Sie können Constraints (Einschränkungen) anwenden, um sicherzustellen, dass die gespeicherten Typen bestimmte Operationen unterstützen:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Generische Strukturen können Felder enthalten, die den Typparameter verwenden, einschließlich Slices:
type Container[T any] struct {
Items []T
}Sie können Methoden für generische Strukturen definieren und generische Konstruktorfunktionen erstellen:
func NewContainer[T any]() *Container[T] {
return &Container[T]{Items: []T{}}
}
func (c *Container[T]) Add(item T) {
c.Items = append(c.Items, item)
}Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Elementtyp lesen
scanner.Scan()
itemType := scanner.Text()
// Anzahl lesen
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: Erstelle einen Container[string] mit NewContainer
// TODO: Lies 'count' Elemente (jedes hat den Namen in einer Zeile, die Daten in der nächsten Zeile)
// TODO: Füge jedes Element zum Container hinzu
// TODO: Gib jedes Element als "[Name]: [Data]" aus
// TODO: Gib "Total items: [count]" mit der Count-Methode aus
case "int":
// TODO: Erstelle einen Container[int] mit NewContainer
// TODO: Lies 'count' Elemente (jedes hat den Namen in einer Zeile, Integer-Daten in der nächsten Zeile)
// TODO: Füge jedes Element zum Container hinzu
// TODO: Gib jedes Element als "[Name]: [Data]" aus
// TODO: Gib "Total items: [count]" mit der Count-Methode aus
case "priced":
// TODO: Erstelle einen Slice, um PricedItem[float64]-Instanzen zu halten
// TODO: Lies 'count' Elemente (jedes hat Name, Menge und Preis in separaten Zeilen)
// TODO: Erstelle ein PricedItem[float64] für jedes und füge es zum Slice hinzu
// TODO: Gib jedes Element als "[Name] x[Quantity] @ [Price]" aus, wobei der Preis eine Dezimalstelle anzeigt
// TODO: Gib "Total items: [count]" aus
}
_ = scanner // Verwende scanner, um die Eingabe zu lesen
_ = count // Verwende count für die Schleife
}
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