持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情
泛型允许我们的函数或数据结构采用以通用形式定义的几种类型。
为了真正理解这意味着什么,让我们看看一个非常简单的案例。
假设你需要做一个函数,您可以编写以下类型的函数:
func Print(s []string) {
for _, v := range s {
fmt.Print(v)
}
}
很简单,对吧?如果我们想让切片成为一个整数呢?您需要为此制定一个新方法:
func Print(s []int) {
for _, v := range s {
fmt.Print(v)
}
}
这些解决方案可能看起来是多余的,因为我们只是在更改参数。但目前,这就是我们在Go中解决这个问题的方式,而无需将其转换为某些接口。
现在,有了泛型,它们将允许我们像这样声明我们的函数:
func Print[T any](s []T) {
for _, v := range s {
fmt.Print(v)
}
}
在上述函数中,我们声明了两件事:
- 我们有T,这是
any
关键字的类型(此关键字被专门定义为泛型的一部分,它表示任何类型) - 还有我们的参数,其中我们有变量
s
,其类型是T
的切片。
我们现在可以这样调用我们的方法:
func main() {
Print([]string{"Hello, ", "playground\n"})
Print([]int{1,2,3})
}
这只是泛型最基本的实现之一。但到目前为止,它看起来还不错。
泛型局限性
我们已经看到了泛型能做什么。他们让我们指定一个可以接受任何类型参数的函数。
但我之前给出的例子很简单。泛型能带是有限制的。例如,打印非常简单,因为Golang可以打印出扔进去的任何类型的变量。
如果我们想做更复杂的事情呢?假设我们已经为结构定义了自己的方法,并希望称之为:
package main
import (
"fmt"
)
type worker string
func (w worker) Work(){
fmt.Printf("%s is working\n", w)
}
func DoWork[T any](things []T) {
for _, v := range things {
v.Work()
}
}
func main() {
var a,b,c worker
a = "A"
b = "B"
c = "C"
DoWork([]worker{a,b,c})
}
你会得到这个:
type checking failed for main
prog.go2:25:11: v.Work undefined (type bound for T has no method Work)
它无法运行,因为函数内部处理的切片是any
类型,并且它没有实现方法Work
,这使得它无法运行。
然而,我们实际上可以通过使用界面来让它工作:
package main
import (
"fmt"
)
type Person interface {
Work()
}
type worker string
func (w worker) Work(){
fmt.Printf("%s is working\n", w)
}
func DoWork[T Person](things []T) {
for _, v := range things {
v.Work()
}
}
func main() {
var a,b,c worker
a = "A"
b = "B"
c = "C"
DoWork([]worker{a,b,c})
}
它将打印出这个:
A is working
B is working
C is working
好吧,它适用于接口,但只是一个没有泛型的接口也很好:
package main
import (
"fmt"
)
type Person interface {
Work()
}
type worker string
func (w worker) Work(){
fmt.Printf("%s is working\n", w)
}
func DoWorkInterface(things []Person) {
for _, v := range things {
v.Work()
}
}
func main() {
var d,e,f worker
d = "D"
e = "E"
f = "F"
DoWorkInterface([]Person{d,e,f})
}
这将给我们以下结果:
D is working
E is working
F is working
使用泛型只会为我们的代码添加额外的逻辑。因此,如果仅使用接口就足够了,我认为没有任何理由向代码中添加泛型。
泛型仍处于早期的开发阶段,它们仍然有进行复杂处理的局限性。
约束
早些时候,我们发现了any
类型的通用约束。除了这种类型,我们还可以使用其他几个约束。
其中一个约束是comparable
的。让我们看看它是如何工作的:
func Equal[T comparable](a, b T) bool {
return a == b
}
func main() {
Equal("a","a")
}
除此之外,我们还可以尝试像这样进行自己的约束:
package main
import(
"fmt"
)
type Number interface {
type int, float64
}
func MultiplyTen[T Number](a T) T{
return a*10
}
func main() {
fmt.Println(MultiplyTen(10))
fmt.Println(MultiplyTen(5.55))
}
我认为这很整洁——我们可以为一个简单的数学表达式有一个函数。通常,我们最终会制作两个函数来接受它,或者我们会使用反射,所以我们只写一个函数。
虽然这很酷,但我们仍然需要进行相当多的实验来制定自己的约束。现在知道他们的局限性还为时过早。我们应该小心不要滥用它,只有在我们真正确定需要它时才使用它。
除了将泛型用作函数的一部分外,您还可以将它们声明为以下变量:
type GenericSlice[T any] []T
您可以将其用作函数中的参数,也可以用该类型制作方法:
func (g GenericSlice[T]) Print() {
for _, v := range g {
fmt.Println(v)
}
}
func Print [T any](g GenericSlice[T]) {
for _, v := range g {
fmt.Println(v)
}
}
func main() {
g := GenericSlice[int]{1,2,3}
g.Print() //1 2 3
Print(g) //1 2 3
}
使用情况因您的需求而异。我只能说,我们仍然需要更多地尝试泛型,看看哪些用例最有效。
结论
感谢您你的阅读!!!