Menu
Coddy logo textTech

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: 25

Sie 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.

challenge icon

Aufgabe

Einfach

Lass 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) und Data (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 Feld Items, das ein Slice von Item[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) und Price (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 Add fü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 Count für Container[T], die die Anzahl der Artikel im Container zurückgibt.

  • main.go: Demonstriere deine generischen Strukturen mit verschiedenen Typen.

    Lies einen Artikeltyp ein (string, int oder priced), lies dann eine Anzahl gefolgt von den Artikeldetails ein.

    Für den Typ string: Lies Paare aus Name und String-Daten. Erstelle einen Container[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 einen Container[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). Erstelle PricedItem[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, int oder priced)
  • 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 Paper

Deine Ausgabe sollte sein:

Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3

Und gegeben:

int
2
Apples
50
Oranges
30

Deine Ausgabe sollte sein:

Apples: 50
Oranges: 30
Total items: 2

Und gegeben:

priced
2
Widget
10
19.99
Gadget
5
49.50

Deine Ausgabe sollte sein:

Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2

Beachte, 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
}
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