Golang中的泛型你真的了解吗

目录
  • 什么是泛型
  • 为什么需要泛型
  • 泛型语法
    • 类型参数
    • 类型集
    • 类型推断
  • 总结

Golang 在 1.18 版本更新后引入了泛型,这是一个重要的更新,Gopher 万众瞩目,为 Golang 带来了更多的灵活性和可重用性,同时也解决了在特定场景下 Golang 类型系统的限制。

今天,我们将深入探讨泛型的概念、为什么需要泛型、泛型的语法,并探讨如何在实践中使用它。

go version >= 1.18

什么是泛型

泛型是一种在软件开发中广泛使用的编程概念,它允许开发者编写可重用的代码,而不需要考虑具体的数据类型。使用泛型,开发者可以编写一些通用的算法和数据结构,这些算法和数据结构可以适用于不同类型的数据,而不需要为每种类型都编写一份专用的代码。泛型的概念在其他许多编程语言中都有支持,比如 C++、Java、C# 等。

为什么需要泛型

假设我们需要实现一个返回一个 Map key 的 切片 []int -- MapKeysToInt。

func MapKeysToInt(m map[int]string) []int {
	r := make([]int, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

可是这个函数只能接收map[int]string 类型的参数,如果我们想支持 int8 类型的参数,我们就需要再定义一个MapKeysToInt8 函数。

func MapKeysToInt8(m map[int8]string) []int8 {
	r := make([]int8, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

如果要想支持 int64 类型切片就要定义 MapKeysToInt32 函数,如果想支持 xxx 就需要定义一个 MapKeysToXXX...

我们会发现一遍一遍地编写相同的功能非常的低效,

func MapKeysToInt32(m map[int32]string) []int32 {
	r := make([]int32, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

Go1.18 之前我们可以使用反射的方式去实现上述问题,但是会降低代码的执行效率且失去编译期的类型检查等弊端。

Go1.18 之后我们可以用泛型来实现这一系列问题,eg:

// 当调用泛型函数的时候, 我们经常可以使用类型推断。
// 注意,当调用 MapKeys 的时候,我们不需要为 K 和 V 指定类型 - 编译器会进行自动推断
func MapKeys[K comparable, V any](m map[K]V) []K {
	r := make([]K, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

func main (){
	var m = map[int]string{
		1: "2",
		2: "4",
		4: "8",
	}
	fmt.Println("keys m:", MapKeys(m))

    var m2 = map[string]int{
		"程序员祝融": 1,
		"李四": 2,
		"王五": 3,
	}
	fmt.Println("keys m2:", MapKeys(m2))
}

// demo 运行结果:
// keys m: [2 4 1]
// keys m2: [李四 王五 程序员祝融]

泛型语法

泛型为Go语言添加了三个新的重要特性:

  • 类型参数(形参、实参)
  • 类型集
  • 类型推断

类型参数

之前我们定义函数时可以指定其形参,调用函数时传实参,如下。

现在,Go 语言中的函数和类型支持添加类型参数。类型参数以类似于函数参数的方式进行定义,使用方括号 [] 包含一个或多个类型参数。

用泛型实现一个比较两数大小的 demo ,eg:

func max[T int | int32 | int64 | float32](x, y T) T {
	if x >= y {
		return x
	}
	return y
}

类型实例化

我们定义了一个 max 函数,支持传 int、int32、int64、float32 类型,我们可以传入这 4 种类型中的任意一个。 eg 传一个 int 类型:

max[int32](1, 2) // 2

也支持传一个 float32 类型:

max[float32](0.1, 0.2) // 0.2

max 函数提供类型参数(在本例中为 int 和float32 ) 称为实例化。eg:

// 类型实例化,编译器生成 T=float32 的 max 函数
f := max[float32]
fmt.Println(f(0.1, 0.2))

我们定义了一个 max[float32] 函数 f,我们可以在接下来调用函数的方式使用 f(0.1, 0.2) 它 。

类型约束

类型约束是指限制类型参数的类型的约束条件,可以使用interface关键字来表示。以上方的 demo,我们常见的方式有:

类型约束接口直接在类型参数列表中使用:

func max[T interface{ int | int32 | int64 | float32 }](x, y T) T {
	if x >= y {
		return x
	}
	return y
}

也可以事先定义,后复用

// 事先定义类型约束类型
type Value interface {
	int | int32 | int64 | float32
}
func min[T Value](a, b T) T {
	if a <= b {
		return a
	}
	return b
}

在定义一个可以比较大小的泛型函数时,可以使用 comparable约束条件来限制类型参数的类型:

func MapKeys[K comparable, V any](m map[K]V) []K {
	r := make([]K, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

类型集

类型集是泛型语法中用来约束类型参数的工具,它规定了类型参数所能接受的类型范围。在 Golang 1.18 中,类型集使用 interface 来定义,在类型参数后面添加 interface 关键字来实现的。通俗一点解释,接口类型现在可以用作值的类型,也可以用作类型约束。

下面是一个定义了类型集的例子:

type MySlice interface {
	int | float32 | string
}

上面这个就表示定义了一个 int、float32、string 的类型集。

any 接口

Go 在 1.18 引入了一个新的预声明标识符,作为空接口类型的别名。

type any = interface{}

使用 eg:

func Swap[T any](a, b *T) {
    temp := *a
    *a = *b
    *b = temp
}

类型推断

最后说下类型推断,非常重要,在go 1.18 后推出,能够让开发者在使用泛型时更加的自然。

参数类型推断

对于函数参数的类型,需要传递类型参数,使得代码变长。看下一开始的 demo

func max[T int | int32 | int64 | float32](x, y T) T {
	if x >= y {
		return x
	}
	return y
}

调用

var a, b, s int
a := 1
b := 2
s := max[int32](a, b) // 2

在大部分场景下,我们的编译器其实可以自行推断类型参数 T。有了这个可以似的我们的代码变的更短,同事保持清晰。

var a, b, s int
a := 1
b := 2
s := max(a, b) // 2

总结

Go 在 1.18 中引入了泛型这一特性,极大地增强了语言的表现力和灵活性。通过类型参数、类型集、类型推断等语法特性,可以方便地定义和使用泛型类型和泛型函数。同时,编译器对泛型的支持也在不断完善,包括对类型参数的约束、类型集的多态和类型推断的增强等,进一步提升了泛型的实用性和性能。

在实际开发中,泛型可以用来处理许多常见的问题,如集合类的封装、算法的实现和通用接口的定义等。除了标准库中已经实现的泛型类型和函数之外,我们还可以通过自定义泛型类型和函数来满足特定的需求。

最后,使用泛型时需要注意类型安全和性能问题,特别是对于大规模的数据处理和算法计算,需要进行细致的测试和优化。

到此这篇关于Golang中的泛型你真的了解吗的文章就介绍到这了,更多相关Golang泛型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang泛型实现类型转换的方法实例

    目录 1.前言 2.To String 3.To Other Type 3.泛型 4.使用示例 5.go-huge-util 参考文献 总结 1.前言 Golang 标准库提供了很多类型转换的函数,如 strconv包可完成 string 与基本数据类型之间的转换. 比如将 int 与 string 之间的互转. // int to string s := strconv.Itoa(i) // string to int i, err := strconv.ParseInt(i, 0, 64)

  • Golang泛型的使用方法详解

    目录 1. 泛型是什么 2. 泛型的简单使用 2.1. 泛型示例 2.2. 自定义泛型类型 2.3. 调用带泛型的函数 3. 自定义泛型类型的语法 3.1. 内置的泛型类型any和comparable 3.2. 声明一个自定义类型 3.3. 泛型中的"~"符号是什么 4. 泛型的进阶使用 4.1. 泛型与结构体 5. 泛型的限制或缺陷 5.1 无法直接和switch配合使用 1. 泛型是什么 泛型生命周期只在编译期,旨在为程序员生成代码,减少重复代码的编写 在比较两个数的大小时,没有泛

  • Golang 使用接口实现泛型的方法示例

    在C/C++中我们可以使用泛型的方法使代码得以重复使用,最常见例如stl functions:vector<int> vint or vector<float> vfloat等.这篇文章将使用interface{...}接口使Golang实现泛型. interface{...}是实现泛型的基础.如一个数组元素类型是interface{...}的话,那么实现了该接口的实体都可以被放置入数组中.注意其中并不一定必须是空接口(简单类型我们可以通过把他转化为自定义类型后实现接口).为什么i

  • Go1.18新特性对泛型支持详解

    目录 1.泛型是什么 2.泛型类型的定义 2.1.声明一个自定义类型 2.2.内置的泛型类型any和comparable 2.3.泛型中的~符号是什么 1.泛型是什么 Go1.18增加了对泛型的支持,泛型是一种独立于使用的特定类型编写代码的方式.现在可以编写函数和类型适用于一组类型集合的任何一种.泛型生命周期只在编译期,旨在开发中减少重复代码的编写. 由于go属于静态强类型语言,例如在比较两个数的大小时,没有泛型的时候,仅仅只是传入类型不一样,我们就要再复制一份一样的函数,如果有了泛型就可以减少

  • GoLang 中的随机数的示例代码

    随机数我们都知道,就是计算机通过某种算法,"随机"的生成一个数字.很多编程语言都有内置的方法来生成随机数,那么 GoLang 中是怎样一种情况呢? 伪随机数 我们都知道"随机数"在现实生活中的概念,可能你随手抛一个硬币,就可以说其结果是随机的,但是在计算机中要确定一个"随机数"真的是"随机数",那可是有标准的,不是你随随便便说是就是. 根据密码学原理,要想对一个"随机数"进行随机性检验有以下几个标准: 统计

  • Go泛型实战教程之如何在结构体中使用泛型

    目录 01 目标 02 实现 01 目标 假设我们要实现一个blog系统,在该系统中有以下两个结构体: type Category struct { ID int32 Name string Slug string } type Post struct { ID int32 Categories []Category Title string Text string Slug string } 为了提高系统的性能,我们需要实现一个缓存系统,该缓存可以用于缓存各种类型,在该示例中我们限定为只能缓存

  • 在 TypeScript 中使用泛型的方法

    目录 1. 泛型语法 2. 在函数中使用泛型 (1)分配泛型参数 (2)直接传递类型参数 (3)默认类型参数 (4)类型参数约束 3. 在接口.类和类型中使用泛型 (1)接口和类中的泛型 (2)自定义类型中的泛型 4. 使用泛型创建映射类型 5. 使用泛型创建条件类型 (1)基础条件类型 (2)高级条件类型 6. 小结 前言: 泛型是静态类型语言的基本特征,允许将类型作为参数传递给另一个类型.函数.或者其他结构.TypeScript 支持泛型作为将类型安全引入组件的一种方式.这些组件接受参数和返

  • 一文带你了解Golang中的并发性

    目录 什么是并发性,为什么它很重要 并发性与平行性 Goroutines, the worker Mortys Channels, the green portal 总结 并发是一个很酷的话题,一旦你掌握了它,就会成为一笔巨大的财富.说实话,我一开始很害怕写这篇文章,因为我自己直到最近才对并发性不太适应.我已经掌握了基础知识,所以我想帮助其他初学者学习Go的并发性.这是众多并发性教程中的第一篇,请继续关注更多的教程. 什么是并发性,为什么它很重要 并发是指在同一时间运行多个事物的能力.你的电脑有

  • 关于golang中map使用的几点注意事项总结(强烈推荐!)

    目录 前言 1 使用 map 记得初始化 2 map 的遍历是无序的 3 map 也可以是二维的 4 获取 map 的 key 最好使用这种方式 5 map 是并发不安全的 ,sync.Map 才是安全的 总结 前言 日常的开发工作中,map 这个数据结构相信大家并不陌生,在 golang 里面,当然也有 map 这种类型 关于 map 的使用,还是有蛮多注意事项的,如果不清楚,这些事项,关键时候可能会踩坑,我们一起来演练一下吧 1 使用 map 记得初始化 写一个 demo 定义一个 map[

  • golang中defer的关键特性示例详解

    前言 大家都知道golang的defer关键字,它可以在函数返回前执行一些操作,最常用的就是打开一个资源(例如一个文件.数据库连接等)时就用defer延迟关闭改资源,以免引起内存泄漏.本文主要给大家介绍了关于golang中defer的关键特性,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 一.defer 的作用和执行时机 go 的 defer 语句是用来延迟执行函数的,而且延迟发生在调用函数 return 之后,比如 func a() int { defer b() return

  • Golang中如何使用lua进行扩展详解

    前言 最近在项目中需要使用lua进行扩展,发现github上有一个用golang编写的lua虚拟机,名字叫做gopher-lua.使用后发现还不错,借此分享给大家,下面话不多说了,来一起看看详细的介绍吧. 数据类型 lua中的数据类型与golang中的数据类型对应关系作者已经在文档中说明,值得注意的是类型是以L开头的,类型的名称是以LT开头的. golang中的数据转换为lua中的数据就必须转换为L开头的类型: str := "hello" num := 10 L.LString(st

  • Golang中数据结构Queue的实现方法详解

    前言 本文主要给大家介绍了关于Golang中数据结构Queue实现的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 需求 队列的特性较为单一,基本操作即初始化.获取大小.添加元素.移除元素等.最重要的特性就是满足先进先出. 实现 接下来还是按照以前的套路,一步一步来分析如何利用Go的语法特性实现Queue这种数据结构. 定义 首先定义每个节点Node结构体,照例Value的值类型可以是任意类型,节点的前后指针域指针类型为node type node struct {

  • golang中make和new的区别示例详解

    前言 本文主要给大家介绍了关于golang中make和new区别的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍: new 和 make 都可以用来分配空间,初始化类型,但是它们确有不同. new(T) 返回的是 T 的指针 new(T) 为一个 T 类型新值分配空间并将此空间初始化为 T 的零值,返回的是新值的地址,也就是 T 类型的指针 *T,该指针指向 T 的新分配的零值. p1 := new(int) fmt.Printf("p1 --> %#v \n &quo

  • java 在观察者模式中使用泛型T的实例

    被观察者 public class Observable<T> { List<Observer> observers = new ArrayList<Observer>(); boolean changed = false; /** * Adds the specified observer to the list of observers. If it is already * registered, it is not added a second time. *

随机推荐

其他