Go 泛型和非泛型代码详解
来源:脚本之家
时间:2022-12-27 20:10:46 138浏览 收藏
知识点掌握了,还需要不断练习才能熟练运用。下面golang学习网给大家带来一个Golang开发实战,手把手教大家学习《Go 泛型和非泛型代码详解》,在实现功能的过程中也带大家重新温习相关知识点,温故而知新,回头看看说不定又有不一样的感悟!
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)) }
小结:
总的来说,泛型可以简化代码的编写,同时在编译时进行类型检查,如果类型不满足约束,就会在编译时报错;这样就避免了运行时不可控的错误了。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于Golang的相关知识,也可关注golang学习网公众号。
-
234 收藏
-
346 收藏
-
131 收藏
-
185 收藏
-
265 收藏
-
450 收藏
-
330 收藏
-
193 收藏
-
271 收藏
-
414 收藏
-
179 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习