Menu
Coddy logo textTech

ジェネリック構造体

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 70/107。

関数と同様に、構造体も型パラメータを持つことができます。ジェネリック構造体を使用すると、型安全性を維持しながら、あらゆる型で動作するデータ構造を作成できます。これは、スタック、キュー、キーと値のペアのような再利用可能なコンテナを構築するのに最適です。

ジェネリックな構造体を定義するには、構造体名の後に型パラメータを記述します:

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
}

ジェネリック関数とは異なり、ジェネリック構造体のインスタンスを作成する際には、型を明示的に指定する必要があります。Goは文脈だけから型を推論することはできません。

ジェネリック構造体は複数の型パラメータを持つことができ、ペアやマップのような構造に最適です:

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

格納された型が特定の操作をサポートすることを保証するために、制約を適用することもできます:

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

ジェネリック構造体は、Go言語において型安全で再利用可能なデータ構造を構築するための基礎となります。次のレッスンでは、これらのジェネリック型に対してメソッドを定義する方法を学びます。

challenge icon

チャレンジ

簡単

さまざまな種類のアイテムを追跡できる、汎用的な在庫管理システムを構築しましょう!完全な型安全性を維持しながら、あらゆる型で動作する再利用可能なコンテナ構造を作成します。

コードは2つのファイルに分けて構成します:

  • inventory.go: 汎用的なコンテナ構造を定義します。

    2つのフィールドを持つジェネリック構造体 Item[T any] を作成します:Name (string) と Data (T型)。これは、柔軟な型のデータが関連付けられた任意のアイテムを表します。

    Item[T] のスライスを保持する単一のフィールド Items を持つジェネリック構造体 Container[T any] を作成します。このコンテナは、同じデータ型の複数のアイテムを格納できます。

    3つのフィールドを持つ制約付きジェネリック構造体 PricedItem[T int | float64] を作成します:Name (string)、Quantity (int)、および Price (T型)。この制約により、価格は常に数値であることが保証されます。

    空の Container を作成してそのポインタを返す関数 NewContainer[T any]() *Container[T] を実装します。

    名前 (string) とデータ (T) を受け取り、Item を作成してコンテナの Items スライスに追加するメソッド Add*Container[T] に実装します。

    コンテナ内のアイテム数を返すメソッド CountContainer[T] に実装します。

  • main.go: 異なる型を使用してジェネリック構造体を実演します。

    アイテムの型 ("string""int"、または "priced") を読み取り、次にカウントとそれに続くアイテムの詳細を読み取ります。

    "string" 型の場合:名前と文字列データのペアを読み取ります。Container[string] を作成し、すべてのアイテムを追加してから、各アイテムを [Name]: [Data] の形式で出力し、最後に合計カウントを出力します。

    "int" 型の場合:名前と整数データのペアを読み取ります。Container[int] を作成し、すべてのアイテムを追加してから、各アイテムを [Name]: [Data] の形式で出力し、最後に合計カウントを出力します。

    "priced" 型の場合:名前、数量、価格 (float) の3つの値を読み取ります。PricedItem[float64] のインスタンスを直接作成し、各アイテムを [Name] x[Quantity] @ [Price] の形式で出力します。価格は小数点第1位まで表示します。

    カウントの行は "Total items: [count]" として出力します。

以下の入力が提供されます:

  • 1行目:アイテムの型 ("string""int"、または "priced")
  • 2行目:アイテムの数 (整数)
  • 続く行:型に基づいたアイテムの詳細

例えば、以下が与えられた場合:

string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paper

出力は以下のようになります:

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

また、以下が与えられた場合:

int
2
Apples
50
Oranges
30

出力は以下のようになります:

Apples: 50
Oranges: 30
Total items: 2

さらに、以下が与えられた場合:

priced
2
Widget
10
19.99
Gadget
5
49.50

出力は以下のようになります:

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

同じ Container 構造が文字列と整数の両方でシームレスに動作する一方で、PricedItem は制約を使用して価格に数値型のみが使用されるようにしていることに注目してください。これらのジェネリック構造体のインスタンスを作成するときは、型パラメータを明示的に指定する必要があります。

チートシート

ジェネリック構造体を使用すると、型安全性を維持しながら、あらゆる型で動作するデータ構造を作成できます。

ジェネリック構造体を定義するには、構造体名の後に型パラメータを記述します。

type Box[T any] struct {
    Value T
}

ジェネリック構造体のインスタンスを作成するときは、型を明示的に指定する必要があります(Goはこれを推論できません)。

intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}

ジェネリック構造体は、複数の型パラメータを持つことができます。

type Pair[K, V any] struct {
    Key   K
    Value V
}

p := Pair[string, int]{Key: "age", Value: 25}

制約を適用して、格納される型が特定の操作をサポートするように保証できます。

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

ジェネリック構造体には、スライスを含む、型パラメータを使用するフィールドを含めることができます。

type Container[T any] struct {
    Items []T
}

ジェネリック構造体にメソッドを定義したり、ジェネリックなコンストラクタ関数を作成したりできます。

func NewContainer[T any]() *Container[T] {
    return &Container[T]{Items: []T{}}
}

func (c *Container[T]) Add(item T) {
    c.Items = append(c.Items, item)
}

自分で試してみよう

package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// アイテムの種類を読み込む
	scanner.Scan()
	itemType := scanner.Text()
	
	// 個数を読み込む
	scanner.Scan()
	count, _ := strconv.Atoi(scanner.Text())
	
	switch itemType {
	case "string":
		// TODO: NewContainerを使用してContainer[string]を作成する
		// TODO: 'count'個のアイテムを読み込む(各アイテムは1行目に名前、次の行にデータを持つ)
		// TODO: 各アイテムをコンテナに追加する
		// TODO: 各アイテムを "[Name]: [Data]" として出力する
		// TODO: Countメソッドを使用して "Total items: [count]" を出力する
		
	case "int":
		// TODO: NewContainerを使用してContainer[int]を作成する
		// TODO: 'count'個のアイテムを読み込む(各アイテムは1行目に名前、次の行に整数データを持つ)
		// TODO: 各アイテムをコンテナに追加する
		// TODO: 各アイテムを "[Name]: [Data]" として出力する
		// TODO: Countメソッドを使用して "Total items: [count]" を出力する
		
	case "priced":
		// TODO: PricedItem[float64]のインスタンスを保持するスライスを作成する
		// TODO: 'count'個のアイテムを読み込む(各アイテムは名前、数量、価格が別々の行にある)
		// TODO: 各アイテムに対してPricedItem[float64]を作成し、スライスに追加する
		// TODO: 各アイテムを "[Name] x[Quantity] @ [Price]" として出力し、価格は小数点第1位まで表示する
		// TODO: "Total items: [count]" を出力する
	}
	
	_ = scanner // 入力を読み込むためにscannerを使用する
	_ = count   // ループのためにcountを使用する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン