泛型编程在许多编程语言中都是一项非常强大的特性,它可以使程序更加通用、具有更高的重用性。然而,Go语言在很长一段时间内一直没有提供泛型功能。在过去的一些版本中,Go语言开发者试图引入泛型,但最终都因为各种原因被取消或搁置了。直到Go 1.18版本,终于引入了泛型功能。在本文中,将会介绍这项新特性及其使用方法。
泛型是一种编程语言的特性,它可以将类型参数化,并以类型参数形式传递到不同的算法和数据结构中。泛型使得程序可以更加通用、安全且具有更高的重用性。不同的类型参数可以通过参数化类型类型来表示。例如,在Java中,可以使用ArrayList<Integer>来表示包含整数的动态数组,其中Integer是类型参数的类型。
在Go语言中,泛型的类型参数可以是任何类型,包括基本类型、引用类型、结构体和接口等。这些类型参数可以用在函数、方法、结构体、接口、通道和映射等语法结构中。
当谈到泛型编程时,我们需要了解两个重要的概念:类型形参和类型实参。
使用类型形参和类型实参的一个典型例子是在泛型函数中定义类型形参,然后调用该函数时提供类型实参的类型。例如:
package mainimport "fmt"// 定义泛型函数func PrintType[T any](x T) { fmt.Printf("Type: %T/n", x)}func main() { // 调用泛型函数,类型实参为 int PrintType[int](42) // 调用泛型函数,类型实参为 string PrintType[string]("hello")}输出结果:Type: intType: string
在上面的示例中,我们定义了一个名为 PrintType 的泛型函数,并使用 [T any] 声明了一个类型形参。然后,在调用该函数时,我们使用类型实参来具体化类型形参,例如使用 int 和 string。这样,在函数内部,我们就可以使用具体的类型信息来打印数据的类型。
类型形参和类型实参的使用为我们提供了更大的灵活性和通用性,使得我们可以编写可处理多种类型的泛型代码。
通过上面的代码,我们对Go的泛型编程有了最初步也是最重要的认识——类型形参 和类型实参。而Go 1.18也是通过这种方式实现的泛型,但是单纯的形参实参是远远不能实现泛型编程的,所以Go还引入了非常多全新的概念:
type MySlice[T int|float32|float64 ] []Tvar mySlice MySlice[int]上面这段代码定义了一个具有类型约束的泛型类型MySlice,T为类型参,必须是int、float32或float64之一,表示只能用这个明确的类型代替T。MySlice[T]表示一个元素类型为T切片类型。T 就是类型形参(Type parameter),类似一个占位符int|float32|float64 就是类型约束(Type constraint),中间的 | 就是或的意思,表示类型形参 T 只接收 int 或 float32 或 float64 这三种类型的实参中括号里的 T int|float32|float64 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为 类型形参列表(Type parameter list)在使用MySlice时,如MySlice[int]表示元素类型为int切片类型,int 就是类型实参(Type argument)上面只是个最简单的例子,实际上类型形参的数量可以远远不止一个,如下:// CostMap类型定义了两个类型形参 KEY 和 VALUE。分别为两个形参指定了不同的类型约束// 这个泛型类型的名字叫:CostMap[KEY, VALUE]type CostMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE // 用类型实参 string 和 flaot64 替换了类型形参 KEY 、 VALUE,// 泛型类型被实例化为具体的类型:CostMap[string, float64]var a CostMap[string, float64] = map[string]float64{ "dept1_cost": 8913.34, "dept2_cost": 4295.64,}
用上面的例子重新复习下各种概念:
用如下一张图就能简单说清楚:
图片
在Go语言中,泛型的实现方式是使用类型参数化函数和类型参数化结构体。类型参数化函数是一种函数,接受类型参数作为输入,并根据这些类型参数返回不同的结果。类型参数化结构体是一种结构体,其中一些或全部成员字段由类型参数确定。
以下是一个用于从切片中查找元素并返回其索引的类型参数化函数的代码示例:
func Find[T comparable](slice []T, value T) int { for i, v := range slice { if v == value { return i } } return -1}
这个函数接收一个任意类型的切片和一个具有相同类型的值,并返回第一次出现该值的索引。类型参数T必须是“comparable”类型,也就是说,它必须是可比较的类型,这是Go泛型的一个限制。
以下是一个用于实现一个类型安全的栈的类型参数化结构体代码示例:
type Stack[T any] struct { data []T}func (s *Stack[T]) Push(v T) { s.data = append(s.data, v)}func (s *Stack[T]) Pop() (t T, err error) { if len(s.data) == 0 { return t, errors.New("stack is empty") } res := s.data[len(s.data)-1] s.data = s.data[:len(s.data)-1] return res, nil}func main() { var stack Stack[int] stack.Push(1) stack.Push(2) stack.Push(3) item, err := stack.Pop()if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Pop item:", item) } item, err = stack.Pop()if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Pop item:", item) }}
这个结构体表示栈,其中T是元素类型,并且在Push和Pop函数中使用。注意,这里的类型参数T没有任何限制,因此可以传递任何类型。var stack Stack[int] 在初始化实例时,就把类型设置好了。
以上是一些示例代码,展示了Go泛型的使用。在复杂的程序中,泛型的使用可以使代码更加通用、易于阅读、安全且具有更高的重用性。
Go语言的泛型实现与其他编程语言(如Java、C++、C#等)的泛型实现有一些不同的地方。以下是它们在一些方面的对比:
总的来说,Go泛型的实现方式比较简单、灵活,但在性能方面有些损失。但同时,Go语言也在持续地改进其泛型实现,以提高其性能,并加入更多的功能特性。
以下代码是Go中用泛型实现Set无序集合,包含了添加,删除,是否存在,转成列表等方法。
type Set[T comparable] struct { m map[T]struct{}}func (s *Set[T]) Add(t T) { s.m[t] = struct{}{}}func (s *Set[T]) Remove(t T) { delete(s.m, t)}func (s *Set[T]) Exist(t T) bool { _, ok := s.m[t] return ok}func (s *Set[T]) List() []T { t := make([]T, len(s.m)) var i int for k := range s.m { t[i] = k i++ } return t}func (s *Set[T]) ForEach(f func(T)) { for k, _ := range s.m { f(k) }}
Go泛型的出现,使得我们可以更加通用、安全且具有更高的重用性。它的出现具有以下优势:
在Golang中,泛型功能的引入提高了Go的通用性、可读性和安全性。使用类型参数化的方式,我们可以编写出可以处理任何类型的代码。尽管Go泛型的实现方式略有不同于其他语言,但仍然可以为程序员提供实用的工具和功能,使代码更加通用、安全、易读和易于维护。
本文链接:http://www.28at.com/showinfo-26-10439-0.htmlGo语言进化之路:泛型的崛起与复用的新篇章
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com