首页 >> 百科

Go 泛型和非泛型代码详解

  • 百科
  • 2026-07-20 21:00:11
  • 点击次数:26

1. 开启泛型

在 Go1.17 版本中  ,泛型泛型可以经由过程:

export GOFLAGS="-gcflags=-G=3"

或在编译运转法度圭表类型时加上:

go run -gcflags=-G=3 main.go

2.无泛型代码和泛型代码

2.1. AddSlice

起首看如今没有泛型的和非代码: 

package main ​ import (   "fmt" ) ​ func AddIntSlice(input []int, diff int) []int {    output := make([]int, 0, len(input))   for _, item := range input {      output = append(output, item+diff)   }   return output } ​ func AddStrSlice(input []string, diff string) []string {    output := make([]string, 0, len(input))   for _, item := range input {      output = append(output, item+diff)   }   return output } ​ func main() {    intSlice := []int{ 1, 2, 3, 4, 5, 6}   fmt.Printf("intSlice [%+v] + 2 = [%+v]\n", intSlice, AddIntSlice(intSlice, 2)) ​   strSlice := []string{ "hi,", "hello,", "bye,"}   fmt.Printf("strSlice [%+v] + man = [%+v]\n", strSlice, AddStrSlice(strSlice, "man")) } //output //intSlice [[1 2 3 4 5 6]] + 2 = [[3 4 5 6 7 8]] //strSlice [[hi, hello, bye,]] + man = [[hi,man hello,man bye,man]]

上面没无益用泛型的代码中,对 intSlice 和 strSlice ,代码需求构造两个函数对它们举办措置;而假定后续另有 float64 、详解uint32 等圭表类型就需求更多地 Add...Slice 函数。泛型泛型

而假定独霸泛型此后 ,和非这些 Add...Slice 函数便可以回并为一个函数了 ,代码在这个函数中,详解对那些可独霸 + 独霸符的泛型泛型圭表类型举办加独霸(非论是数学的加仍是字符串的邻接)。

泛型代码以下:

package main ​ import (   "fmt" ) ​ type PlusConstraint inte***ce {    type int,和非 string } ​ func AddSlice[T PlusConstraint](input []T, diff T) []T {    output := make([]T, 0, len(input))   for _, item := range input {      output = append(output, item+diff)   }   return output } ​ func main() {    intSlice := []int{ 1, 2, 3, 4, 5}   fmt.Printf("intSlice [%+v] + 2 = [%v]\n", intSlice, AddSlice(intSlice, 2)) ​   strSlice := []string{ "hi,", "hello,", "bye,"}   fmt.Printf("strSlice [%v] + man = [%v]\n", strSlice, AddSlice(strSlice, "man")) } //output //intSlice [[1 2 3 4 5]] + 2 = [[3 4 5 6 7]] //strSlice [[hi, hello, bye,]] + man = [[hi,man hello,man bye,man]]

是不是是超等复杂 ,可是代码 AddSlice 函数中引进了束厄狭隘的定见,即 PlusConstraint。详解AddSlice 的泛型泛型方括号中是圭表类型参数 ,T 就是和非这个圭表类型参数的形参 ,后背的代码 PlusConstraint 就是 T 的束厄狭隘前提 ,意思是只需知足束厄狭隘前提的 T 圭表类型才可以在这个函数中独霸 。

AddSlice 后背圆括号中的参数是常例参数也称为非圭表类型参数,它们可以不制订具体圭表类型(int、string 等),可独霸 T 来庖代 。

而在 AddSlice 中 ,对 T 圭表类型的值 item,它会将 item 和 diff 举办 + 独霸 ,多是数学上的累加,也多是字符串的邻接。

那如今你大年夜大年夜约要问了,T 圭表类型就必定是支撑 + 独霸符的吗 ,有没有多是一个 struct 呢?

谜底是:不成能 。

后面说过 ,只需知足束厄狭隘前提的 T 才可以在 AddSlice 中独霸,而束厄狭隘前提就是上面的 PlusConstraint 。

PlusConstraint 定义的编制和接口圭表类型的定义是一样的,只不过内部多了一行:

type int, string

这句话就是说,只需 int、string 这两个圭表类型才知足这个束厄狭隘,这里触及到圭表类型集的定见 ,后续会提到。

是以,有了这个束厄狭隘前提,传进到 AddSlice 的参数 input 和 diff 都是可独霸 + 独霸符的。假定你的 AddSlice 函数中想传进 float46、uint64 等圭表类型 ,就在 PlusConstraint 中加上这两个圭表类型便可 。

上面的代码中,只是对 int 和 string 两种根本圭表类型举办束厄狭隘 。理论斥地中 ,我们大年夜大年夜约会定义本身的圭表类型:

type MyInt int type MyStr string

那假定在 AddSlice 中独霸这两种圭表类型可以编译经由过程吗  ?谜底是可以的。在泛型草案中,这类气候是没法编译经由过程的 ,需求在束厄狭隘前提中添加~int | ~string ,展示底层圭表类型是 int 或 string 的圭表类型。而在 Go1.17 中 ,上面的 PlusConstraint 就包含了 int、string、和以这二者为底层圭表类型的圭表类型 。

package main ​ import (   "fmt" ) ​ type MyInt int type MyStr string ​ type PlusConstraint inte***ce {    type int, string } ​ func AddSlice[T PlusConstraint](input []T, diff T) []T {    output := make([]T, 0, len(input))   for _, item := range input {      output = append(output, item+diff) ​   }   return output ​ } ​ func main() {    intSlice := []MyInt{ 1, 2, 3, 4, 5}   fmt.Printf("intSlice [%+v] + 2 = [%v]\n", intSlice, AddSlice(intSlice, 2)) ​   strSlice := []MyStr{ "hi,", "hello,", "bye,"}   fmt.Printf("strSlice [%v] + man = [%v]\n", strSlice, AddSlice(strSlice, "man")) ​ } //output //intSlice [[1 2 3 4 5]] + 2 = [[3 4 5 6 7]] //strSlice [[hi, hello, bye,]] + man = [[hi,man hello,man bye,man]]

2.2. 带编制的束厄狭隘 StringConstraint

后面说到,束厄狭隘的定义和接口很像,那假定束厄狭隘中有编制呢,那不就是妥妥的接口吗 ?

二者仍是有分辨的  :

  • 接口的成员只需编制和内嵌的接口圭表类型
  • 束厄狭隘的成员有编制、内嵌束厄狭隘圭表类型 、圭表类型(int、string等)

看上面一个没无益用泛型的例子:

package main ​ import (   "fmt" ) ​ func ConvertSliceToStrSlice(input []fmt.Stringer) []string {    output := make([]string, 0, len(input))   for _, item := range input {      output = append(output, item.String())   }   return output } ​ type MyInt int ​ func (mi MyInt) String() string {    return fmt.Sprintf("[%d]th", mi) } func ConvertIntSliceToStrSlice(input []MyInt) []string {    output := make([]string, 0, len(input))   for _, item := range input {      output = append(output, item.String())   }   return output } ​ type MyStr string ​ func (ms MyStr) String() string {    return string(ms) + "!!!" } func ConvertStrSliceToStrSlice(input []MyStr) []string {    output := make([]string, 0, len(input))   for _, item := range input {      output = append(output, item.String())   }   return output } func main() {    intSlice := []MyInt{ 1, 2, 3, 4}   // compile error, []MyInt not match []fmt.Stringer   //fmt.Printf("%v convert %v", intSlice, ConvertSliceToStrSlice(intSlice)) ​   fmt.Printf("%v convertIntToStr %v \n", intSlice, ConvertIntSliceToStrSlice(intSlice)) ​   strSlice := []MyStr{ "111", "222", "333"}   fmt.Printf("%v convertStrToStr %v \n", strSlice, ConvertStrSliceToStrSlice(strSlice))   // output   //[[1]th [2]th [3]th [4]th] convertIntToStr [[1]th [2]th [3]th [4]th]   //[111!!! 222!!! 333!!!] convertStrToStr [111!!! 222!!! 333!!!] }

上面代码中 ,MyInt 和 MyStr 都完成了 fmt.Stringer 接口,可是两个都没法调用 ConvertSliceToStrSlice 函数,因为它的进参是 []fmt.Stringer 圭表类型 ,[]MyInt 和它不婚配 ,这在编译的时辰就是会报错的,而假定我们想要把[]MyInt 转换为 []string ,就需求定义一个进参为[]MyInt 的函数,如 ConvertIntSliceToStrSlice;对 []MyStr,则需求此外一个函数。。。那光鲜较着二者都完成了 fmt.Stringer,幻想上理应都可以经由过程 ConvertSliceToStrSlice 啊 ,这也太***了 。

哈哈 ,泛型完成了这个下场 。

 

package main ​ import (   "fmt" ) ​ type StringConstraint inte***ce {    String() string } ​ func ConvertSliceToStrSlice[T StringConstraint](input []T) []string {    output := make([]string, 0, len(input))   for _, item := range input {      output = append(output, item.String())   }   return output } ​ type MyInt int ​ func (mi MyInt) String() string {    return fmt.Sprintf("[%d]th", mi) } ​ type MyStr string ​ func (ms MyStr) String() string {    return string(ms) + "!!!" } func main() {    intSlice := []MyInt{ 1, 2, 3, 4}   // compile error, []MyInt not match []fmt.Stringer   fmt.Printf("%v convert %v\n", intSlice, ConvertSliceToStrSlice(intSlice)) ​ ​   strSlice := []MyStr{ "111", "222", "333"}   fmt.Printf("%v convert %v\n", strSlice, ConvertSliceToStrSlice(strSlice))   // output   //[[1]th [2]th [3]th [4]th] convert [[1]th [2]th [3]th [4]th]   //[111!!! 222!!! 333!!!] convert [111!!! 222!!! 333!!!] }

复杂吧,在 StringConstraint 束厄狭隘中定义一个 String() string,多么只需有这浅近例的圭表类型都可以作为 T 在 ConvertSliceToStrSlice 独霸。在这个束厄狭隘前提下 ,全数具有 String() string 编制的圭表类型都可以举办转换 ,可是我们假定想把束厄狭隘前提定的愈加苛刻,比如只需底层圭表类型为 int 或 string 的圭表类型才可以调用这个函数 。 那么我们可以进一步在 StringConstraint 中添加束厄狭隘前提:

type StringConstraint inte***ce {    type int, string   String() string }

多么知足这个束厄狭隘的圭表类型集结就是底层圭表类型是 int 或 string ,并且,具有 String() string 编制的圭表类型 。而这个圭表类型集结就是 type int, string 的圭表类型集结与 String() string 的圭表类型集结的交集。具体的定见后续引见。

多么 ,MyFloat、MyUint 就没法调用 ConvertSliceToStrSlice 这个函数了 。

package main ​ import (   "fmt" ) ​ type StringConstraint inte***ce {    type int, string   String() string } ​ func ConvertSliceToStrSlice[T StringConstraint](input []T) []string {    output := make([]string, 0, len(input))   for _, item := range input {      output = append(output, item.String())   }   return output } ​ type MyFloat float64 ​ func (mf MyFloat) String() string {    return fmt.Sprintf("%fth", mf) } ​ type MyInt int ​ func (mi MyInt) String() string {    return fmt.Sprintf("[%d]th", mi) } ​ type MyStr string ​ func (ms MyStr) String() string {    return string(ms) + "!!!" } func main() {    intSlice := []MyInt{ 1, 2, 3, 4}   // compile error, []MyInt not match []fmt.Stringer   fmt.Printf("%v convert %v\n", intSlice, ConvertSliceToStrSlice(intSlice)) ​   strSlice := []MyStr{ "111", "222", "333"}   fmt.Printf("%v convert %v\n", strSlice, ConvertSliceToStrSlice(strSlice))   // output   //[[1]th [2]th [3]th [4]th] convert [[1]th [2]th [3]th [4]th]   //[111!!! 222!!! 333!!!] convert [111!!! 222!!! 333!!!]   floatSlice := []MyFloat{ 1.1, 2.2, 3.3}   //type checking failed for main   //prog.go2:48:44: MyFloat does not satisfy StringConstraint (MyFloat or float64 not found in int, string) ​   fmt.Printf("%v convert %v\n", floatSlice, ConvertSliceToStrSlice(floatSlice)) }

小结:

总的来讲  ,泛型可以简化代码的编写 ,同时在编译时举办圭表类型搜检  ,假定圭表类型不知足束厄狭隘 ,就会在编译时报错;多么就阻拦了运转时不成控的偏向了。

到此这篇关于Go 泛型和非泛型代码详解的文章就引见到这了,更多相干Go 泛型和非泛型代码内容请搜刮完竣下载之前的文章或延续不雅不雅不雅不雅鉴赏上面的相干文章希看大年夜师往后多多支撑完竣下载 !

本文由作文网百科栏目发布,感谢您对作文网的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人站长或者朋友圈,但转载请说明文章出处“Go 泛型和非泛型代码详解