泛型结构体
本课属于 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 中构建类型安全、可复用数据结构的基础。在下一课中,你将学习如何为这些泛型类型定义方法。
挑战
简单让我们构建一个通用库存系统,用来跟踪不同类型的物品!你将创建可重复使用的容器结构,让它们能处理任意类型,同时保持完整的类型安全。
你将把代码组织到两个文件中:
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 控制循环
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器