文章目录
- [🚀 15 - Go 泛型(Generics):从入门到实战](#🚀 15 - Go 泛型(Generics):从入门到实战)
- 什么是泛型?
- [Go 泛型基础语法](#Go 泛型基础语法)
-
- [✅ 基本语法](#✅ 基本语法)
- [📌 示例:泛型函数](#📌 示例:泛型函数)
- 类型约束(Constraint)
-
- [✅ 内置约束:`any`](#✅ 内置约束:
any) - [✅ 自定义约束](#✅ 自定义约束)
- [✅ 内置约束:`any`](#✅ 内置约束:
- 泛型切片操作(实战重点)
-
- [📌 示例:查找元素](#📌 示例:查找元素)
- [📌 示例:获取最大值](#📌 示例:获取最大值)
- 泛型结构体
-
- [📌 示例:泛型容器](#📌 示例:泛型容器)
- [泛型 Map 封装(高级实战)](#泛型 Map 封装(高级实战))
- 泛型的底层原理(重点)
-
- [👉 **字典传递(Dictionary Passing) + 类型实例化**](#👉 字典传递(Dictionary Passing) + 类型实例化)
- [📌 对比其他语言](#📌 对比其他语言)
- [泛型 vs interface{}](#泛型 vs interface{})
-
- [📌 对比](#📌 对比)
- [📌 示例对比](#📌 示例对比)
- [interface{} 写法](#interface{} 写法)
- 泛型写法
- 泛型使用最佳实践
-
- [✅ 优先使用具体类型](#✅ 优先使用具体类型)
- [✅ 只在"重复逻辑"时使用](#✅ 只在“重复逻辑”时使用)
- [✅ 约束尽量精确](#✅ 约束尽量精确)
- 常见坑点(面试高频)
-
- [⚠️ 不能直接比较任意类型](#⚠️ 不能直接比较任意类型)
- [⚠️ 泛型不能做类型断言](#⚠️ 泛型不能做类型断言)
- [⚠️ 方法不能单独声明类型参数](#⚠️ 方法不能单独声明类型参数)
- 总结
🚀 15 - Go 泛型(Generics):从入门到实战
Go 1.18 引入泛型,彻底改变了 Go 在类型抽象方面的能力。
本文将带你从原理到实战,全面掌握 Go 泛型。
什么是泛型?
在 Go 1.18 之前,我们如果想写"通用代码",通常只能:
- 使用
interface{} - 或者写重复代码
👉 典型问题:
go
package main
import "fmt"
// SumInt 返回两个整数的和
func SumInt(a, b int) int {
return a + b
}
// SumFloat 返回两个浮点数的和
func SumFloat(a, b float64) float64 {
return a + b
}
func main() {
sumInt := SumInt(1, 2)
fmt.Println("整数之和:", sumInt) // 输出: 3
sumFloat := SumFloat(1.5, 2.6)
fmt.Println("浮点数:", sumFloat) // 输出: 4.1
}
❌ 问题:
- 重复代码
- 不优雅
- 不易维护
Go 泛型基础语法
Go 使用 类型参数(Type Parameters) 来实现泛型。
✅ 基本语法
go
func 函数名[T 类型约束](参数 T) 返回值 T {}
📌 示例:泛型函数
go
package main
import "fmt"
// 泛型函数
// Add 对两个同类型数值进行加法运算
// 支持 int 和 float64 两种类型
//
// 参数:
//
// a: 第一个加数
// b: 第二个加数
//
// 返回:
//
// 两数之和
func Add[T int | float64](a, b T) T {
return a + b
}
func main() {
fmt.Println(Add(1, 2)) // 输出: 3
fmt.Println(Add(1.5, 2.6)) // 输出: 4.1
}
类型约束(Constraint)
泛型不是"任意类型都可以",必须指定约束
✅ 内置约束:any
go
package main
import "fmt"
// 泛型函数
// Print 打印任意类型的值到标准输出
//
// 参数 v: 要打印的值,支持任意类型
func Print[T any](v T) {
fmt.Println(v)
}
func main() {
// 调用Print函数,传入一个整型值
Print(10) // 输出:10
// 调用Print函数,传入一个字符串值
Print("hello") // 输出:hello
// 调用Print函数,传入一个浮点型值
Print(1.234423) // 输出:1.234423
// 调用Print函数,传入一个布尔值
Print(true) // 输出:true
// 调用Print函数,传入一个切片值
Print([]int{1, 2, 3}) // 输出:[1 2 3]
}
👉 any 等价于:
go
interface{}
✅ 自定义约束
go
package main
import "fmt"
// Number 是一个泛型约束,用于限制Sum函数只能接受满足该约束的类型
type Number interface {
int | int32 | int64 | float32 | float64
}
// Sum 计算两个数字的和
//
// 参数:
// - a: 第一个数字
// - b: 第二个数字
//
// 返回值:
// - 两个数字的和
func Sum[T Number](a, b T) T {
return a + b
}
// main 是程序的入口函数
//
// 演示泛型Sum函数的不同调用场景,包括整数相加、浮点数相加和混合类型相加
func main() {
fmt.Println(Sum(1, 2)) // 输出: 3
fmt.Println(Sum(1.3, 2.2)) // 输出: 3.5
fmt.Println(Sum(1, 2.2)) // 输出: 3.2
}
泛型切片操作(实战重点)
📌 示例:查找元素
go
package main
import "fmt"
func Contains[T comparable](slice []T, target T) bool {
// 遍历切片中的每个元素
for _, v := range slice {
// 如果找到目标元素,返回 true
if v == target {
return true
}
}
// 遍历结束未找到,返回 false
return false
}
func main() {
// 创建整数切片
s := []int{1, 2, 3}
// 测试切片是否包含2
fmt.Println("2:", Contains(s, 2)) // 输出:true
// 测试切片是否包含4
fmt.Println("4:", Contains(s, 4)) // 输:false
}
👉 为什么用 comparable?
comparable 的本质不是语法,而是:
告诉编译器:这个类型集合里的所有类型,都支持 == 操作
📌 示例:获取最大值
go
package main
import "fmt"
func Max[T int | float64](slice []T) T {
max := slice[0] // 初始化最大值为切片第一个元素
for _, v := range slice { // 遍历切片所有元素
if v > max { // 若当前元素大于当前最大值
max = v // 更新最大值
}
}
return max // 返回最大值
}
func main() {
// 求整数切片最大值并打印
fmt.Println(Max([]int{3, 1, 2})) // 输出:3
// 求浮点数切片最大值并打印
fmt.Println(Max([]float64{1.1, 5.5, 3.3})) // 输出:5.5
}
泛型结构体
泛型不仅可以用于函数,还可以用于结构体 👇
📌 示例:泛型容器
go
package main
import "fmt"
// 定义一个泛型结构体 Box
// [T any] 表示类型参数 T,可以是任意类型(any = interface{})
type Box[T any] struct {
value T // 存储的值,类型为 T(泛型类型)
}
// 定义 Box 的方法 Get
// 注意:这里的接收者 (b Box[T]) 也必须带上 [T]
func (b Box[T]) Get() T {
return b.value // 返回存储的值
}
func main() {
// 实例化泛型结构体
// Box[int] 表示把 T 替换为 int
b := Box[int]{value: 10}
// 调用方法
// 此时 Get() 返回的是 int 类型
fmt.Println(b.Get()) // 输出:10
}
泛型结构体 = 类型参数 + 编译期实例化 + 类型安全复用
泛型 Map 封装(高级实战)
go
package main
import "fmt"
// 定义一个泛型结构体 Map
// K:key 类型,必须是 comparable(因为 map 的 key 必须可比较)
// V:value 类型,可以是任意类型
type Map[K comparable, V any] struct {
data map[K]V // 内部用原生 map 存数据
}
// 构造函数:创建一个 Map 实例
func NewMap[K comparable, V any]() *Map[K, V] {
return &Map[K, V]{ // 返回指针
data: make(map[K]V), // 初始化内部 map
}
}
// 设置键值对
func (m *Map[K, V]) Set(key K, value V) {
m.data[key] = value // 往 map 里存数据
}
// 获取值
func (m *Map[K, V]) Get(key K) V {
return m.data[key] // 从 map 里取数据
}
func main() {
// 创建一个 map:key 是 string,value 是 int
m := NewMap[string, int]()
// 存数据
m.Set("a", 1)
// 取数据
fmt.Println(m.Get("a")) // 输出:1
}
泛型的底层原理(重点)
Go 泛型并不是传统的"模板替换",而是:
👉 字典传递(Dictionary Passing) + 类型实例化
简单理解:
- 编译器会生成具体类型版本(部分场景)
- 或通过字典传递方法实现
📌 对比其他语言
| 语言 | 泛型实现 |
|---|---|
| C++ | 模板(编译期展开) |
| Java | 类型擦除 |
| Go | 混合策略(实例化 + 字典) |
泛型 vs interface{}
很多人会问:
👉 有了泛型,还需要 interface{} 吗?
答案是:需要!
📌 对比
| 维度 | 泛型 | interface{} |
|---|---|---|
| 类型安全 | ✅ | ❌ |
| 性能 | 更高 | 较低 |
| 灵活性 | 中 | 高 |
| 使用复杂度 | 高 | 低 |
📌 示例对比
interface{} 写法
go
func Print(v interface{}) {
fmt.Println(v)
}
泛型写法
go
func Print[T any](v T) {
fmt.Println(v)
}
泛型使用最佳实践
✅ 优先使用具体类型
不要滥用泛型
✅ 只在"重复逻辑"时使用
✔ 推荐:
- 通用算法
- 数据结构(栈、队列、缓存)
❌ 不推荐:
- 业务代码
✅ 约束尽量精确
go
// 不推荐
func Add[T any](a, b T) T
// 推荐
func Add[T int | float64](a, b T) T
常见坑点(面试高频)
⚠️ 不能直接比较任意类型
go
func Equal[T any](a, b T) bool {
return a == b // ❌ 编译错误
}
✔ 正确:
go
func Equal[T comparable](a, b T) bool {
return a == b
}
⚠️ 泛型不能做类型断言
go
func Test[T any](v T) {
// v.(int) ❌
}
⚠️ 方法不能单独声明类型参数
go
type A struct{}
// ❌ 错误
func (a A) Test[T any]() {}
总结
Go 泛型的核心价值:
✅ 提高代码复用
✅ 提高类型安全
✅ 减少重复代码
📌 一句话总结:
泛型 = 类型安全的抽象能力 + 更优雅的代码复用