目录
(六)如何使用Golang泛型自带的comparable约束
(一)如何对泛型进行输出
下面的例子是一个对泛型输出的基本例子。函数可以有一个额外的类型参数列表,它使用方括号,但看起来像一个普通的参数列表: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,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 {
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"))
}
输出为:
3
helloworld
在官方的最新proposal里有提到,在Golang中,并不是所有的类型都满足+号运算。在1.17的版本中,泛型函数只能使用类型参数所能实例化出的任意类型都能支持的操作。
比如下面的add函数的类型参数T没有任何约束,它可以被实例化为任何类型;那么这些实例化后的类型是否都支持+操作符运算呢?显然不是;因此,报错了!对于没有任何约束的类型参数实例,允许对其进行的操作包括:
-
声明这些类型的变量。
-
使用相同类型的值为这些变量赋值。
-
取这些变量的地址。
-
将这些类型的值转换或赋值给interface{}类型变量。
-
通过类型断言将一个接口值赋值给这类类型的变量。
-
在type switch块中作为一个case分支。
-
定义和使用由该类型组成的复合类型,比如:元素类型为该类型的切片。
-
将该类型传递给一些内置函数,比如new。
这就意味着,如果不用interface约束,直接使用的话,你将得到如下的结果:
package main
import (
"fmt"
)
func add[T any] (a,"world"))
}
输出为:
type checking Failed for main
prog.go2:8:9: invalid operation: operator + not defined for a (variable of type parameter type T)
在约束里,甚至可以放进去接口如下:
package main
import (
"fmt"
)
type Addable interface {
type 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 {
type int,complex128
}
func add[T Addable] (a,"world"))
}
输出为:
type checking Failed for main
prog.go2:19:14: string does not satisfy Addable (string or string not found in int,complex128)
注意:我们自己定义的带有类型列表的接口将无法用作接口变量类型,如下代码将会报错
package main
type MyType interface {
type int
}
func main() {
var n int = 6
var i MyType
i = n
_ = i
}
输出为:
type checking Failed for main
prog.go2:9:8: interface contains type constraints (int)
(三)泛型中的接口本身对范型进行约束
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.String())
}
return ret
}
func main() {
fmt.Println(stringify([]StringInt{1,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,5}))
}
会报错:
type checking Failed for main
prog.go2:27:14: int does not satisfy MyStringer (missing method String)
只有实现了Stringer接口的类型才会被允许作为实参传递给Stringify泛型函数的类型参数并成功实例化!当然也可以将MyStringer接口写成如下的形式:
type MySignedStringer interface {
type int,int64
String() string
}
表示只有int,int64,这样类型参数的实参类型既要在MySignedStringer的类型列表中,也要实现了MySignedStringer的String方法,才能使用。像这种不在里面的type StringInt uint就会报错。
(四)泛型中如何操作切片
可以看到在下面的例子里面,我们声明了一个可以存放任何类型的切片,叫做slice,如type slice[T any] []T。和泛型函数一样,使用泛型类型时,首先要对其进行实例化,即显式为类型参数赋值类型。如果在类型定义时,将代码改成vs:=slice{5,1},那么你会得到如note1中的结果。因为编译器并没有办法进行类型推导,也就是表示它并不知道,你输出的是那种类型。哪怕你在interface里面定义了约束。哪怕你在接口中定义了类型约束type int,string,同样会报错,如note2所示。
package main
import (
"fmt"
)
type slice[T any] []T
/*
type any interface {
type int,string
}*/
func printSlice[T any](s []T) {
for _,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,1}
printSlice(vs)
vs2 := slice[string]{"hello","world"}
printSlice(vs2)
}
输出为:
5 4 2 1
hello world
(五)如何利用泛型实现最大值最小值函数
package main
import (
"fmt"
)
type minmax interface {
type int,float64
}
func max[T minmax](a []T) T {
m := a[0]
for _,v := range a {
if m < v {
m = v
}
}
return m
}
func min[T minmax](a []T) T {
m := a[0]
for _,v := range a {
if m > v {
m = v
}
}
return m
}
func main() {
vi := []int{1,5,6,7,8,9,10}
result := max(vi)
fmt.Println(result)
vi = []int{1,10}
result = min(vi)
fmt.Println(result)
}
输出为:
10
1
(六)如何使用Golang泛型自带的comparable约束
当你写成如下代码时,便会报错:
package main
import (
"fmt"
)
func findFunc[T any](a []T,v T) int {
for i,e := range a {
if e == v {
return i
}
}
return -1
}
func main() {
fmt.Println(findFunc([]int{1,6},5))
}
输出为:
type checking Failed for main
prog.go2:9:6: cannot compare e == v (operator == not defined for T)
因为不是所有的类型都可以==比较,所以Golang内置提供了一个comparable约束,表示可比较的。参考下面代码:
package main
import (
"fmt"
)
func findFunc[T comparable](a []T,5))
}
输出为:
4
(七)如何在泛型中操作指针
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)
}
输出为:
foo
123
234
(八)Golang泛型中如何操作map
在现实开发过程中,我们往往需要对slice中数据的每个值进行单独的处理,比如说需要对其中数值转换为平方值,在泛型中,我们可以抽取部分重复逻辑作为map函数:
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,6}
vs := mapFunc(vi,func(v int) string {
return "<" + fmt.Sprint(v*v) + ">"
})
fmt.Println(vs)
}
输出为:
[<1> <4> <9> <16> <25> <36>]
(九)如何在Golang泛型中使用队列操作
在现实开发过程中,我们有可能会需要一个队列去处理一些数据,在泛型中,我们可以抽取部分重复逻辑来实现
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())
}
输出为:
&[5 6]
5 true
6 true
0 false
(十)Golang泛型中新加入的一些约束包
官方也引入了一些官方包来方面泛型的使用,具体如下:
// constraints 定义了一组与类型参数一起使用的约束
package constraints
// Signed是允许任何有符号整数类型的约束。
type Signed interface { ... }
// Unsigned是允许任何无符号整数类型的约束。
type Unsigned interface { ... }
// Integer是允许任何整数类型的约束。
type Integer interface { ... }
// Float是一个允许任何浮点类型的约束。
type Float interface { ... }
// Complex是允许任何复杂数值类型的约束。
type Complex interface { ... }
// Ordered是一个约束,允许任何有序类型:任何支持操作符< <= >= >的类型。
type Ordered interface { ... }
使用方式示例如下:
package main
import (
"constraints"
"fmt"
)
type v[T constraints.Ordered] T
type Vector[T constraints.Ordered] struct {
x,y T
}
func (v *Vector[T]) Add(x,y T) {
v.x += T(x)
v.y += T(y)
}
func (v *Vector[T]) String() string {
return fmt.Sprintf("{x: %v,y: %v}",v.x,v.y)
}
func NewVector[T constraints.Ordered](x,y T) *Vector[T] {
return &Vector[T]{x: x,y: y}
}
func main() {
v := NewVector[float64](1,2)
v.Add(2,3)
fmt.Println(v)
}
总结
尽管最新的proposal冗长而详尽,但总结起来如下:
-
此设计完全向后兼容,但建议对func F(x(T))的含义进行更改。
适用性
此外,标准库中将会引入一系列新的package。
-
一个新slices包将会被引入,它与现存的bytes和strings包类似,用于操作任何类型元素的slice。新的maps和chans包将会提供一些简单的算法,用于操作任意类型的元素。set包也会被引入。
-
诸如container/list,container/ring这些包,或者是诸如sync.Map,sync/atomic.Value之类,将会升级到编译时类型安全(使用新的名字或新的版本 )。
-
可能会开发新的特殊容器,这些容器是编译时类型安全的,也可能会增加泛型的sort包。
复杂性
Golang的一大优点是它的简单性。显然,这种设计使语言更加复杂,对于泛型推出,无论采用什么技术,都会增加Golang的复杂性,提升其学习门槛,代码的可读性也可能会下降,官方对其增加的复杂性的解释如下:
效率
官方目前尚不清楚人们期望从通用代码中获得什么样的效率,他们将其划分为泛型函数和泛型类型。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。