浅析Golang泛型

1. 概述

泛型是很多语言的标配，合理的使用泛型便于开发，然而，Golang 一开始并不支持泛型，因为开发 Golang 的人认为泛型重要但不是必须的，所以 Golang 一开始发布的时候没有泛型。今年，在 Go 社区的呼吁下，Go1.17 发布了泛型的体验版，为 Go1.18 的正式版泛型做了铺垫。本文介绍一下 Go 中的泛型和一些使用方法。

2. 什么是泛型

在介绍泛型之前先了解一下多态

什么是多态？
多态是不同对象对同一事件的不同表现

如：

动物类

有三种对象 -> 狗，猫，鸡

有一个相同的事件 -> 叫

有三种不同的表现 -> 汪汪，喵喵，咯咯达

多态分为两类：

临时性多态（根据实参类型调用对应的版本，支持调用的数量十分有限，如：函数重载）

参数化多态（根据实参类型生成不同的版本，支持任意数量的调用，这就是泛型）

什么是泛型？
简言之，就是把元素类型变成了参数

3. 泛型困境

泛型有好处但也有坏处，怎么平衡泛型的好处和坏处就成了泛型设计者要面临的问题了。

泛型的困境：

简单来说就是：编码效率低（程序员开发慢）、编译效率低（编译器编译慢）、运行效率低（用户体验差）

4. Go1.17 中泛型的要点

函数可以通过 type 关键字引入额外的类型参数 (type parameters) 列表：func F (type T)(p T) { ... } 。

这些类型参数可以像一般的参数一样在函数体中使用。

类型也可以拥有类型参数列表：type M (type T) [] T。

每个类型参数可以拥有一个约束：func F (type T Constraint)(p T) { ... }。

使用 interface 来描述类型的约束。

被用作类型约束的 interface 可以拥有一个预声明类型列表，限制了实现此接口的类型的基础类型。

使用泛型函数或类型时需要传入类型实参。

一般情况下，类型推断允许用户在调用泛型函数时省略类型实参。

如果类型参数具有类型约束，则类型实参必须实现接口。

泛型函数只允许进行类型约束所规定的操作。

5. 如何使用泛型

5.1 如何输出泛型

Copy
package main

import (
"fmt"
)

//使用[]来保存额外的类型参数列表
//[T any]是类型参数，表示该函数支持任意T类型
func printSlice[T any](s []T) {
for _, v := range s {
fmt.Printf("%v ", v)
}
fmt.Print("\n")
}

func main() {
printSlice[int]([]int{1, 2, 3, 4, 5})
printSlice[float64]([]float64{1.01, 2.02, 3.03, 4.04, 5.05})
//调用printSlice[string]([]string{“Hello”，“World”})时，会被类型推导为string类型
//如果编译器可以实现类型推导，printSlice([]string{“Hello”，“World”})
printSlice([]string{"Hello", "World"})
printSlice[int64]([]int64{5, 4, 3, 2, 1})
}

使用泛型首先要 Go1.17 版本以上

如果直接运行上面的代码可能会报错

Copy
.\main.go:9:6: missing function body
.\main.go:9:16: syntax error: unexpected [, expecting (

要想顺利运行，需要加上参数：-gcflags=-G=3

5.2 如何约束泛型的类型范围

每个类型都有一个类型约束，就像每个普通参数都有一个类型一样。

在 Go1.17 版本中，泛型函数只能使用类型参数所能实例化出的任意类型 都能 支持的操作

（对于下代码，add 函数中的 + 应是 int, int8, int16, int32, int64,uint, uint8, uint16, uint32, uint64,uintptr,float32,float64, complex64, complex128,string 都能支持的操作）

Copy
package main

import (
"fmt"
)
//限定泛型类型范围
type Addable interface {
type int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64, complex64, complex128,
string
}

func add[T Addable] (a, b T) T {
return a + b
}

func main() {
fmt.Println(add(1,2))
fmt.Println(add("hello","world"))
}

对于没有任何约束的类型参数实例，允许对其的操作有：

声明这些类型的变量。

使用相同类型的值为这些变量赋值。

将这些类型的变量以实参形式传给函数或从作为函数返回值。

取这些变量的地址。

将这些类型的值转换或赋值给 interface {} 类型变量。

通过类型断言将一个接口值赋值给这类类型的变量。

在 type switch 块中作为一个 case 分支。

定义和使用由该类型组成的复合类型，比如：元素类型为该类型的切片。

将该类型传递给一些内置函数，比如 new。

5.21 comparable 约束

不是所有的类型都可以用 == 进行比较，Go 内置了一个 comparable 约束，表示可比较

Copy
package main

import (
"fmt"
)

func findFunc[T comparable](a []T, v T) int {
for i, e := range a {
if e == v {
return i
}
}
return -1
}

func main() {
fmt.Println(findFunc([]int{1, 2, 3, 4, 5, 6}, 5))
}

5.3 泛型中的切片

和泛型函数一样，使用泛型类型时，首要要对其进行实例化（显示为类型参数赋值类型）如：vs:=slice{5,4,2,1}

Copy
package main

import (
"fmt"
)

type slice[T any] []T

/*
type any interface {
type int, string
}*/

func printSlice[T any](s []T) {
for _, v := range s {
fmt.Printf("%v ", v)
}
fmt.Print("\n")
}

func main() {
// note1: cannot use generic type slice[T interface{}] without instantiation
// note2: cannot use generic type slice[T any] without instantiation
vs := slice[int]{5, 4, 2, 1}
printSlice(vs)

5.4 泛型中的指针

Copy
package main

import (
"fmt"
)

func pointerOf[T any](v T) *T {
return &v
}

func main() {
sp := pointerOf("foo")
fmt.Println(*sp)

ip := pointerOf(123)
fmt.Println(*ip)
*ip = 234
fmt.Println(*ip)
}

5.5 泛型中的 map

Copy
package main

import (
"fmt"
)

func mapFunc[T any, M any](a []T, f func(T) M) []M {
n := make([]M, len(a), cap(a))
for i, e := range a {
n[i] = f(e)
}
return n
}

func main() {
vi := []int{1, 2, 3, 4, 5, 6}
vs := mapFunc(vi, func(v int) string {
return "<" + fmt.Sprint(v*v) + ">"
})
fmt.Println(vs)
}

5.6 泛型中的队列

Copy
package main

import (
"fmt"
)

type queue[T any] []T

func (q *queue[T]) enqueue(v T) {
*q = append(*q, v)
}

func (q *queue[T]) dequeue() (T, bool) {
if len(*q) == 0 {
var zero T
return zero, false
}
r := (*q)[0]
*q = (*q)[1:]
return r, true
}

func main() {
q := new(queue[int])
q.enqueue(5)
q.enqueue(6)
fmt.Println(q)
fmt.Println(q.dequeue())
fmt.Println(q.dequeue())
fmt.Println(q.dequeue())
}

6. 总结

本文简单介绍了 Go 中的泛型用法，合理地使用泛型有利于提高开发效率。