什么是go中的泛型

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: Go 语言在1.18版引入了泛型,旨在简化代码并提高复用性。泛型通过类型参数允许编写通用函数,如一个`Add`函数可以处理整数和浮点数的加法,减少了为不同类型编写重复代码的需求。类型约束(如`int`或`float64`)定义了类型参数适用的范围。编译器自动进行类型推导,简化调用。泛型可用于结构体(如创建泛型缓存)和其他场景,当逻辑相同但涉及不同类型时,可考虑使用泛型。注意泛型在处理自定义类型时,返回值可能是底层类型而非自定义类型,需通过类型约束来保持一致性。

泛型是go在1.18版本引入的新特性,泛型的引入使得在某些场景下,可以极大的简化代码的编写,提高了代码的复用性。有必要掌握泛型,可以减少很多重复的代码。

一、为什么需要泛型?

为什么我们需要泛型?在前面我们已经提到了简化代码的编写,提高代码的复用,这里我们举例详细说明? 假设我们需要实现一个函数,它的主要功能是做加法计算,比如计算a + b的值。

对于整数类型,我们可以使用如下的代码:

go

复制代码

func Add(a, b int) int {
  return a + b
}

对于浮点数类型,我们可以使用如下的代码:

go

复制代码

func Add(a, b float64) float64 {
  return a + b
}

看到了吧,我们发现,对于整数类型和浮点数类型,我们实现的函数是相同的,只是参数类型不同而已。究其原因在于,go作为静态类型语言,为了应对不同类型的变量,需要编写不同的函数做相应的计算。这正是泛型所要解决的问题。

下面我们看看,范型是如何解决这个问题的呢?

二、怎么用?

1. 用法

直接看代码

go

复制代码

package main

import "fmt"

func main() {
	a := Add(1, 2)
	b := Add(1.2, 2.3)
	fmt.Printf("int add sum: %d\n", a)
	// int add sum: 3
	fmt.Printf("float64 add sum: %f\n", b)
	// float64 add sum: 3.500000
}

// 泛型函数
// [] 中放的是类型参数
// T int | float64 类型约束为 int/float64
func Add[T int | float64](a, b T) T {
	return a + b
}

我们通过泛型的使用,将原来的intfloat64 Add函数,合并成一个函数了。 在使用时,本质是我们将类型提取成参数,类型也是一种参数(类型参数),这样就可以做到忽略某个具体类型,而编写通用的代码逻辑。

我们在使用时,无需显示传递类型参数,这是由于编译器会偷偷的在背后帮我们做类型推导的原因,实际上你显示传递Add[float64](1.2, 2.3)也是ok的。

2. 类型约束

前面我们提到类型参数,类型约束就是用于约束支持哪些类型的。

那么类型约束有哪些写法呢?

  • 直接在使用位置写明

go

复制代码

// 直接在使用位置写明
func Add[T int | float64](a, b T) T {
	return a + b
}
  • 定义接口

go

复制代码

type addable interface {
	// 前面带~ 表示只要底层类型为int或float64即可 场景是自定义类型
	~int | ~float64
}

// 这里引入addable约束
func Add[T addable](a, b T) T {
	return a + b
}
  • 内置类型
名称 使用场景 示例
any 任意类型 any
comparable 包括基本类型(如整数、浮点数、字符串)和一些复合类型(如数组),但不包括切片、映射、函数、通道等 a == b 或 a != b
constraints.Ordered 做顺序比较,所有整数类型、浮点数类型和字符串类型 <, >, <=, >= 比较操作

3. 使用举例

除了我们前面示例函数中使用泛型外,在其它地方也能使用比如结构体,在结构体使用举例。

go

复制代码

package main

import "fmt"

type Cache[T any] struct {
	cache map[string]T
}

func (c *Cache[T]) Set(key string, value T) {
	c.cache[key] = value
}

func (c *Cache[T]) Get(key string) (T, bool) {
	value, exists := c.cache[key]
	return value, exists
}

func main() {
	cache := &Cache[string]{
		cache: make(map[string]string),
	}

	cache.Set("hello", "world")
	value, _ := cache.Get("hello")
	fmt.Println("缓存中hello值为:", value)
	// 缓存中hello值为: world
}

4. 什么时候考虑使用范型?

当我们发现代码逻辑都一致,唯一不同的地方是类型不同时,考虑使用泛型。

三、注意的坑?

在使用泛型操作自定义类型时,需要注意它的返回值是底层类型还是自定义类型,下面我们看一个例子。

go

复制代码

package main

import "fmt"

// 自定义int 切片类型
type Point []int

// point 打印方法
func (p Point) print() {
	fmt.Printf("Point(%d, %d)", p[0], p[1])
}

// 用泛型的函数 缩放切片
func ScaleSlice[T int | float64](slice []T, scale T) []T {
	for i, val := range slice {
		slice[i] = val * scale
	}
	return slice
}

func main() {
	// 创建一个切片
	slice := Point{1, 2, 3, 4, 5}
	// 调用泛型函数
	scaledSlice := ScaleSlice(slice, 2)
	// 打印结果
	fmt.Println(scaledSlice)
	// [2 4 6 8 10]

	fmt.Printf("slice is %T\n", scaledSlice)
	// slice is []int

	// 这里会提示没有找到print方法 因为当前返回的scaledSlice是[]int类型 而非point
	scaledSlice.print()
}

我们发现,泛型函数返回的切片类型是底层类型,而不是自定义类型。 那要怎么解决呢?我们在类型参数上再组合一次使用~[]T 构造原始类型。

代码如下:

go

复制代码

package main

import "fmt"

// 自定义int 切片类型
type Point []int

// point 打印方法
func (p Point) print() {
	fmt.Printf("Point(%d, %d)", p[0], p[1])
}

// 用泛型的函数 缩放切片
// 引入S 类型保证返回自定义类型
func ScaleSlice[S ~[]T, T int | float64](slice S, scale T) S {
	for i, val := range slice {
		slice[i] = val * scale
	}
	return slice
}

func main() {
	// 创建一个切片
	slice := Point{1, 2, 3, 4, 5}
	// 调用泛型函数
	scaledSlice := ScaleSlice(slice, 2)
	// 打印结果
	fmt.Println(scaledSlice)
	// [2 4 6 8 10]

	fmt.Printf("slice is %T\n", scaledSlice)
	// slice is main.Point

	scaledSlice.print()
	// Point(2, 4)
}

四、总结

什么是泛型,也就是广泛的类型,在定义时候不具体指定某一个具体的类型,而是通过类型参数来表示。以此达到代码复用、简化代码的目的。


转载来源:https://juejin.cn/post/7380486156213600271

相关文章
|
存储 缓存 安全
Go 简单设计和实现可扩展、高性能的泛型本地缓存
本文将会探讨如何极简设计并实现一个可扩展、高性能的本地缓存。支持多样化的缓存策略,例如 最近最少使用(LRU)等。
254 0
Go 简单设计和实现可扩展、高性能的泛型本地缓存
|
IDE Go 开发工具
一文搞懂Go1.18泛型新特性
一文搞懂Go1.18泛型新特性
155 0
|
6月前
|
算法 Go
Go 语言泛型 — 泛型语法与示例
本文详解 Go 语言泛型语法与使用示例,涵盖泛型函数、类型声明、类型约束及实战应用,适合入门学习与开发实践。
|
3月前
|
Java 编译器 Go
【Golang】(5)Go基础的进阶知识!带你认识迭代器与类型以及声明并使用接口与泛型!
好烦好烦好烦!你是否还在为弄不懂Go中的泛型和接口而烦恼?是否还在苦恼思考迭代器的运行方式和意义?本篇文章将带你了解Go的接口与泛型,还有迭代器的使用,附送类型断言的解释
231 3
|
6月前
|
存储 安全 算法
Go语言泛型-泛型对代码结构的优化
Go语言自1.18版本引入泛型,极大提升了代码的通用性与可维护性。通过泛型,开发者可以减少重复代码、提高类型安全性,并增强程序的复用性和可读性。本文详细介绍了泛型在数据结构、算法及映射功能中的应用,展示了其在优化代码结构方面的优势。同时,Go编译器对泛型代码进行类型推导,确保运行时性能不受影响。合理使用泛型,有助于构建更加灵活高效的程序。
|
6月前
|
分布式计算 算法 安全
Go语言泛型-泛型约束与实践
Go语言中的泛型约束用于限制类型参数的范围,提升类型安全性。通过接口定义约束,可实现对数值类型、排序与比较等操作的支持。开发者既可使用标准库提供的预定义约束,如constraints.Ordered和constraints.Comparable,也可自定义约束以满足特定需求。泛型广泛应用于通用数据结构(如栈、队列)、算法实现(如排序、查找)及构建高效可复用的工具库,使代码更简洁灵活。
|
6月前
|
人工智能 Go
GO语言之泛型应用
本文介绍了Go语言中泛型的使用,包括为何引入泛型、泛型语法详解以及如何自定义约束。通过实例展示了泛型在简化代码、提高复用性方面的优势,并演示了泛型在slice、指针、map等数据类型中的应用。
188 1
|
6月前
|
Linux Go 开发者
Go语言泛型-泛型约束与实践
《Go语言实战指南》介绍了如何使用Go进行交叉编译,即在一个操作系统上编译出适用于不同系统和架构的二进制文件。通过设置GOOS和GOARCH环境变量,开发者可轻松构建跨平台程序,无需在每个平台上单独编译。Go从1.5版本起原生支持此功能,极大提升了多平台部署效率。
|
分布式计算 安全 Java
简单易懂的 Go 泛型使用和实现原理介绍
简单易懂的 Go 泛型使用和实现原理介绍
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
270 7