首页 > golang > golang1.18新增泛型详解
2020
08-06

golang1.18新增泛型详解

(一)如何对泛型进行输出

下面的例子是一个对泛型输出的基本例子。函数可以有一个额外的类型参数列表,它使用方括号,但看起来像一个普通的参数列表:func F[T any](p T) { ... },

代码中的[T any]即为类型参数,意思是该函数支持任何T类型,当我们调用printSlice[string]([]string{“Hello”,“World”})时,会被类型推导为string类型,不过在编译器完全可以实现类型推导时,也可以省略显式类型

如:printSlice([]string{“Hello”,“World”}) ,这样也将会是对的;

package main

import "fmt"

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{"Hello", "World"})
   printSlice[int64]([]int64{5, 4, 3, 2, 1})
}

输出为

1 2 3 4 5 
1.01 2.02 3.03 4.04 5.05
Hello World
5 4 3 2 1


(二)如何用泛型约束使用的类型范围

这个例子包含了一个类型约束。每个类型参数都有一个类型约束,就像每个普通参数都有一个类型:func F[T Constraint](p T) { ... },

类型约束是接口类型。该提案扩展了interface语法,新增了类型列表(type list)表达方式,专用于对类型参数进行约束。

package main

import (
   "fmt"
)

type Addable interface {
   int | int8 | int16 | int32 | int64 | uint | uintptr | float32 | complex64 | string
}

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

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

输出为:

3
helloworld


比如下面的add函数的类型参数T没有任何约束,它可以被实例化为任何类型;那么这些实例化后的类型是否都支持+操作符运算呢?显然不是;因此,报错了!对于没有任何约束的类型参数实例,允许对其进行的操作包括:

声明这些类型的变量。
使用相同类型的值为这些变量赋值。
将这些类型的变量以实参形式传给函数或从作为函数返回值。
取这些变量的地址。
将这些类型的值转换或赋值给interface{}类型变量。
通过类型断言将一个接口值赋值给这类类型的变量。
在type switch块中作为一个case分支。
定义和使用由该类型组成的复合类型,比如:元素类型为该类型的切片。
将该类型传递给一些内置函数,比如new。

这就意味着,如果不用interface约束,直接使用的话,你将得到如下的结果:

package main

import (
   "fmt"
)

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

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

报错为:

# command-line-arguments
.\test.go:8:9: invalid operation: operator + not defined on a (variable of type T constrained by any)

在约束里,甚至可以放进去接口如下:

package main

import (
   "fmt"
)

type Addable interface {
   int | interface{}
}

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

func main() {
   fmt.Println(add(1))
}

接着假如我们去掉string,如下代码所示。以该示例为例,如果编译器通过类型推导得到的类型不在这个接口定义的类型约束列表中,那么编译器将允许这个类型参数实例化;否则就像类型参数实例化将报错!

package main

import (
   "fmt"
)

type Addable interface {
   int | int8 | int16 | int32 | int64 | uint | uintptr | float32 | complex64
}

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

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

报错为:

.\test.go:17:17: string does not implement Addable

注意:我们自己定义的带有类型列表的接口将无法用作接口变量类型,如下代码将会报错

package main

type MyType interface {
   int
}

func main() {
   var n int = 6
   var i MyType
   i = n
   _ = i
}

报错为:

.\test.go:9:8: interface contains type constraints


(三)泛型中的接口本身对范型进行约束

package main

import (
   "fmt"
   "strconv"
)

type MyStringer interface {
   String() string
}

type StringInt int
type myString string

func (i StringInt) String() string {
   return strconv.Itoa(int(i))
}

func (str myString) String() string {
   return string(str)
}

func stringify[T MyStringer](s []T) (ret []string) {
   for _, v := range s {
      ret = append(ret, v.String())
   }
   return ret
}

func stringify2[T MyStringer](s []T) (ret []string) {
   for _, v := range s {
      ret = append(ret, v.String())
   }
   return ret
}

func main() {
   fmt.Println(stringify([]StringInt{1, 2, 3, 4, 5}))
   fmt.Println(stringify2([]myString{"1", "2", "3", "4", "5"}))
}

输出为:

[1 2 3 4 5]
[1 2 3 4 5]

代码中我们声明了MyStringer接口,并且使用StringInt和myString类型实现了此接口;在范型方法中,我们声明了范型的类型为:任意实现了MyStringer接口的类型;只要实现了这个接口,那么你就可以直接使用,在现在某些需要传interface{}作为参数的函数里面,可以直接指定类型了。当你改为如下代码时

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

会报错:

.\test.go:38:23: int does not implement MyStringer (missing String method)

只有实现了Stringer接口的类型才会被允许作为实参传递给Stringify泛型函数的类型参数并成功实例化!当然也可以将MyStringer接口写成如下的形式:

package main

import (
   "fmt"
   "strconv"
)

type StringInt int

func (i StringInt) String() string {
   return strconv.Itoa(int(i))
}

func stringify3[T MySignedStringer](s []T) (ret []string) {
   for _, v := range s {
      ret = append(ret, v.String())
   }
   return ret
}

type MySignedStringer interface {
   ~int | ~int8 | ~int16 | ~int32 | ~int64
   String() string
}

func main() {
   fmt.Println(stringify3([]StringInt{1, 2, 3, 4, 5}))
}

表示只有int, int8, int16, int32, int64,这样类型参数的实参类型既要在MySignedStringer的类型列表中,也要实现了MySignedStringer的String方法,才能使用。像这种不在里面的type StringInt uint就会报错。


本文》有 0 条评论

留下一个回复