千家信息网

如何理解Go泛型和非泛型代码

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,本篇内容主要讲解"如何理解Go泛型和非泛型代码",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"如何理解Go泛型和非泛型代码"吧!目录1. 开启泛型2.无泛型
千家信息网最后更新 2026年11月02日如何理解Go泛型和非泛型代码

本篇内容主要讲解"如何理解Go泛型和非泛型代码",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"如何理解Go泛型和非泛型代码"吧!

目录
  • 1. 开启泛型

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

    • 2.1. AddSlice

    • 2.2. 带方法的约束 StringConstraint

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 interface {   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 interface {   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 interface {   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 interface {   type int, string   String() string }

这样满足这个约束的类型集合就是底层类型是 int 或者 string,并且,具有 String() string 方法的类型。而这个类型集合就是 type int, string 的类型集合与 String() string 的类型集合的交集。具体的概念后续介绍。

这样,MyFloat、MyUint 就无法调用 ConvertSliceToStrSlice 这个函数了。

 package main  import (   "fmt" )  type StringConstraint interface {   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泛型和非泛型代码"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

类型 函数 代码 条件 就是 方法 参数 接口 只有 两个 底层 编译 操作符 概念 面的 内容 可以通过 字符 字符串 实际 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 科技股互联网 互联网科技开发公司 杭州互联网科技公司 互联网科技类展会 广州优美互联网科技 欧洲十大科技互联网公司 互联网企业和科技型企业 重庆互联网医院沙龙医盟信息科技 哈尔滨屹滨互联网科技有限公司 互联网金融科技展 如何创新互联网科技 佛山市翊博互联网络科技有限公司 明优互联网科技有限公司 互联网科技大赛获奖感言 上海京穆互联网科技有限公司 新氧科技和美国互联网有关系吗 版纳互联网科技选哪家 上海旺乐尼互联网科技有限公司 山东亿川互联网科技有限责任公司 小牛互联网科技有限公司地址 互联网科技未来 互联网科技公司做什么 湖南洣水互联网科技有限公司扣款 宁波青枫互联网科技有限公司 互联网在线教育科技创业 广东鹅鹅鹅互联网科技有限公司 上海同邻互联网科技有限公司规模 银川新华互联网科技学校是国营吗 平安好医生打造互联网科技医疗 恒生科技互联网指数十大成分股
0