🕰 前情提要:Go 泛型,2022 年才来,但迟到总比不到强
就像你妈在你 25 岁生日那天终于送了辆自行车——
“早干嘛去了?”
“……在造更稳的轴承。”
在泛型降临前,Go 程序员靠三招续命:
interface{}+ 断言 → 运行时惊喜盲盒 🎁(panic: interface conversion: interface {} is string, not int)- 代码生成(
go generate) → 写一份逻辑,生成 8 份.go,Git 提交时手抖 😅 - 说服老板:我们不需要复用 → 高风险高回报 🎯(慎用)
🪄 泛型是什么?——“函数的填空题”
普通函数:
func CopySliceInt(src []int) []int { /* ... */ }
func CopySliceString(src []string) []string { /* ... */ }
// func CopySliceCat(src []Cat) []Cat { /* 求你了,住手 */ }
泛型函数:
func CopySlice[T any](src []T) []T {
dst := make([]T, len(src))
copy(dst, src)
return dst
}
✅ 一行搞定万物
✅ 编译时检查类型(不是运行时开盲盒)
✅ T 是占位符,any 是“啥都行”(相当于 interface{} 的编译时版)
💬 土拨鼠小声:
“所以……T就是函数界的‘此处填姓名’?”
✅ 正解!
🎯 三分钟掌握泛型核心语法
| 语法 | 含义 | 类比 |
|---|---|---|
func Foo[T any](x T) | T 可以是任意类型 | “请填一个名字” 👉 张三、李四、喵星人 |
func Max[T constraints.Ordered](a, b T) | T 必须能比大小(int/string/float) | “请填一个能打分的科目” 👉 数学✅ 英语✅ 早睡❌ |
type Box[T any] struct { value T } | 泛型结构体 | “快递盒”📦:装啥都行,但得提前说好大小 |
🔔 注意:
constraints.Ordered是 Go 1.21+ 的标准库约束(旧版用golang.org/x/exp/constraints)
👉 它说:“我只收能<>==的类型,谢绝[]byte和Cat。”
🛠 实战:别再写 MapIntToString 了!
需求:把 []int → []string(比如 [1,2,3] → ["1","2","3"])
以前(痛苦面具版):
func IntSliceToStringSlice(is []int) []string {
ss := make([]string, len(is))
for i, v := range is {
ss[i] = strconv.Itoa(v)
}
return ss
}
现在(泛型优雅版):
func Map[T any, U any](s []T, f func(T) U) []U {
r := make([]U, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
// 用起来:
nums := []int{1, 2, 3}
strs := Map(nums, strconv.Itoa) // ✅ 编译通过!类型自动推导
🐹 土拨鼠感动落泪:
“我终于不用在MapIntToString、MapStringToInt、MapCatToDog之间反复横跳了!”
⚠️ 泛型常见“坑位” & 避坑指南
| 误区 | 现实 | 土拨鼠应对策略 |
|---|---|---|
| “泛型 = 运行时反射” | ❌ 编译时单态化(monomorphization)→ 生成具体类型代码 | 享受零运行时开销 🚀 |
| “我能泛型嵌套泛型泛型?” | ✅ 但别学 Haskell 程序员写 [][][]T | 深度 >2 时,泡杯茶冷静一下 ☕ |
“[]T 和 T[] 一样吗?” | ❌ Go 只认 []T(方括号在前!) | 记住口诀:“切片像煎饼,方的裹圆的” 🥞 |
🧪 小测验:以下代码能编译吗?
type Container[T any] struct {
items []T
}
func (c *Container[T]) Add(item T) {
c.items = append(c.items, item)
}
func main() {
c := Container[int]{} // 注意:必须带类型参数!
c.Add(42)
}
✅ 能!
❌ 但 Container{}(漏掉 [int])会报错:
cannot infer T → 编译器:“大哥,你到底想装 int 还是 string?给个准话!”
📌 口诀:泛型实例化,方括号不能忘!
🎁 终极赠礼:泛型三原则(土拨鼠版)
- 能用
any就用any—— 别一上来就写T constraints.Integer,除非你真需要< - 类型推导是你的朋友 ——
Map(nums, f)比Map[int, string](nums, f)简洁 200% - 当泛型让你头秃 → 先写非泛型版 → 跑通 → 再泛化 → ✅ 安全落地
🐾 结语:
Go 泛型不是银弹,但它是你工具箱里那把瑞士军刀——平时收着,关键时刻咔嗒一开,问题迎刃而解。
所以,放下interface{}的扳手,拿起泛型的螺丝刀——
你的CopySlice,值得一次编写,终身复用。
