Golang 泛型使用
Golang 泛型
泛型函数
定义
func Func1[T int | int32 | int64 | float32 | float64](a, b T) T {
return a + b
}
使用
Func1[int32](1, 2)
但是这样好像看起来很丑,所以可以使用自动类型推导,也就是让 Golang 自己去判断泛型参数(方括号中的类型)究竟是什么,所以调用方式就可以像下面这样
Func1(1, 2)
泛型 map
定义
type Map1[K int | float32, V string] map[K]V
⚠️:K,V 并不是一定要同时定义,比如上例就可以直接定义成
type Map1[K int | float32] map[K]string
⚠️:K,V 没有顺序关系,只是泛型的约束,只是一个代称(叫其他名字也OK)。也就是说 K 不一定非得做“键”,V 不一定非得做“值”,也就是说,上例写成下面这样也没有问题,只是约束对象发生了交换。
type Map1[K int | float32, V string] map[V]K
等价于
type MapInt map[int]string
type MapFloat32 map[float32]string
实例化
var m Map1[int, string] = make(Map1[int, string])
泛型 slice
定义
type Slice1[T int | int32] []T
等价于
type SliceInt []int
type SliceInt32 []int32
实例化
// 等价于 var arr SliceInt 或 var arr []int
var arr Slice1[int]
泛型 struct
定义
type Struct1[T float32 | float64] struct {
X T
Y T
}
等价于
type StructFloat32 struct {
X float32
Y float32
}
type StructFloat64 struct {
X float64
Y float64
}
使用
var p Struct1[float32]
⚠️
- 虽然在上述提到的个例当中,除了泛型的 map 其他都只使用了一个泛型参数,但实际上 struct 和泛型函数都可以使用多个泛型参数,与 泛型 map 使用方式相同
- 泛型变量被声明之后其使用方法与直接将其定义为指定数据类型是相同的——你可以认为泛型是一个盒子,盒子里面可能装着 int32 和 int64 两种数据类型中任何一种。在你声明某个泛型变量之前永远不知道盒子中的具体数据类型,此时盒子处于两种数据类型的叠加状态,既此时盒子里面的数据类型既是 int32 又是 int64(我不是说 int 在不同平台上的表现)。但是你一旦声明了某个泛型的变量,盒子中的数据类型就由原本的类型叠加态坍缩为单一的某个数据类型,既盒子中只可能是 int32 或 int64
- 匿名结构体不支持使用泛型
- 泛型类型可以相互嵌套,你可以在一个泛型 struct 中使用一个泛型 slice,但是需要显式声明泛型 slice 的数据类型(也就是说这么搞意义不大,不如直接定义两个泛型参数)。
泛型约束
泛型约束指的的约束泛型泛型能接受的数据类型,更直白的说就是这个 T 可以指代的数据类型。关于泛型约束我们上面提及了一部分,那就是如何使用——在定义的时候在方括号内指定。但是我们先看下面的一个🌰:
func Foreach[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](list []T) {
for _, t := range list {
fmt.Println(t)
}
}
操蛋不?所以这时候就可以自定义泛型约束,比如这样:
func ForeachPro[T Number](list []T) {
for _, t := range list {
fmt.Println(t)
}
}
type Int interface {
int | int8 | int16 | int32 | int64
}
type Uint interface {
uint | uint8 | uint16 | uint32 | uint64
}
type Float interface {
float32 | float64
}
type Number interface {
Int | Uint | Float
}
这里是使用 interface 实现行泛型约束自定义,这个语法是与泛型一同上线的,既只能在 go1.18 之后的版本使用。
是不是感觉清爽了很多?
上述使用 |
连接各个数据类型都是采用交集的意思,如果想要使用并集只需要使用换行即可
type MyInt interface {
int8 | int16
}
type YourInt interface {
int16 | int32
}
type BabyInt interface {
MyInt
YourInt
}
type Int16Slice[T BabyInt] []T
使用交集的时候必须要保证两个约束有交集——不能为空,不然无法使用(可以定义)。
三个特殊的泛型约束
- any:任何数据类型
- comparable:可以经行比较的数据类型(==, ≠)
- Ordered:可以进行大小判断的数据类型(<, >, ≤, ≥)
超集
先看一个🌰:
type MyInt int16
type Int interface {
int8 | int16 | int32 | int64
}
func funcDemo[T Int]() {
// TODO
}
func main() {
// 错误✖️
funcDemo[MyInt]()
// 正确✔️
funcDemo[int16]()
}
是不是很搞?MyInt 明明就是 int16 的别名,却无法使用其泛型方法。所以这时候你可以这样定义 Int
泛型约束
type Int interface {
int8 | ~int16 | int32 | int64
}
~int16
就表实 int16
和他的超集(所有底层数据类型是 int16
)的数据类型。
泛型方法
此处讲的方法是指接收器,既通过实例对象进行调用的函数,需要依赖于某种类型而存在。而上文说的函数是指单独的函数,只需要通过包名调用即可。
type Int interface {
int8 | int16 | int32 | int64
}
type Number[T Int] []T
func (n Number[T]) max() T {
var max T
for _, v := range n {
if v > max {
max = v
}
}
return max
}
func main() {
int16List := Number[int16]{1, 22, 53, 14}
fmt.Println(int16List.max())
int8List := Number[int8]{11, 13, 3, 4}
fmt.Println(int8List.max())
}
像上面这样你就很容易的找出一个数组的最大值。
⚠️:方法不支持泛型,既不允许下面的写法:
type Point struct {
x int
y int
}
func (p Point)[T int] move (t T) {
p.x += t
p.y += t
}
你可能会问,“哪你前面的那个为何又是允许的呢?”,注意了,前面的 max
方法中虽然有泛型的存在,但是这个泛型是依赖于泛型类型 Number
而存在的,并不是依赖于 max
方法本身,但是后面这个写法却是让泛型依托在方法 move
上的,他们存在本质上的不同,不过,你可以使用下面这种方式将泛型移动到结构体上。
type Point[T int] struct {
x T
y T
}
func (p Point[T]) move(t T) {
p.x += t
p.y += t
}
泛型和接口
在泛型出来之前,interface 的定义大概是这样的:
type rw interface {
writer(t T)
reader() T
}
给他加上泛型之后就变成了这样:
type rw[T int8 | int16] interface {
writer(t T)
reader() T
}
随着泛型一同上线的还有另一种 interface 的定义方法:
type Int interface {
int8 | int16 | int32 | int64
}
第一种写法是约束了一个方法集,你要实现这个接口,只需要实现方法集中的所有方法即可。第二种写法是一个类型集,定义泛型的时候泛型种类必须属于其中某一种。那么下面这个啥意思呢?
type fuckInterface[T int32 | int64] interface {
int8 | int16
triple(n T) T
}
没关系,我们一个一个讨论:
官方给出的说明如下:
只有方法的接口叫基本接口(Basic Interface)
包含类型约束的叫_一般接口(General Interface)——无论是否包含方法_
泛型+方法
前面我们已经讨论过很多关于 泛型+数据类型
即类型约束的事情,此处将不做赘述。故先讨论最接近原本写法的一种——interface 中只有方法和泛型(不带泛型的也不做讨论)。先看下面的例子:
type rw[T int8 | int16] interface {
writer(t T)
reader() T
}
type Number struct {
buf int8
}
func (n *Number) writer(b int8) {
n.buf = b
}
func (n *Number) reader() int8 {
return n.buf
}
func main() {
var r rw[int8] = &Number{}
r.writer(13)
fmt.Println(r.reader())
}
可见,虽然加了泛型,但是只需要和原本一样实现了里面所有的方法,依旧算是实现了这个接口……………………….吗?如果声明这样一个实例呢?
var r rw[int16] = &Number{}
然后你的 IDE 就会提醒你
cannot use &(Number literal) (value of type *Number) as rw[int16] value in variable declaration: *Number does not implement rw[int16] (wrong type for method reader)
have reader() int8
want reader() int16
也就是说你却是实现了 writer
和 reader
方法,但是是 int8 的版本,但 var r rw[int16] = &Number{}
这句话的意思是我需要你实现一个 int16 的版本。也就是说,泛型参数的类型和你实现的版本的类型需要对齐。
泛型+方法+类型约束
我们把上面的 rw
接口修改一下,Number
结构体保持不变
type rw[T int8 | int16] interface {
int8 | int16
writer(t T)
reader() T
}
这时候再使用var
r
rw[``int8``]
=
&``Number{}
去实例化会怎么样呢?编译期会告诉你
cannot use type rw[int8] outside a type constraint: interface contains type constraints
啥意思呢?就是 Number
不在约定的数据类型之内,也就是说,只能实例化成 int8 或者 int16 ,但是你使用 var
r
rw[``int8``]
=
1
去实例化的时候就会发现编告诉你:
cannot use 1 (constant of type int) as rw[int8] value in variable declaration: int does not implement rw[int8] (missing method reader)
也就是说,1
虽然是 int8 或 int16 中的一种,但是 int8 或 int16 并没有 reader
和 writer
方法。
结合两条错误消息,翻译一下就是:当接口中同时定义了方法和数据类型的时候,只能够被实例化成接口中的数据类型,且要求此数据类型实现了这个接口中的所有方法。所以你可以像下面这样使用它:
type CapLV rune
const (
A CapLV = 'A'
B CapLV = 'B'
C CapLV = 'C'
D CapLV = 'D'
)
type Man struct {
Name string
Age uint8
}
func (m Man) date() string {
return "no date, just game"
}
type Women struct {
Cap CapLV
Lover Man
}
func (w Women) date() string {
return "date with " + w.Lover.Name
}
type Human[T string] interface {
Man | Women
date() T
}
func ToDate[T Human[string]](h T) {
fmt.Println(h.date())
}
func main() {
man := Man{
Name: "ant",
Age: 17,
}
women := Women{
Cap: C,
Lover: man,
}
ToDate(man)
ToDate(women)
}
但是这样为何就对了呢?var
r
rw[``int8``]
=
1
会失败很好理解,因为 1 是 int8 ,int8 没有 rw
中定义的方法,哪 var r rw[int16] = &Number{}
为何也失败了呢?仔细看就会发现错误消息还指出了错误类型:MisplacedConstraintIface
这个啥意思呢,在源码中解释如下:
// MisplacedConstraintIface occurs when a constraint-type interface is used
// outside of constraint position.
//
// Example:
// type I interface { ~int }
//
// var _ I
MisplacedConstraintIface
在约束范围外使用?什么意思?意思就是当 interface 中带有类型的时候该接口只能存在于一个地方,而这个地方就是定义泛型时候的方括号中——作为类型约束。再直白一点就是当 interface 中有类型约束的时候这个接口只能作为泛型的类型约束,而不能被实例化。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器