形参和实参
假设我们有个计算两数之和的函数
func Add(a int, b int) int {
return a + b
}
这个函数很简单,但是它有个问题——无法计算int类型之外的和。
如果我们想计算浮点或者字符串的和该怎么办?解决办法之一就是像下面这样为不同类型定义不同的函数
func AddFloat32(a float32, b float32) float32 {
return a + b
}
func AddString(a string, b string) string {
return a + b
}
类型定义
type IntSlice []int
type Int32Slice []int32
type Float32Slice []float32
定义了一个类型IntSlice,此类型为可以容纳int类型的切片。
如果此时想定一个可以容纳int32类型的切片或者float32类型的切片,需要给每种类型定义新类型.
使用泛型后,只定义一个类型就能代表上面的所有类型
type MySlice[T int | int32 | float32] []T
- MySlice名称后有中括号”[]“
- T为类型形参,T的具体类型并不确定,类似占位符
- int | int32 | float32被称为类型约束,类型T只能接收int、int32、float32这三种类型的类型实参
- 中括号”[]”里的被称为类型形参列表,MySlice中只有一个T
- 定义的泛型类型名称为MySlice[T]
var a MySlice[int] = []int{1, 2, 3}
var b MySlice[float32] = []int{1.0, 2.0}
函数定义
上面两数之和
package main
import "fmt"
func add[T int | int32 | float64 | string](a, b T) T {
return a + b
}
func main() {
fmt.Println(add(1, 3))
// 字符串拼接
fmt.Println(add("A", "B"))
var int32A int32 = 3
var int32B int32 = 4
res := add(int32A, int32B)
fmt.Println(res)
}
4
AB
7
指针参数
package main
import (
"fmt"
"reflect"
)
func add[T int | int32 | float64 | string](a, b T) T {
return a + b
}
func init() {
fmt.Println(add(1, 3))
fmt.Println(add("A", "B"))
var int32A int32 = 3
var int32B int32 = 4
res := add(int32A, int32B)
fmt.Println(res)
fmt.Println("\n")
}
// 获取指针
func ptr[T any](in T) *T {
return &in
}
// 获取指针值
func ptrValue[T any](in *T) T {
return *in
}
func main() {
intPtr1 := ptr[int](100)
fmt.Println(*intPtr1, reflect.TypeOf(intPtr1))
intPtr2 := ptr(200)
fmt.Println(*intPtr2, reflect.TypeOf(intPtr2))
strPtr := ptr("abc")
fmt.Println(reflect.TypeOf(strPtr))
strValue := ptrValue(strPtr)
fmt.Println(strValue, reflect.TypeOf(strValue))
}
4
AB
7
100 *int
200 *int
*string
abc string
泛型实现简单说明
泛型的引入提高了Go的编码效率,但 编码效率,编译速度,运行速度 三者是无法同时提高的。Go引入泛型后,会提高编码效率,但编译速度和运行速度会有略微降低。可以通过了解Go的泛型实现,从而了解为什么会降低编译速度和运行速度。
实现泛型的两种常见方式为“虚拟方法表”和“单态化”。
虚拟方法表(Virtual Method Table)
泛型函数,在编译时都会被编译器改成只接受指针作为参数,因为指针都是一样的,所以可以只编译一份代码。这些指针的值都被分配到堆上,调用泛型函数时会将指针传递给泛型函数。
当传入的对象,且要调用对象的方法时,由于只有指向对象的指针,不知道方法在哪,所以需要一个查询方法的内存地址的表格“Virtual Method Table”,根据table查询到对应的方法地址。指针值的推导和调用虚拟函数,会比直接调用函数慢。
单态化(Monomorphization)
编译器为每个被调用传入的数据类型,生成一个泛型函数的副本。对应的编译速度会慢,但运行时性能会更快,跟不使用泛型一致。
Go的实现
go采用了两者结合的实现。
- 对于值类型,比如int、int32、struct{} 等,会分别生成一份对应的代码副本。且int和type MyInt int,其底层类型都是int,只会生成同一个副本
- 对于指针类型和接口类型的调用,会统一生成一份泛型方法的副本。调用到方法时根据方法表动态查找方法地址
综上可以理解为什么Go泛型引入后,编译速度和运行速度都会略微下降了。
Go泛型使用
基础
上述Go的泛型-类型定义、函数定义是泛型的基础。
且Go 1.18因为泛型,引入了一些新的变化。
interface{}、any、comparable
这这里的interface不是方法集的interface,而是指空接口interface{},从Go 1.18后,空接口interface{}的定义发生了变更,它可以表示所有类型的集合。
- 所有类型的集合,并非空集
- 类型约束中的空接口interface{},表示包含了所有类型的类型集,并不是限定只能传入空接口interface{}
type Slice[T interface{}] []T
var s1 Slice[int]
var s2 Slice[string]
var s3 Slice[interface{}]
由于空接口interface{}的定义发生了变更,不能直接体现实际语义,所以Go 1.18提供了新的等价关键词any。
且Go官方推荐所有的使用空接口的地方都使用any替换。
type any = interface{}
// 上述的Slice可以改写成
type Slice[T any] []T
Go 1.18内置comparable约束,表示所有可以用 != 和 == 对比的类型。
// 可以用comparable来做泛型Map的key
type Map[K comparable, V any] map[K]V
符号“~”
符号“~”,指底层类型约束,举个例子。
type Slice[T int | int32] []T
type MyInt int
var a Slice[MyInt] // 错误
MyInt底层类型是int,但其本身并不是int,所以不能用于Slice[T int | int32]的实例化。
可以使用 ~int 的写法,表示所有以int为底层类型的类型都可以用于实例化。
type Slice[T ~int | ~int32] []T
type MyInt int
var a Slice[MyInt] // 正确
泛型类型
类型形参不能单独使用
下面这种是无法定义泛型Generic[T]的。
type Generic[T int | int32] T
泛型数据结构
类型形参不能单独使用,需要跟其他数据结构组合起来一起定义泛型。
可以组合的有ptr、slice、array、struct、map、channel、interface。
package main
import (
"fmt"
"reflect"
)
type myInt interface {
~int | ~int32 | ~int64
}
// ptr、slice、struct、map、channel、interface
type Ptr[T int | int32] *T // ptr
type SliceInt[T myInt] []T // slice
type MapInt[K myInt, V any] map[K]V // map
type ChannelInt[T myInt] chan T // chan
// 只要满足有方法 Val() T,T的约束为 ~int | ~int32 | ~int64的都为这个接口的实现
type InterfaceInt[T myInt] interface { // interface
Val() T
}
// 例子1,InterfaceInt的实现
type StructInt[T myInt] struct { // struct
Data T
}
func (s *StructInt[T]) Val() T {
return s.Data
}
// 例子2,InterfaceInt的实现
type InterfaceIntImpl1 struct{}
func (i *InterfaceIntImpl1) Val() int {
return 1
}
// 例子3,InterfaceInt的实现
type InterfaceIntImpl2 int32
func (i InterfaceIntImpl2) Val() int32 {
return int32(i)
}
// 测试
func main() {
var interfaceInt InterfaceInt[int]
interfaceInt = &InterfaceIntImpl1{}
val := interfaceInt.Val()
fmt.Println(val)
fmt.Println(reflect.TypeOf(val)) // int
interfaceInt = &StructInt[int]{Data: 2}
val2 := interfaceInt.Val()
fmt.Println(val2)
fmt.Println(reflect.TypeOf(val2)) // int
var interfaceInt32 InterfaceInt[int32] = InterfaceIntImpl2(int32(2))
valInt32 := interfaceInt32.Val()
fmt.Println(valInt32)
fmt.Println(reflect.TypeOf(valInt32)) // int32
}
1
int
2
int
2
int32
指针类型约束
指针类型泛型定义约束,不能直接定义,需消除歧义。
// type Test1 [T * int] []T // error 会当做 T 乘 int
// 可以用逗号消除歧义
type Test2[T *int,] []T
type Test3[T *int | *int32,] []T
// 推荐写法
type Test4[T interface{ *int | *int32 }] []T
获取具体类型方式
在使用泛型时,获取泛型具体的类型,可以通过使用反射或者any转换获取。
func GetType[T int | string](t T) {
// t.(int) // error,泛型类型定义的变量不能使用类型断言
// 1. 反射
v := reflect.ValueOf(t)
switch v.Kind() {
case reflect.Int:
default:
}
// 2. 转换
var i any = t
switch i.(type) {
case int:
default:
}
}
实际开发中如果用到这种,需要慎重考虑是否需要使用泛型。
泛型的出现是为了屏蔽具体类型或者避免使用反射,现在又在泛型中使用反射或者any转换获得具体类型,这种做法是不合适的。
泛型函数
简单泛型函数
比较常用的泛型函数形式如下。
func Add[T int | int32](a, b T) T {
return a + b
}
// 实例化使用
Add[int](1,1) // 声明实例化类型为int
Add(1,1) // 类型推断
匿名函数
匿名函数无法自己定义类型形参,但可以使用定义好的类型形参。
// 1. 匿名函数不能自己定义类型形参
//func test() {
// fn1 := func[T int | int32](a, b T) T {
// return a + b
// }
//}
// 2. 匿名函数可以使用定义好的类型形参
func test[T int | int32](a, b T) T {
result := func(a, b T) T {
return a + b
}(a, b)
return result
}
函数闭包
可以使用函数闭包实现一些高级功能,比如下面泛型Filter、Map、Reduce的实现。
func Filter[T any](src []T, f func(T) bool) []T {
res := make([]T, 0)
for _, t := range src {
if f(t) {
res = append(res, t)
}
}
return res
}
func Map[S, T any](src []S, f func(S) T) []T {
res := make([]T, 0)
for _, s := range src {
t := f(s)
res = append(res, t)
}
return res
}
func Reduce[T any](src []T, f func(T, T) T) T {
if len(src) == 1 {
return src[0]
}
return f(src[0], Reduce(src[1:], f))
}
// 测试函数闭包
func main() {
// filter test
filterTest := Filter[int]([]int{1, 2, 3, 4, 5}, func(i int) bool {
if i > 3 {
return true
}
return false
})
fmt.Println(filterTest)
// map test
mapTest := Map[int, string]([]int{1, 2, 3}, func(i int) string {
return "str" + strconv.Itoa(i)
})
fmt.Println(mapTest)
// reduce test
reduceTest := Reduce([]int{1, 2, 3}, func(a int, b int) int {
return a + b
})
fmt.Println(reduceTest)
}
泛型结构体
结构体
泛型结构体是上面泛型数据结构的一种,这里单讲一下。
定义一个支持Map的结构体,key为comparable,value为any。
type Map[K comparable, V any] struct {
Data map[K]V
}
func NewMap[K comparable, V any]() *Map[K, V] {
return &Map[K, V]{
Data: make(map[K]V),
}
}
func (m *Map[K, V]) Set(key K, value V) {
m.Data[key] = value
}
func (m *Map[K, V]) Get(key K) V {
return m.Data[key]
}
func (m *Map[K, V]) Exist(key K) bool {
_, ok := m.Data[key]
return ok
}
func (m *Map[K, V]) PrintAll() {
for k, v := range m.Data {
fmt.Println("key: ", k, ", val: ", v)
}
}
// 使用
intStringMap := NewMap[int, string]()
intStringMap.Set(1, "a")
intStringMap.Set(2, "b")
intStringMap.PrintAll()
s1 := &Student{
Num: 1,
Name: "a",
}
s2 := &Student{
Num: 2,
Name: "b",
}
numStudentMap := NewMap[int, *Student]()
numStudentMap.Set(s1.Num, s1)
numStudentMap.Set(s2.Num, s2)
numStudentMap.PrintAll()
定义的struct map,可以兼容所有使用到map的场景,且提供统一的方法。比如PrintAll()方法,无需跟之前一样每个不同的map都要自己写一遍。
泛型方法
泛型方法无法定义类型形参,只能通过receiver使用类型形参。
// 不支持泛型方法
// func (m *Map[K, V]) TestGeneric[T int | string](a, b T) T { // error
// return a + b
// }
//
// 只能通过receiver使用类型形参
func (m *Map[K, V]) Equal(a, b K) bool {
return a == b
}
匿名结构体
匿名结构体是不支持泛型的。
// 匿名结构体不支持泛型
//testCase := struct [T int | string] { // error
// a T
//}[int] {
// a: 1
//}
接口
接口比较复杂,在go 1.18以后,go的接口分为了两种,分别是基本接口和一般接口。
基本接口
非泛型基本接口
不包含泛型的基本接口。举例
type BasicInterface interface {
Name() string
Age() int
}
可以定义变量。
// 可以定义变量
var a BasicInterface
基本接口本身也代表一个类型集,可以用在类型约束中。
// 基本接口也代表一个类型集,可以用在类型约束中
type ATest[T BasicInterface] []T
// 可以当做类型集,用在泛型方法
func BasicInterfaceFunc1[T BasicInterface](b T) {
b.Name()
b.Age()
}
// 可以跟go1.18之前写法一致
func BasicInterfaceFunc2(b BasicInterface) {
b.Name()
b.Age()
}
泛型基本接口
包含泛型的基本接口。举例
type BasicInterface2[T int | int32 | string] interface {
Func1(in T) (out T)
Func2() T
}
可以定义变量
var b1 BasicInterface2[int]
var b2 BasicInterface2[string]
也代表一个类型集,可以用在类型约束中。
// 可以用在类型约束
type BTest1[T BasicInterface2[int]] []T
type BTest2[T int] BasicInterface2[T]
// 可以当做类型集,用在泛型方法
func BTestFunc1[T BasicInterface2[int]](t T) {
t.Func2()
}
// 可以跟go1.18之前写法一致
func BTestFunc2(t BasicInterface2[int]) {
t.Func2()
}
如何实现泛型基本接口?对于BasicInterface2来说,需要满足以下条件。
- 有方法 Func1(in T) (out T),方法 Func2() T。
- T满足约束为 int | int32 | string。
- 方法的T,同时只能为一种类型。比如Func1中的T为int,Func2中的T也只能为int。
// 举例1-是BasicInterface2的实现
type BasicInterface2Impl struct{}
func (b BasicInterface2Impl) Func1(in int) (out int) {
panic("implement me")
}
func (b BasicInterface2Impl) Func2() int {
panic("implement me")
}
// 举例2-不是BasicInterface2的实现
type BasicInterface2Impl2 struct{}
func (b BasicInterface2Impl2) Func1(in string) (out string) {
panic("implement me")
}
func (b BasicInterface2Impl2) Func2() int {
panic("implement me")
}
// 举例3-不是BasicInterface2的实现
type BasicInterface2Impl3 struct{}
func (b BasicInterface2Impl3) Func1(in float32) (out float32) {
panic("implement me")
}
func (b BasicInterface2Impl3) Func2() float32 {
panic("implement me")
}
一般接口
包含类型约束的接口都被称为一般接口,无论是否包含方法。一般接口无法定义变量。
简单约束和集合操作
一般常用的是将类型约束定义在接口中,且多个类型约束接口可以进行集合操作。
type CommonInterface interface {
int | int8 | float32 | string
}
// 不能用来定义变量
//var commonInterface CommonInterface // error
type Int interface {
int | int8 | int32 | int64
}
type Float interface {
float32 | float64
}
// 并集操作
type IntAndFloat interface {
Int | Float
}
// 交集操作
type IntExceptInt8 interface {
Int
int8
}
// 空集,无实际意义
type Null interface {
int
int32
}
复杂类型约束
上述为比较简单的类型约束,一般接口也可以包含方法,也可以包含泛型。
复杂约束1:包含方法的一般接口
type CommonInterface2 interface {
~int | ~int8 | ~struct {
Data string
}
Func1() string
}
不能用来定义变量
//var c CommonInterface2 // error
如何实现或者实例化复杂接口?对上述CommonInterface2来说,需要满足以下条件。
- 底层类型为int|int8|struct{Data string}
- 有方法Func1() string
// 举例1 是CommonInterface2的实例化
type CommonInterface2_1 int
func (c CommonInterface2_1) Func1() string {
return "CommonInterface2_1"
}
// 举例2 是CommonInterface2的实例化
type CommonInterface2_2 struct {
Data string
}
func (c CommonInterface2_2) Func1() string {
return c.Data
}
// 举例3 不是CommonInterface2的实例化
type CommonInterface2_3 int32
func (c CommonInterface2_3) Func1() string {
panic("CommonInterface2_3")
}
// 针对CommonInterface2的泛型方法
func DoCommonInterface2[T CommonInterface2](t T) {
fmt.Println(t.Func1())
}
测试例子
commonInterface2_1 := CommonInterface2_1(1)
DoCommonInterface2[CommonInterface2_1](commonInterface2_1)
DoCommonInterface2(commonInterface2_1) // 类型推断
commonInterface2_2 := CommonInterface2_2{}
DoCommonInterface2(commonInterface2_2)
//commonInterface2_3 := CommonInterface2_3(1)
//DoCommonInterface2(commonInterface2_3) // error, commonInterface2_3不是CommonInterface2的实例化
复杂约束2:包含方法,且包含泛型的一般接口
type CommonInterface3[T string | float32] interface {
~int | ~int8 | ~struct {
Data T
}
Func2() T
}
不能用来定义变量。
//var a CommonInterface3[string]
如何实现或者实例化复杂接口?对上述CommonInterface3来说,需要满足以下条件。
- 底层类型为int|int8|struct{Data T} ,T的约束为string | float32
- 有方法Func2() T, T的约束为string | float32
- 如果底层类型定义的没有T, 则Func2() T的约束可以为string | float32
- 如果底层类型定义的有T, 比如struct{Data T}, 此时Func2() T,两个T需要同时为string或者同时为float32
// 举例1 CommonInterface3的实例化
type CommonInterface3Impl1 int
func (c CommonInterface3Impl1) Func2() string {
return strconv.Itoa(int(c))
}
// 举例2 CommonInterface3的实例化
type CommonInterface3Impl2 int
func (c CommonInterface3Impl2) Func2() float32 {
return float32(c)
}
// 举例3 CommonInterface3的实例化
type CommonInterface3Impl3[T string | float32] struct {
Data T
}
func (c CommonInterface3Impl3[T]) Func2() T {
return c.Data
}
// 针对CommonInterface3[string]的泛型函数
func DoCommonInterface3_1[T CommonInterface3[string]](t T) {
fmt.Println(reflect.TypeOf(t.Func2()))
}
// 针对CommonInterface3[float32]的泛型函数
func DoCommonInterface3_2[T CommonInterface3[float32]](t T) {
fmt.Println(reflect.TypeOf(t.Func2()))
}
// 针对CommonInterface3[T]的泛型函数
// 新增一个泛型D, 用来表示CommonInterface3里的泛型
func DoCommonInterface3[D string | float32, T CommonInterface3[D]](t T) {
fmt.Println(reflect.TypeOf(t.Func2()))
}
测试例子
commonInterface3_1 := CommonInterface3Impl1(1)
DoCommonInterface3_1(commonInterface3_1)
DoCommonInterface3[string](commonInterface3_1)
commonInterface3_2 := CommonInterface3Impl2(1)
DoCommonInterface3_2(commonInterface3_2)
DoCommonInterface3[float32](commonInterface3_2)
commonInterface3_3 := CommonInterface3Impl3[string]{
Data: "data",
}
DoCommonInterface3_1(commonInterface3_3)
DoCommonInterface3[string](commonInterface3_3)