当前位置:网站首页>golang中使用泛型
golang中使用泛型
2022-07-31 12:28:00 【m0_67401382】
泛型支持
go >= 1.18
为什么需要泛型
编写一个函数用来比较两个数的大小,对于golang这种强类型的语言,要么针对不同的类型分别实现一遍,要么使用 interface{}
类型。
func CompareInt64(a, b int64) bool {
if a >= b {
return true
} else {
return false
}
}
func CompareFloat64(a, b float64) bool {
if a >= b {
return true
} else {
return false
}
}
func Compare(a, b interface{}) bool {
switch a.(type) {
case int64:
a1 := a.(int64)
b1 := b.(int64)
if a1 >= b1 {
return true
} else {
return false
}
case float64:
a1 := a.(float64)
b1 := b.(float64)
if a1 >= b1 {
return true
} else {
return false
}
default:
return false
}
}
显然 interface{}
类型会打破强类型的约束,使程序暴露更多的错误。
如果引入泛型就可以将 CompareInt64
和 CompareFloat64
整合成一个函数。
定义泛型函数
golang支持泛型函数和泛型类型。
// 泛型函数
func GenericFunc[T any](args T) {
}
[T any]
为类型约束,any
表示任意类型,(args T)
为参数。
如果只想支持特定的几个类型可以这样写。
func GenericFunc[T int64|float64](args T) {
}
如果类型太多了,可以这样表示
type Number interface{
int | int32 | int64 | float64 | float32
}
func GenericFunc[T Number](args T) {
}
有时候需要类型可以进行算数运算,但是any
中有些类型又是不支持的,因此需要用到编译器内置的约束 comparable
,该类型必须支持==
方法。
func GenericFunc[T comparable](args T) {
}
如果使用了自定义的类型
type MyInt int8
func GenericFunc[T int64|float64|~int8](args T) {
}
如果只想支持一个类型,那就不需要使用泛型了。
最终改造为
func Compare[T int64|float64](a, b T) bool {
if a >= b {
return true
} else {
return false
}
}
多个参数多个类型
func GenericFunc[A int64|float64, B int32|float32](a A, b B) {
}
调用泛型函数
在调用的时候一定要传入具体类型,或者依靠类型推断
var a int64
a = 10
GenericFunc[int64](a)
GenericFunc(a)
定义泛型类型
指的是复合类型,它包含了泛型。
type KvMap[K comparable, V Number] map[K]V
func (kv KvMap[K,V]) Set(k K, v V) (KvMap[K,V]) {
kv[k] = v
return kv
}
type Slice[V Number] []V
func (s Slice[V]) Append(v V) (Slice[V]) {
s = append(s, v)
return s
}
type Kv [Vt Number] struct {
K string
V Vt
}
比如定义一个结构体,使其可以满足 Number
类型
type Stack[V Number] struct {
size int
value []V
}
func (s *Stack[V]) Push(v V) {
s.value = append(s.value, v)
s.size++
}
func (s *Stack[V]) Pop() V {
e := s.value[s.size-1]
if s.size != 0 {
s.value = s.value[:s.size-1]
s.size--
}
return e
}
调用泛型类型
// INT STACK
strS := &Stack[int64]{}
strS.Push(1)
strS.Push(2)
strS.Push(3)
fmt.Println(strS.Pop())
fmt.Println(strS.Pop())
fmt.Println(strS.Pop())
// FLOAT STACK
floatS := &Stack[float64]{}
floatS.Push(1.1)
floatS.Push(2.2)
floatS.Push(3.3)
fmt.Println(floatS.Pop())
fmt.Println(floatS.Pop())
fmt.Println(floatS.Pop())
泛型 OR interface
两者的区别很明显,泛型虽然可以接受多个类型,但是最终类型是要固定下来的,他相当于是一个语法糖,经过词法分析和语法分析,最终的类型是明确的,并不需要反射,对性能没影响。而 interface 代表无类型限制,需要使用反射来知道其类型。
泛型实现机制
通常,把高级语言编译成机器本地可以执行的汇编代码,大致需要进行词法分析,语法分析,语义分析,生成中间代码,优化,以及最终生成目标代码等几个步骤。其中词法分析,语法分析,语义分析属于前端,而 golang 支持泛型只是前端的改动,本质上是语法糖。例如词法分析器要能正确解析泛型新引入的’[’ ‘]’ 括号,语法分析器能正确识别并判断代码是否符合泛型的语法规则,并构造正确的语法树 AST。而到了语义分析阶段,编译器需要能根据前面提到的类型参数和接口限制,来正确的推导出参数的实际类型,检查类型是否实现了相关接口定义的方法,实例化支持特定类型的函数,以及进行函数调用的类型检查等等。
幸运的是,golang 团队已经给我们提供了两种途径来预先感受下泛型新特性,一种是通过https://go2goplay.golang.org/ 网站,用户可以在上面写合法的泛型代码,并编译执行,但是可能需要翻墙,且没有太多编译细节,这里不展开。
我们重点讲下通过本地下载编译 go2go 工具来编译泛型代码。具体的 go2go 工具的编译过程,可以参考这篇文档, https://golang.org/doc/install/source。(使用go源码分支dev.go2go)
下面我们来编译一个最基本的泛型示例代码,内容如下:
import(
"fmt"
)
func Print[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func main(){
Print([]string{"Hello, ", "World
"})
}
输入命令
go tool go2go translate typeparam_basic.go2
注意 go2go 工具目前只支持.go2 后缀的源码文件。
编译完成后,我们看代码长这个样子
// Code generated by go2go; DO NOT EDIT.
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:1
package main
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:1
import "fmt"
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:13
func main() {
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:13
instantiate??Print?string([]string{"Hello, ", "World
"})
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:15
}
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:7
func instantiate??Print?string(s []string,) {
for _, v := range s {
fmt.Println(v)
}
}
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:11
type Importable? int
//line /Users/abc/work/go_generics_demo/typeparam_basic.go2:11
var _ = fmt.Errorf
可以看到工具已经自动为我们插入注释,并且实例化了一个支持 string slice 类型的函数,且为了避免和已有代码中的其它函数重名,造成错误,工具引入了两个不常用的 Unicode 字符,并插入到实例化的函数名称中,最后工具把生成的代码,重新命名为.go 后缀的文件,并写到文件系统。接下来我们就可以正常的编译执行生成的.go 代码。
进一步的,我们可以通过编译 debug go2go 的源码,来看看究竟工具如何做这些做事情的,通过 debug go2go 工具,我们发现,其实 go2go 帮我们把使用泛型的 golang 代码,通过重写 AST 的方式,转换成 go 1.x 版本的代码, 如下所示:
// rewriteAST rewrites the AST for a file.
func rewriteAST(fset *token.FileSet, importer *Importer, importPath string, tpkg *types.Package, file *ast.File, addImportableName bool) (err error) {
t := translator{
fset: fset,
importer: importer,
tpkg: tpkg,
types: make(map[ast.Expr]types.Type),
typePackages: make(map[*types.Package]bool),
}
t.translate(file)
// Add all the transitive imports. This is more than we need,
// but we're not trying to be elegant here.
imps := make(map[string]bool)
for _, p := range importer.transitiveImports(importPath) {
imps[p] = true
}
for pkg := range t.typePackages {
......
上面的 AST 转换工具相关的代码和思路应该会被正式的 golang 编译器实现所借鉴。
泛型实现对比
说明
C++
C#
Java
Go
实现
使用宏生成对应类型的类/函数代码
生成中间语言IL,运行时创建类型的专用类
编译擦拭法,泛型当作object处理,只有一个类型
编译为代码中所有类型的具体函数
实际类型数量
编译后,所有代码引用的类型
所有的引用类型的泛型实例共享一个模板,而为一个不同的值类型,产生独立的代码。
只有一个类型
代码中引用的所有类型
类型支持范围
类,虚拟类,接口,虚拟接口,函数参数
类,接口,委托,结构以及方法成员;
类,函数,接口
支持函数,结构体,map,slice
优点
无运行时负担,运行效率快。C++模板基于签名的隐式约束,灵活性高。
1不会导致C++中代码膨胀的问题;2因为是JIT编译时实例化,可以应用于反射;3可以使用泛型参数约束来实现对类型参数的显式约束;4类型安全,不用向下转换,尤其是装箱拆箱操作。
不会导致代码膨胀
不影响运行效率。类型安全,编译期检查。
缺点
会导致代码膨胀。
无相关资料
只能使用参数Object的接口,对泛型支持比较弱;运行时生成类,效率较低。
会导致代码膨胀
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦
边栏推荐
猜你喜欢
订song餐系统
字符函数和字符串函数
给你一个大厂面试的机会,你能面试上吗?进来看看!
PyQt5快速开发与实战 10.1 获取城市天气预报
DCM middleware family welcomes a new member
Different lower_case_table_names settings for server ('1') and data dictionary ('0') solution
手撕Verilog PWM呼吸灯
机器学习基本概念
PyQt5 rapid development and actual combat 10.2 compound interest calculation && 10.3 refresh blog clicks
AMBA APB学习记录(AMBA 3/4)
随机推荐
Selenium自动化测试之Selenium IDE
MySQL面试八股文(2022最新整理)
消息队列面试题(2022最新整理)
centos7安装mysql5.7
Encapsulation of conversion between Json and objects (Gson)
Different lower_case_table_names settings for server ('1') and data dictionary ('0') solution
分布式监视 Zabbix 和 Prometheus 到底怎么选?千万别用错了!
PyQt5快速开发与实战 10.1 获取城市天气预报
Chrome开发自定义右键菜单实现快速跳转到指定页面
CWE4.8 -- 2022年危害最大的25种软件安全问题
ASM module in SAP Ecommerce Cloud Spartacus UI and Accelerator UI
A Week of Wonderful Content Sharing (Issue 14)
Use ODBC in Excel to read data from CDS view on SAP BTP platform
Two methods of NameNode failure handling
2022年最新重庆建筑安全员模拟题库及答案
Qt鼠标穿透
【核心概念】图像分类和目标检测中的正负样本划分以及架构理解
Markdown编辑器语法
ASM外部冗余是否可以替换磁盘
anaconda虚拟环境安装pytorch gpu版本