15 - Go 泛型(Generics):从入门到实战

文章目录

  • [🚀 15 - Go 泛型(Generics):从入门到实战](#🚀 15 - Go 泛型(Generics):从入门到实战)
  • 什么是泛型?
  • [Go 泛型基础语法](#Go 泛型基础语法)
    • [✅ 基本语法](#✅ 基本语法)
    • [📌 示例:泛型函数](#📌 示例:泛型函数)
  • 类型约束(Constraint)
    • [✅ 内置约束:`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) + 类型实例化

简单理解:

  1. 编译器会生成具体类型版本(部分场景)
  2. 或通过字典传递方法实现

📌 对比其他语言

语言 泛型实现
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 泛型的核心价值:

✅ 提高代码复用

✅ 提高类型安全

✅ 减少重复代码


📌 一句话总结:

泛型 = 类型安全的抽象能力 + 更优雅的代码复用


相关推荐
Halo_tjn2 小时前
Java 内部类
java·开发语言·算法
碎碎念的安静2 小时前
WPF 与 Qt 进程间通信(IPC)
开发语言·qt·wpf
geovindu2 小时前
密码进行加盐哈希 using CSharp,Python,Go,Java
java·python·golang·c#·哈希算法
boonya2 小时前
Spring AI 深度实践教程:从“能用”到“用好”
开发语言·python
(Charon)2 小时前
【Qt/C++】Qt/C++ 中 :: 和 . 到底有什么区别?
开发语言·c++·qt
REDcker2 小时前
C++跨平台与跨语言绑定工具:SWIG、Djinni 等选型
开发语言·c++
傻啦嘿哟2 小时前
Python 操作 Word 文档属性与字数统计方法详解
开发语言·c#
郝学胜-神的一滴2 小时前
[ 力扣 1124 ] 解锁最长良好时段问题:前缀和+哈希表的优雅解法
java·开发语言·数据结构·python·算法·leetcode·散列表
戴西软件2 小时前
戴西CAxWorks.VPG车辆工程仿真软件|假人+座椅双调整 汽车仿真效率直接拉满
java·开发语言·人工智能·python·算法·ui·汽车