练习
学完本章你应该掌握
- 能判断什么场景适合用泛型函数、泛型结构体、泛型切片或泛型 map,而不是把所有类型都硬塞进
any。 - 能写出
func Name[T Constraint](...)、type Result[T any] struct {}、type List[T any] []T、type Dict[K comparable, V any] map[K]V这类基础泛型定义。 - 能根据函数体里的真实操作选择合适约束,例如
any、comparable、~int和自定义Ordered约束。 - 能处理泛型代码里的常见细节,例如零值返回、
value, ok模式、泛型方法接收者要带[T]、泛型 map 写入前要先初始化。 - 能把前面学过的结构体、自定义类型、函数、高阶函数、切片、map 和错误处理自然带进泛型工具代码里,写出更可复用的基础组件。
- 能识别并修正泛型常见错误,例如约束过宽却做比较、约束过窄导致复用性差、忘记写
comparable、把自定义类型和底层类型的支持范围想错。
简单
第 1 题:编写一个支持两类数字的泛型加法函数
编写一个 Go 程序,完成下面要求:
- 定义约束
Number,只允许int和float64 - 编写泛型函数
Add[T Number](a, b T) T - 在
main中分别调用:Add(12, 30)Add(19.5, 0.5) - 输出两次计算结果
查看参考答案
| |
输出结果:
| |
说明:这道题的重点是先把“允许哪些类型”写进约束,再在函数体里安全地使用 +。
第 2 题:编写一个双类型参数的标签函数
编写一个 Go 程序,完成下面要求:
- 编写泛型函数
BuildTag[T int, K string | int](lessonNo T, tag K) string - 返回格式如下:
| |
- 在
main中分别调用:BuildTag(13, "generic")BuildTag(8, 101)
查看参考答案
| |
一种可能的输出结果:
| |
说明:这道题的重点是体会一个泛型函数里可以同时声明多个类型参数,而且每个类型参数都可以有自己的约束。
第 3 题:修正 any 约束下无法比较的最大值函数
下面程序的目标是输出:
| |
但它现在不能通过编译。请改正代码,让它正常运行。
| |
查看参考答案
修正后的代码如下:
| |
原因说明:
any只表示“可以是任意类型”,并不代表这些类型一定支持>。- 如果函数体里要做比较,就应该把约束收窄到“支持比较”的类型集合。
第 4 题:定义一个泛型结果结构体
编写一个 Go 程序,完成下面要求:
- 定义泛型结构体
Result[T any] - 字段包括:
Code intMessage stringData T - 给它绑定方法
GetData() T - 在
main中分别创建:Result[string]Result[[]int] - 输出两个实例中的
Data
查看参考答案
| |
输出结果:
| |
说明:方法接收者要写成 Result[T],不能只写 Result。
第 5 题:实现泛型切片的 Last 方法
编写一个 Go 程序,完成下面要求:
- 定义泛型切片类型
List[T any] - 给它绑定方法
Last() (T, bool) - 如果切片为空,返回
T的零值和false - 如果切片不为空,返回最后一个元素和
true - 在
main中分别测试:List[string]{"Go", "泛型", "练习"}List[int]{}
查看参考答案
| |
输出结果:
| |
说明:var zero T 是泛型代码里很常见的写法,适合在“找不到值”时返回对应类型的零值。
第 6 题:编写一个泛型字典的读取方法
编写一个 Go 程序,完成下面要求:
- 定义泛型 map 类型
Dict[K comparable, V any] - 给它绑定方法
Get(key K) (V, bool) - 在
main中准备下面这张表:
| |
- 先读取
"接口" - 再读取
"切片" - 输出两次读取结果
查看参考答案
| |
输出结果:
| |
说明:K 要写成 comparable,因为 map 的键必须是可比较类型。
一般
第 7 题:让自定义分数类型也能使用泛型求和
编写一个 Go 程序,完成下面要求:
- 定义自定义类型
Score,底层类型是int - 编写泛型函数
Sum[T ~int](nums []T) T - 让它既能统计
[]int,也能统计[]Score - 在
main中准备:
| |
- 输出总分
查看参考答案
| |
输出结果:
| |
说明:~int 的意思是“底层类型是 int 的类型也可以传进来”,所以 Score 也满足这个约束。
第 8 题:用泛型把键值对切片转成 map
编写一个 Go 程序,完成下面要求:
- 定义泛型结构体
Pair[K comparable, V any] - 字段包括:
Key KValue V - 编写泛型函数
ToMap[K comparable, V any](pairs []Pair[K, V]) map[K]V - 在
main中准备下面这组数据:
| |
- 把它转成 map 后,按下面顺序输出:
| |
查看参考答案
| |
说明:这道题把泛型结构体、多类型参数、切片和 map 组合起来了,很接近真实开发里“把数据转成查找表”的小工具函数。
第 9 题:实现一个泛型 IndexOf
编写一个 Go 程序,完成下面要求:
- 编写泛型函数
IndexOf[T comparable](list []T, target T) int - 如果找到了,返回第一次出现的下标
- 如果没找到,返回
-1 - 在
main中至少测试下面两组数据:
| |
查看参考答案
| |
输出结果:
| |
说明:如果函数体里要用 == 比较元素,就应该把类型参数约束成 comparable。
第 10 题:实现一个带错误返回的泛型查找函数
编写一个 Go 程序,完成下面要求:
- 编写泛型函数
FindOrError[K comparable, V any](dict map[K]V, key K) (V, error) - 如果
key存在,返回对应值和nil - 如果
key不存在,返回V的零值和错误信息 - 在
main中准备下面这张表:
| |
- 先查询
102 - 再查询
999
查看参考答案
| |
一种可能的输出结果:
| |
说明:这道题会自然复习前面异常处理章节里的 error 返回,同时也练到了泛型里的“零值返回”写法。
第 11 题:实现一个泛型集合 Set
编写一个 Go 程序,完成下面要求:
- 定义泛型 map 类型
Set[T comparable] - 编写函数
NewSet[T comparable]() Set[T] - 给
Set[T]绑定两个方法:Add(value T)Has(value T) bool - 在
main中创建一个Set[string] - 依次加入
"Go"、"泛型"、"Go" - 最后输出:
集合长度
是否包含
"泛型"是否包含"接口"
查看参考答案
| |
输出结果:
| |
说明:这里的 value 使用 struct{},是 Go 里实现集合时很常见的写法,因为它不需要额外存储有效负载。
进阶
下面四题会把泛型和前面学过的结构体、自定义类型、切片、map、高阶函数、错误处理自然放到一起,更接近真实开发里写“通用工具组件”的思路。
第 12 题:实现一个泛型 GroupBy
编写一个 Go 程序,完成下面要求:
- 定义结构体
Student - 字段包括:
Name stringCity stringScore int - 编写泛型函数:
| |
- 它的作用是:按照
keyFn返回的键把元素分组 - 在
main中准备下面这组数据:
| |
- 按城市分组
- 最后按下面顺序输出每个城市的人数:
| |
查看参考答案
| |
说明:这道题把高阶函数和泛型组合起来了。真正想练的是“把分组逻辑抽象出来,而不是为每种结构体都单独写一版”。
第 13 题:实现一个泛型 MaxBy
编写一个 Go 程序,完成下面要求:
- 定义约束
Ordered,允许:~int~float64~string - 定义自定义类型
Score,底层类型是int - 定义结构体
Student - 字段包括:
Name stringScore Score - 编写泛型函数:
| |
- 如果切片为空,返回
T的零值和false - 否则返回“得分最高的元素”和
true - 在
main中找到最高分学生,并输出姓名、分数和ok
查看参考答案
| |
输出结果:
| |
说明:这里的 Score 虽然是自定义类型,但因为底层类型是 int,所以在 ~int 约束下仍然可以参与比较。
第 14 题:实现一个泛型分页结果转换器
编写一个 Go 程序,完成下面要求:
- 定义泛型结构体
Page[T any] - 字段包括:
Items []TTotal int - 编写泛型函数:
| |
- 它的作用是:
保留
Total把Items中的每个元素转换成另一种类型 - 在
main中定义结构体Student - 准备下面这个分页结果:
| |
- 把它转换成
Page[string] - 每一项都变成:
| |
查看参考答案
| |
输出结果:
| |
说明:这道题会让你体会到,泛型不只是“少写几份函数”,还可以让“数据结构本身”和“转换逻辑”一起复用。
第 15 题:合并两张泛型计数表
编写一个 Go 程序,完成下面要求:
- 定义自定义类型
LessonCount,底层类型是int - 编写泛型函数:
| |
- 它的作用是: 把两张计数表按 key 合并 相同 key 的值相加 不同 key 保留原值
- 在
main中准备下面两张表:
| |
- 最后按下面顺序输出:
| |
查看参考答案
| |
说明:这道题把泛型 map、~int、自定义类型和固定顺序输出组合到了一起,已经很接近“写一段可复用的小工具函数”的真实感觉了。