golang中使用泛型_golang泛型的使用-程序员宅基地

技术标签: golang  

泛型支持

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{} 类型会打破强类型的约束,使程序暴露更多的错误。

如果引入泛型就可以将 CompareInt64CompareFloat64 整合成一个函数。

定义泛型函数

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\n"})
}

输入命令

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\n"})
//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的接口,对泛型支持比较弱;运行时生成类,效率较低。 会导致代码膨胀
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/raoxiaoya/article/details/124322746

智能推荐

react之antd组件InputNumber控制小数点_antd inputnumber decimalseparator-程序员宅基地

文章浏览阅读3.9k次。InputNumber控制用户输入小数点的个数在项目过程中有个需求,需要控制用户输入小数点的个数问题。话不多说上代码://输入框绑定方法<InputNumber style={{ width: '100%' }} formatter={limitDecimals} parser={limitDecimals}/>方法:const limitDecimals = (value: string | number): string => { const reg_antd inputnumber decimalseparator

matlab 约束条件下三元函数的图像问题_三元函数图像-程序员宅基地

文章浏览阅读4.1k次,点赞5次,收藏15次。题目:绘制z=x+y,0<x<1,0<y<1,0.5<x+y<1(0.5<z)约束条件下的图像代码:clc;clear;x=0:0.01:1;y=0:0.01:1;[xx,yy]=meshgrid(x,y);[m,n]=size(xx); for i=1:m for j=1:n z(i,j)=x(i)+y..._三元函数图像

linux下恢复误删除oracle的数据文件_oracle 数据文件误删 linux-程序员宅基地

文章浏览阅读480次。场景描述:操作系统级别的删除数据文件(/oracle/oradata/ora10g/system1.dbf),而且数据库没有崩溃,仍然处于open状态。原理:在Linux操作系统中,如果文件从操作系统级别被删除掉,之前打开该文件的进程仍然持有相应的文件句柄,所指向的文件仍然可以读写,并且该文件的文件描述符可以从/proc目录中获得。如果关闭数据库,则句柄就会丢失。恢复步骤如..._oracle 数据文件误删 linux

VScode 自定义代码颜色、背景颜色、方法名、括号颜色-程序员宅基地

文章浏览阅读10w+次,点赞108次,收藏323次。自学前端刚开始的时候使用Hbuilder,Hbuilder界面设计的很小清新,我特别喜欢,代码提示啥方面做的也特别好,很好上手,作为小白我用了很长一段时间。后来浅浅学习微信小程序开发,得写wxml,Hbilder上没有wxml格式的代码提示,代码高亮等。于是我用了一段时间vscode,在网上按推荐安装了很多花里胡哨的插件呢。给我感觉是不好上手。于是后来又接触了sublime,sublime相比..._vscode 自定义代码颜色、背景颜色、方法名、括号颜色

深度学习框架Tensorflow学习与应用 图像数据处理之二_tensorflow深度学习框架实现了对图像亮度的调整-程序员宅基地

文章浏览阅读255次。四:图像色彩调整 和图像翻转类似,调整图像的亮度、对比度、饱和度和色相在很多图像识别应用中都不会影响识别结果。所以在训练神经网络模型时,可以随机的调整训练图像的这些属性,从而使训练得到的模型尽可能地受到无关因素的影响。话不多说,上代码了。注意:路径要用英文,不要有中文(一)调整亮度与调整对比度import matplotlib.pyplot as pltimport tensorf..._tensorflow深度学习框架实现了对图像亮度的调整

《ASP.NET5》无法路由到Web API Controller控制器_.net的web无法访问到控制器-程序员宅基地

文章浏览阅读5.6k次。这个标题不知道恰当不恰当,具体的问题就是我在一个现有的项目上创建了一个Web API Controller Class,但是按F5启动调试后,通过给定的路径如“http://localhost:3753/api/values/5”访问Get(int id)方法时,没有反应,设置了断点也进不去。但这个问题在网上又没有找到解决方法,于是又新建了一个ASP.NET Web Application类型的项_.net的web无法访问到控制器

随便推点

嵌入式Linux开发板_迅为iTOP-4412精英版入门篇(一)_4412开发入门-程序员宅基地

文章浏览阅读911次,点赞2次,收藏4次。迅为iTOP-4412开发板平台,ARM Cortex A9架构,主频1.4GHz-1.6GHz,1GB 双通道 DDR3(2GB 可选),4GB EMMC(16GB 可选),提供多种外接模块,如:RFID模块、继电器模块、CAN总线 RS485总线模块、串口转接板、矩阵键盘模块、AVIN模块、GPS模块、VGA模块、500万摄像头模块、WIFI/蓝牙模块等。拥有丰富的板载接口以及众多配套扩展模块,并提供丰富的学习教程与资源,是嵌入式Linux学习与开发最佳选择。_4412开发入门

扫荡倾斜摄影单体化难题_进行模型单体化容易遇到哪些问题-程序员宅基地

文章浏览阅读5.2k次,点赞6次,收藏27次。单体化问题的由来 我们先来说说什么是“单体化”。“单体化”其实指的就是每一个我们想要单独管理的对象,是一个个单独的、可以被选中的实体(Entity);即用鼠标点击时可以显示为不同颜色(称为“高亮”)显示,可以附加属性,可以被查询统计等等。只有具备了“单体化”的能力,数据才可以被管理,而不仅仅是被用来查看。 对于人工建模而言,单体化是一个不言而喻的事情。即在人工建模的过程中,自然会把需要单独管理的_进行模型单体化容易遇到哪些问题

snakeyaml自定义pojo写入yml文件时属性字段排序问题_snakeyaml 写入yaml-程序员宅基地

文章浏览阅读1.1k次。snakeyaml自定义pojo写入yml文件时属性排序问题解决。_snakeyaml 写入yaml

使用jbpm出现异常 java.lang.NoClassDefFoundError: de/odysseus/el/ExpressionFactoryImpl-程序员宅基地

文章浏览阅读2.9k次。java.lang.NoClassDefFoundError: de/odysseus/el/ExpressionFactoryImpl at org.jbpm.pvm.internal.script.JuelScriptEngine.(JuelScriptEngine.java:66) at org.jbpm.pvm.internal.script.JuelS_java.lang.noclassdeffounderror: de/odysseus/el/expressionfactoryimpl

浅谈安全运营中心-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏6次。最近两年安全运营中心这个概念被提到的次数越来越多了,虽然没有一个建设的标准模式,但很多大厂都在提,也号称有了各种落地。那么,到底是怎样呢。_安全运营中心

mysql 序列化缓存到txt文件查找数据与直接查找数据 性能对比,13,601条数据文件缓存平均0.085秒后只需0.025秒 推荐 程序员导航网http://www.je666.com_je666com-程序员宅基地

文章浏览阅读2.8k次。推荐 程序员导航网http://www.je666.comCREATE TABLE IF NOT EXISTS `ylmf_site_search` ( `id` int(11) NOT NULL, `displayorder` int(11) NOT NULL, `pinyin` varchar(255) CHARACTER SET gbk NOT NULL,_je666com

推荐文章

热门文章

相关标签