Menu
CoddyTech

泛型结构体

本课属于 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

挑战

简单

让我们构建一个通用库存系统,用来跟踪不同类型的物品!你将创建可重复使用的容器结构,让它们能处理任意类型,同时保持完整的类型安全。

你将把代码组织到两个文件中:

  • inventory.go:定义通用容器结构。

    创建一个通用结构体 Item[T any],包含两个字段:Name(string 类型)和 Data(T 类型)。它可以表示带有关联数据的任意物品,数据类型灵活。

    创建一个通用结构体 Container[T any],包含一个字段 Items,用于存放 Item[T] 切片。这个容器可以存储多个数据类型相同的物品。

    创建一个受约束的通用结构体 PricedItem[T int | float64],包含三个字段:Name(string 类型)、Quantity(int 类型)和 Price(T 类型)。这个约束可确保价格始终是数值。

    实现一个函数 NewContainer[T any]() *Container[T],用于创建并返回一个指向空 Container 的指针。

    为 *Container[T] 实现一个方法 Add,接收名称(string)和数据(T),创建一个 Item,并将其追加到容器的 Items 切片中。

    为 Container[T] 实现一个方法 Count,用于返回容器中的物品数量。

  • main.go:用不同类型演示你的通用结构。

    读取物品类型(string、int 或 priced),然后读取数量,接着读取物品详情。

    对于 string 类型:读取名称和字符串数据组成的配对。创建一个 Container[string],添加所有物品,然后将每件物品打印为 [Name]: [Data],并在后面打印总数。

    对于 int 类型:读取名称和整数数据组成的配对。创建一个 Container[int],添加所有物品,然后将每件物品打印为 [Name]: [Data],并在后面打印总数。

    对于 priced 类型:读取名称、数量和价格(浮点数)组成的三元组。直接创建 PricedItem[float64] 实例,并将每件物品打印为 [Name] x[Quantity] @ [Price],价格保留一位小数。

    数量行按以下格式打印: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 使用约束来确保价格只能使用数值类型。创建这些通用结构体的实例时,必须明确指定类型参数。

亲手试试

package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// 读取商品类型
	scanner.Scan()
	itemType := scanner.Text()
	
	// 读取 count
	scanner.Scan()
	count, _ := strconv.Atoi(scanner.Text())
	
	switch itemType {
	case "string":
		// TODO:使用 NewContainer 创建一个 Container[string]
		// TODO:读取 'count' 个商品(每个商品的名称占一行,数据占下一行)
		// TODO:将每个商品添加到容器中
		// TODO:按 "[Name]: [Data]" 的格式打印每个商品
		// TODO:使用 Count 方法打印 "Total items: [count]"
		
	case "int":
		// TODO:使用 NewContainer 创建一个 Container[int]
		// TODO:读取 'count' 个商品(每个商品的名称占一行,整数数据占下一行)
		// TODO:将每个商品添加到容器中
		// TODO:按 "[Name]: [Data]" 的格式打印每个商品
		// TODO:使用 Count 方法打印 "Total items: [count]"
		
	case "priced":
		// TODO:创建一个切片来存放 PricedItem[float64] 实例
		// TODO:读取 'count' 个商品(每个商品的名称、数量和价格各占一行)
		// TODO:为每个商品创建 PricedItem[float64],并添加到切片中
		// TODO:按 "[Name] x[Quantity] @ [Price]" 的格式输出每个条目,价格保留一位小数
		// TODO:输出 "Total items: [count]"
	}
	
	_ = scanner // 使用 scanner 读取输入
	_ = count   // 使用 count 控制循环
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器