【Golang】合理运用泛型,简化开发流程

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑

在这里插入图片描述

Go语言中的泛型

一、泛型介绍

Go语言自1.18版本开始引入了泛型支持,这一特性极大地增强了代码的灵活性和重用性。
泛型通过引入类型参数,使得函数、类型和方法能够处理多种数据类型,从而减少了重复代码,提高了开发效率。本文将详细介绍Go语言中泛型的用法,并通过实际案例展示泛型在编程中的具体应用。

泛型:多种不确定的类型

在泛型之前,我们定义一个变量或函数参数类型,类型都是写死的,比如:

var name string
func test(name string){}

这样,功能就比较受限。
如果我们想要编写一个反转切片的函数,需要为每种类型分别编写一个函数。例如,反转整型切片和反转浮点型切片的函数需要分别实现。但是,从Go 1.18开始,我们可以使用泛型来实现一个通用的反转函数。

我们先用空接口来测试多种类型的适配实现,还是需要做类型判断

package main

import "fmt"

func main() {
    str := []string{"1", "2", "3", "4", "5"}
    printSlice(str)
    str2 := []int{1, 2, 3, 4, 5}
    printSlice(str2)

}

func printSlice(i interface{}) {
    //  断言 x.(T), 如果x实现了T,那么就将 x转换为T类型
    //这样的话,也是需要做好多种判断
    switch i.(type) {
    case []string:
        for _, i3 := range i.([]string) {
            fmt.Println(i3)

        }
        fmt.Println()
    case []int:
        for _, i3 := range i.([]int) {
            fmt.Println(i3)
        }

    }
    // 其他很多种类型都要做判断,所以通过这种方式不现实.........

}

这种情况,传递的参数进来,需要自己做N种判断来进行适配。
在这里插入图片描述

也可以通过反射来实现,在go1.18之后,我们最常用的还是泛型

泛型的作用:

1、减少重复性的代码,提高安全性

  • 针对不同的类型,写了相同逻辑的代码,我们就可以通过泛型来简化代码!

2、在1.18版本之前 反射 来实现。 泛型并不能完全取代反射!

二、泛型的基本概念

不限定参数的类型,让调用的人自己去定义类型。
类型参数:类型参数是在函数或类型定义时,紧随名称后的方括号中定义的。例如,func T any {…}中的[T any]表示类型参数T可以是任何类型。

约束:约束是指类型参数必须满足的接口。例如,any是一个内置约束,表示任何类型。通过约束,可以限制类型参数的范围,确保它们具备某些特定的行为或属性。

三、泛型类型的声明和使用

1. 声明泛型类型:使用[]括起方括号来声明泛型类型。例如:

type List[T any] []T
这里,List[T any]表示一个泛型类型,其中T是类型参数,可以是任何类型。any是约束, 是Go语言中的空接口,表示 T 可以是任何类型。
T 说白了就是一个占位符,类型的形式参数,T是不确定的,需要在使用的时候进行传递。也可以随便用个其他字母代替

2. 使用泛型类型:在使用泛型类型时,需要指定类型参数。

T是占位符,在使用的时候,必须要实例化为具体的类型。
声明泛型类型后,可以像普通类型一样使用它。例如:
myIntList := List[int]{1, 2, 3}
这里,myIntList是一个整型列表。

3. 泛型类型的应用:泛型类型可以应用于结构体、接口、函数和方法中。

例如,定义一个泛型结构体:

type Box[T any] struct {  
    content T  
}  
  
func (b Box[T]) Content() T {  
    return b.content  
}

使用:

box := Box[int]{content: 123}  
fmt.Println(box.Content()) // 输出: 123

泛型使用案例1:
普通的定义类型,这个类型只能代表本身一个,泛型类型,我们可以实现,参数类型传递

package main

import "fmt"

// 我们定义的结构都是一样的,只是它的类型不同,就需要重新定义这么多的类型。
// 思考:是否有一种机制,只定义一个类型就可以代表上面的所有类型?
// 泛型:类型 参数化了!   参数:人为传递的

/*
1、T 说白了就是一个占位符,类型的形式参数,T是不确定的,需要在使用的时候进行传递。
2、由于T类型是不确定的,我们需要加一些约束 int|float64|float32 。告诉编译器我这个T,只接受
  int、float64、float32 类型
3、我们这里定义的类型是什么?Slice[T]
*/

// Slice 这种类型的定义方式,带了类型形参,和普通定义类型就完全不同的。
// 普通的定义类型,这个类型只能代表本身一个,泛型类型,我们可以实现,参数类型传递。
// 我们可以在使用的时候来定义类型。
// 语法糖:简化开发
// T后面是约束,约束用户只能用哪些类型
// 创建个不固定类型的切片类型Slice
type Slice[T int | float64 | float32] []T

func main() {

    //使用的时候,必须要实例化为具体的类型
    // Slice是我们上面自己定义的类型,中括号里面是约束,在使用时要用具体的类型
    var a Slice[int] = []int{1, 2, 3}
    fmt.Printf("%T\n", a) // Slice[int]

    var b Slice[float64] = []float64{1.0, 2.0, 3.0}
    fmt.Printf("%T\n", b) // Slice[float64]

    // 不能够赋值(string 不在T的约束当中,不能实例化的)
    //var c Slice[string] = []string{"jigntian","xxx"}

    // T是占位符,在使用的时候,必须要实例化为具体的类型。
    //var d Slice[T] = []int{1,2,3}
}

在这里插入图片描述

泛型使用案例2:
泛型可以用在所有有类型的地方

package main

import "fmt"

// 泛型可以用在所有有类型的地方

type MyStruct[T int | string] struct {
    Id   T
    Name string
}

type IprintData[T int | float64 | string] interface {
    Print(data T)
}

// MyChan 通道
type MyChan[T int | string | float64] chan T

func main() {

    //T 泛型的参数类型的属性可以远不止一个,所有东西都可以泛型化。不一定非得用T  其他字母也可以
    // map(int)string

    // map[KEY]VALUE 类型形参(参数是不确定) KEY  、VALUE
    // KEY int | string   , VALUE float32 | float64 约束
    // 类型的名字 MyMap[KEY,VALUE], 通过这一个类型,来代表多个类型!--> 泛型
    //map泛型就是多个参数 KEY VALUE
    type MyMap[KEY int | string | float64, VALUE float32 | float64 | int | string] map[KEY]VALUE

    //定义了泛型后,就可以实例化出不同类型的map
    // map [string]float64
    var score MyMap[string, float64] = map[string]float64{
        "go":   9.9,
        "java": 8.0,
    }
    fmt.Println(score)

    // map [int]string
    var rank MyMap[int, string] = map[int]string{
        1: "张三",
        2: "王五",
    }
    fmt.Println(rank)
}

在这里插入图片描述

4. 特殊的泛型

package main

import "fmt"

func main() {
    // 特殊的泛型类型,泛型的参数时多样的,但是实际类型定义就是int
    type AAA[T int | string] int

    var a AAA[int] = 123
    var b AAA[string] = 123
    fmt.Println(a)
    fmt.Println(b)

    //查看类型
    fmt.Printf("%T\n", a)
    fmt.Printf("%T\n", b)
    //var c AAA[string] = "hello" //不能这样赋值,因为AAA的值约束的类型是int

}

这里虽然使用了泛型。但是底层类型就是int,所以传int和string都可以的,但是赋值,只能是int
在这里插入图片描述

四、泛型函数和方法的定义和使用

1. 定义泛型函数:

单纯的泛型没啥意义。和函数结合使用, 可以使用调用者(调用者的类型可以自定义,就可以实现泛型。)
带了类型形参的函数就叫做泛型函数,极大的提高代码的灵活心,降低阅读性!
泛型函数可以处理多种类型的数据。例如,定义一个交换两个值的泛型函数:

func Swap[T any](a, b T) (T, T) {  
    return b, a  
}

使用:

package main

import "fmt"

func Swap[T any](a, b T) (T, T) {
    return b, a
}

func main() {
    a, b := Swap[int](1, 2) // 显式指定类型
    fmt.Println(a, b)       // 输出: 2 1

    c, d := Swap("hello", "world") // 隐式类型推断,不用写类型
    fmt.Println(c, d)                      // 输出: world hello
}

在这里插入图片描述

泛型可以增加代码的灵活性,降低了可读性!
Go的泛型语法糖:自动推导 (本质,就是编译器帮我们加上去了,在实际运行,这里T还是加上去的)
这种带了类型形参的函数就叫做泛型函数,极大的提高代码的灵活心,降低阅读性!

package main

import (
    "fmt"
)

func main() {
    var a int = 1
    var b int = 2
    fmt.Println(Add[int](a, b))

    var c float32 = 1.1
    var d float32 = 2.2
    fmt.Println(Add[float32](c, d))

    // 每次都去写T的类型是很麻烦的,支持自动推导!
    // Go的泛型语法糖:自动推导 (本质,就是编译器帮我们加上去了,在实际运行,这里T还是加上去的)
    fmt.Println(Add(a, b)) // T : int
    fmt.Println(Add(c, d)) // T : float32

}

// Add 真正的Add实现,传递不同的参数都是可以适配的! Add[T]  T在调用的时候需要实例化
// 这种带了类型形参的函数就叫做泛型函数,极大的提高代码的灵活心,降低阅读性!
func Add[T int | float32 | float64](a T, b T) T {
    return a + b
}

在这里插入图片描述

2. 定义泛型方法:

泛型方法可以应用于泛型类型上。
针对不同类型的切片做累加和,使用泛型比较简便

package main

import (
    "fmt"
)

// MySlice 定义一个泛型切片
type MySlice[T int | float32 | int64] []T

func main() {
    //针对不同类型的切片都可以计算
    var s MySlice[int] = []int{1, 2, 3, 4}
    fmt.Println(s.sum())

    var s1 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.4}
    fmt.Println(s1.sum())
}

// 调用者,类型是不确定的,用户传什么,她就实例化什么。  类型参数化了 , 泛型
// 没有泛型之前, 反射:     reflect.ValueOf().Kind() , 也需要很多if,本质是逻辑相同的,只是类型不同!
func (s MySlice[T]) sum() T {
    var sum T
    for _, v := range s {
        sum += v
    }
    return sum
}

在这里插入图片描述

五、泛型约束的使用

1. 内置约束:

Go语言提供了几个内置约束,包括any(表示任何类型)和comparable(表示可以比较的类型)。
any经常用,就是一个泛型,表示了go所有的内置类型。interface{} 这里就不做太多赘述了。

comparable是一个接口,所有可比较的类型都实现了这个接口。
可比较的类型包括布尔型、数值型、字符串、指针、通道(channel)、可比较类型的数组以及所有字段都是可比较类型的结构体等。
comparable仅能用于泛型中的类型限定(type constraint),不可作为变量的类型。
看下源码
在这里插入图片描述

在使用comparable作为类型约束时,需要确保类型参数确实支持比较操作,否则会导致编译错误。
comparable不支持大小比较操作(如<、<=、>、>=),仅支持相等性比较(==、!=)。
结构体类型可以作为comparable使用,但前提是其所有字段都是comparable的。如果结构体包含不可比较的字段(如切片、映射类型),则整个结构体类型也不可比较。

搜索算法:
泛型也可以用于实现通用的搜索算法。例如,定义一个泛型函数来查找切片中的元素

package main

import "fmt"

func Find[T comparable](slice []T, value T) (int, bool) {
    for i, v := range slice {
        if v == value {
            return i, true
        }
    }
    return -1, false
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    index, found := Find(numbers, 3)
    if found {
        fmt.Println("Element found at index:", index) // 输出: Element found at index: 2
    } else {
        fmt.Println("Element not found")
    }
}

在这里插入图片描述

2. 自定义约束

由于约束有时候很多,我们可以定义一些自己的泛型约束(本质是一个接口)
除了内置约束外,还可以使用自定义接口作为约束条件。例如,定义一个支持加法操作的泛型函数:

package main

import "fmt"

// MyInt 泛型的约束提取定义
type MyInt interface {
    int | float64 | int8 | int32 // 作用域泛型的,而不是一个接口方法  这样泛型约束可以用着集中类型都可以
}

// 自定义泛型
func main() {
    var a int = 10
    var b int = 20

    fmt.Println(GetMaxNum(a, b))
}

// GetMaxNum 函数里面泛型T 用自己定义的约束MyInt
func GetMaxNum[T MyInt](a, b T) T {
    if a > b {
        return a
    }
    return b
}

在这里插入图片描述

3. 支持泛型衍生类型

新符号 ~,和类型一起出现的,表示支持该类型的衍生类型!
衍生类型,就是根据该类型常见的类型

package main

import "fmt"

// int8 衍生类型
type int8A int8
type int8B = int8

// NewInt ~ 表示可以匹配该类型的衍生类型
type NewInt interface {
    ~int8
}

// ~
func main() {
    var a int8A = 8
    var b int8A = 56
    fmt.Println(GetMax(a, b))
}
func GetMax[T NewInt](a, b T) T {
    if a > b {
        return a
    }
    return b
}

在这里插入图片描述

六、泛型在实际项目中的应用案例

  1. 日志库:在日志库中,可以使用泛型来创建通用的日志记录器,支持多种日志级别和数据类型。例如,定义一个泛型日志记录器接口,并为不同类型的日志消息实现该接口。

  2. 数据库ORM:在数据库ORM框架中,泛型可以用于实现通用的查询和更新操作,支持多种数据类型和数据库表结构。通过定义泛型接口和方法,可以简化数据库操作的代码,提高开发效率。

  3. 网络框架:在网络框架中,泛型可以用于实现通用的请求处理函数和中间件,支持多种请求和响应类型。通过定义泛型类型和接口,可以方便地扩展和定制网络框架的功能。

七、泛型使用的注意事项

  1. 性能问题:虽然泛型提高了代码的灵活性和重用性,但在某些情况下可能会引入性能开销。因此,在使用泛型时需要权衡性能与代码可读性之间的关系。

  2. 类型推断:Go语言的类型推断机制在大多数情况下能够正确地推断出泛型类型参数的类型,但在某些复杂情况下可能会出现推断失败的情况。因此,在编写泛型代码时需要注意类型推断的限制和规则。

  3. 约束条件:在使用泛型时,需要为类型参数指定适当的约束条件,以确保它们具备必要的属性和行为。然而,由于Go语言的接口机制相对较为简单,某些复杂的约束条件可能无法通过接口来实现。因此,在定义和使用泛型时需要仔细考虑约束条件的合理性和可行性。

八、总结

Go语言的泛型特性为开发者提供了更加灵活和强大的编程工具,使得代码更加简洁、可读和可重用。通过掌握泛型的基本概念、用法和注意事项,开发者可以更好地利用泛型来优化代码结构和提高开发效率。同时,也需要注意泛型可能带来的性能开销和类型推断限制等问题,以确保代码的正确性和稳定性。

以上是关于Go语言中泛型用法的详细介绍和教程。希望本文能够帮助读者更好地理解和掌握Go语言的泛型特性,并在实际项目中灵活运用泛型来提高代码质量和开发效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/899076.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

灵动AI视频最新AIGC 系统:支持多种模型切换 支持5端部署

灵动AI视频官网地址&#xff1a;https://aigc.genceai.com/

SeCo复现

表1 复现结果–CAM&#xff1a;74.751 Mask&#xff1a;76.47 Val&#xff1a;74.0 Test&#xff1a;73.7948&#xff0c;完全一致 表3 复现结果&#xff1a;0.233&#xff0c;完全一致 感想 第10篇完全复现的论文

知识点框架笔记3.0笔记

如果基础太差&#xff0c;搞不清基本交规的&#xff08;模考做不到60分&#xff09;&#xff0c;建议找肖肖或者小轩老师的课程看一遍&#xff0c;内容差不多&#xff08;上面有链接&#xff09;&#xff0c;笔记是基于肖肖和小轩老师的科目一课程以及公安部交管局法规&#xf…

京东笔试题

和谐敏感词 &#x1f517; 题目地址 &#x1f389; 模拟 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();String s scanner.next();String[] words new String[…

Android GPU Inspector分析帧数据快速入门

使用 谷歌官方工具Android GPU Inspector (AGI) 可以对Android 应用进行深入和全面的系统性能分析和帧性能分析 。AGI 是一个非常强大的分析工具&#xff0c;尤其是在需要诊断 GPU 性能问题和优化应用时&#xff0c;可以帮助你精准找到性能瓶颈。本文介绍如何使用该工具对帧数据…

Docker 安装Postgres和PostGIS,并制作镜像

1. 查找postgres和postgis现有的镜像和版本号 镜像搜索网站&#xff1a;https://docker.aityp.com/ 测试使用的是postgres:15.4 和 postgis:15-3.4 2、镜像拉取 docker pull postgres:15.4docker pull postgis/postgis:15-3.4镜像下载完成&#xff0c;docker images 查看如…

CentOS7安装RabbitMQ-3.13.7、修改端口号

本文安装版本&#xff1a; Erlang&#xff1a;26.0 官网下载地址 Erlang RabbitMQ&#xff1a;3.13.7 官网下载地址 RabbitMQ RabbitMQ和Erlang对应关系查看&#xff1a;https://www.rabbitmq.com/which-erlang.html 注&#xff1a;安装erlang之前先安装下依赖文件&#xff0…

【Hive】8-Hive性能优化及Hive3新特性

Hive性能优化及Hive3新特性 Hive表设计优化 Hive查询基本原理 Hive的设计思想是通过元数据解析描述将HDFS上的文件映射成表 基本的查询原理是当用户通过HQL语句对Hive中的表进行复杂数据处理和计算时&#xff0c;默认将其转换为分布式计算 MapReduce程序对HDFS中的数据进行…

软件测试学习笔记丨Linux三剑客-sed

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32521 一、简介 sed&#xff08;Stream editor&#xff09;是一个功能强大的文本流编辑器&#xff0c;主要用于对文本进行处理和转换。它适用于自动化处理大量的文本数据&#xff0c;能够支持…

九、pico+Unity交互开发——触碰抓取

一、VR交互的类型 Hover&#xff08;悬停&#xff09; 定义&#xff1a;发起交互的对象停留在可交互对象的交互区域。例如&#xff0c;当手触摸到物品表面&#xff08;可交互区域&#xff09;时&#xff0c;视为触发了Hover。 Grab&#xff08;抓取&#xff09; 概念&#xff…

记录一个容易混淆的 Spring Boot 项目配置文件问题

记录一个容易混淆的 Spring Boot 项目配置文件问题 去年&#xff0c;我遇到了这样一个问题&#xff1a; 在这个例子中&#xff0c;由于密码 password 以 0 开头&#xff0c;当它被 Spring Boot 的 bean 读取时&#xff0c;前导的 0 被自动去掉了。这导致程序无法正确读取密码。…

【Next.js 项目实战系列】07-分配 Issue 给用户

原文链接 CSDN 的排版/样式可能有问题&#xff0c;去我的博客查看原文系列吧&#xff0c;觉得有用的话&#xff0c;给我的库点个star&#xff0c;关注一下吧 上一篇【Next.js 项目实战系列】06-身份验证 分配 Issue 给用户 本节代码链接 Select Button​ # /app/issues/[i…

MySQL 查找连续相同名称的记录组,并保留每组内时间最大的一条记录

要求&#xff1a;查找连续相同名称的记录组&#xff0c;并保留每组内时间最大的一条记录&#xff0c;同时计算每组记录的 num 总和。 今天有人问了我一个问题&#xff0c;大致就是下面这样的数据结构&#xff08;原谅我实在不知道怎么描述这个问题&#xff09; 然后需要得到下面…

【C++】C++多态世界:从基础到前沿

【C】C多态世界&#xff1a;从基础到前沿 多态的定义及实现1. 虚函数2. 虚函数的重写3. C11 override 和 final重载、覆盖(重写)、隐藏(重定义)的对比&#xff08;关键&#xff09; 抽象类1. 概念2. 接口继承和实现继承 多态的原理1虚函数表2. 虚函数存在哪的&#xff1f;虚表存…

与ai一起作诗(《校园清廉韵》)

与ai对话犹如拷问自己的灵魂&#xff0c;与其说ai助力还不如说在和自己对话。 (笔记模板由python脚本于2024年10月19日 19:18:33创建&#xff0c;本篇笔记适合喜欢python和诗歌的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&…

基于Django的推荐系统、人脸识别登录、微信支付Demo、打卡门禁系统

基于Django的推荐系统、人脸识别登录、微信支付Demo、打卡门禁系统 1、推荐系统 图书管理、电影推荐、音乐推荐、在线课程选修、旅游推荐系统 图书管理点我跳转 电影管理点我跳转 课程管理点我跳转 2、算法 基于用户协同过滤推荐、物品协同过滤推荐、神经网络推荐、随机森…

Linux中如何理解一切皆文件

根据之前的学习我们会有一些少许的疑惑&#xff0c;我们的stdin &#xff0c;stdout&#xff0c;stderr访问的是键盘显示器&#xff0c;然而键盘显示器等他们都有一个共同的特点就是他们都是外设&#xff0c;那么这些外设是怎么被看成是文件的呢&#xff1f; 看图可以知道硬件的…

Jmeter 实战 JDBC配置

​ JDBC JDBC&#xff08;Java Database Connectivity&#xff09;是一种用于执行SQL语句的Java API。通过这个API&#xff0c;可以直接连接并执行SQL脚本&#xff0c;与数据库进行交互。 使用JMeter压力测试时&#xff0c;操作数据库的场景 在使用JMeter进行接口压力测试时…

【去哪儿-注册安全分析报告-缺少轨迹的滑动条】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

什么是分库分表?为什么要分库分表?什么时候需要分库分表?怎么样拆分?(数据库分库分表详解)

文章目录 1、什么是分库分表&#xff1f;1.1、分库分表的概念1.2、分库分表的方式1.2.1、垂直分库1.2.2、垂直分表1.2.3、水平分库1.2.4、水平分表 2、为什么要分库分表&#xff1f;3、什么时候需要分库分表&#xff1f;4、分库分表的数据路由4.1、数据路由的目的4.2、数据路由…