形参和实参
假设我们有个计算两数之和的函数
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 []inttype Int32Slice []int32type 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 mainimport "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 = 3var int32B int32 = 4res := add(int32A, int32B)fmt.Println(res)}
4AB7
指针参数
package mainimport ("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 = 3var int32B int32 = 4res := 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))}
4AB7100 *int200 *int*stringabc 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{}] []Tvar 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的keytype Map[K comparable, V any] map[K]V
符号“~”
符号“~”,指底层类型约束,举个例子。
type Slice[T int | int32] []Ttype MyInt intvar a Slice[MyInt] // 错误
MyInt底层类型是int,但其本身并不是int,所以不能用于Slice[T int | int32]的实例化。
可以使用 ~int 的写法,表示所有以int为底层类型的类型都可以用于实例化。
type Slice[T ~int | ~int32] []Ttype MyInt intvar a Slice[MyInt] // 正确
泛型类型
类型形参不能单独使用
下面这种是无法定义泛型Generic[T]的。
type Generic[T int | int32] T
泛型数据结构
类型形参不能单独使用,需要跟其他数据结构组合起来一起定义泛型。
可以组合的有ptr、slice、array、struct、map、channel、interface。
package mainimport ("fmt""reflect")type myInt interface {~int | ~int32 | ~int64}// ptr、slice、struct、map、channel、interfacetype Ptr[T int | int32] *T // ptrtype SliceInt[T myInt] []T // slicetype MapInt[K myInt, V any] map[K]V // maptype ChannelInt[T myInt] chan T // chan// 只要满足有方法 Val() T,T的约束为 ~int | ~int32 | ~int64的都为这个接口的实现type InterfaceInt[T myInt] interface { // interfaceVal() T}// 例子1,InterfaceInt的实现type StructInt[T myInt] struct { // structData 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 int32func (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)) // intinterfaceInt = &StructInt[int]{Data: 2}val2 := interfaceInt.Val()fmt.Println(val2)fmt.Println(reflect.TypeOf(val2)) // intvar interfaceInt32 InterfaceInt[int32] = InterfaceIntImpl2(int32(2))valInt32 := interfaceInt32.Val()fmt.Println(valInt32)fmt.Println(reflect.TypeOf(valInt32)) // int32}
1int2int2int32
指针类型约束
指针类型泛型定义约束,不能直接定义,需消除歧义。
// type Test1 [T * int] []T // error 会当做 T 乘 int// 可以用逗号消除歧义type Test2[T *int,] []Ttype 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 = tswitch i.(type) {case int:default:}}
实际开发中如果用到这种,需要慎重考虑是否需要使用泛型。
泛型的出现是为了屏蔽具体类型或者避免使用反射,现在又在泛型中使用反射或者any转换获得具体类型,这种做法是不合适的。
泛型函数
简单泛型函数
比较常用的泛型函数形式如下。
func Add[T int | int32](a, b T) T {return a + b}// 实例化使用Add[int](1,1) // 声明实例化类型为intAdd(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 testfilterTest := Filter[int]([]int{1, 2, 3, 4, 5}, func(i int) bool {if i > 3 {return true}return false})fmt.Println(filterTest)// map testmapTest := Map[int, string]([]int{1, 2, 3}, func(i int) string {return "str" + strconv.Itoa(i)})fmt.Println(mapTest)// reduce testreduceTest := 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() stringAge() 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]] []Ttype 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 // errortype Int interface {int | int8 | int32 | int64}type Float interface {float32 | float64}// 并集操作type IntAndFloat interface {Int | Float}// 交集操作type IntExceptInt8 interface {Intint8}// 空集,无实际意义type Null interface {intint32}
复杂类型约束
上述为比较简单的类型约束,一般接口也可以包含方法,也可以包含泛型。
复杂约束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 intfunc (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 int32func (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 intfunc (c CommonInterface3Impl1) Func2() string {return strconv.Itoa(int(c))}// 举例2 CommonInterface3的实例化type CommonInterface3Impl2 intfunc (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)
