什么是go中的泛型

泛型是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) }

四、总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang自V1.18版本开始正式支持泛型泛型Golang可用于变量、函数、方法、接口、通道等多个语法实体。泛型的使用方法可以分为以下几个方面: 1. 泛型变量类型:泛型变量类型是针对类型变量的,可以用于声明包含不同类型的变量。例如,可以声明一个泛型切片变量,使其可以存储不同类型的元素。 2. 自定义泛型类型:在Golang,可以通过声明自定义的泛型类型来扩展泛型的使用。这意味着可以定义适用于不同类型的自定义数据结构。 3. 调用带泛型的函数:通过使用泛型类型作为函数参数或返回值,可以调用带有泛型的函数。这样可以实现在不同类型上通用的函数逻辑。 4. 泛型与结构体:泛型可以与结构体一同使用,结构体的字段和方法可以是泛型类型,从而实现更灵活和通用的数据结构和操作。 然而,需要注意的是,Golang泛型还存在一些限制和缺陷。例如,无法直接与switch语句配合使用。这是因为在泛型无法判断具体的类型,而switch语句需要根据具体类型进行分支判断。 总的来说,Golang泛型提供了一种通用的类型机制,使得代码更具灵活性和可复用性。但是需要熟悉泛型的语法和使用限制,以避免在实际使用遇到问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [全面解读!Golang泛型的使用](https://blog.csdn.net/QcloudCommunity/article/details/125401490)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [go泛型使用方法](https://blog.csdn.net/qq_42062052/article/details/123840525)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值