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

news2024/11/30 2:35:39

在这里插入图片描述

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

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

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,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语言的泛型特性,并在实际项目中灵活运用泛型来提高代码质量和开发效率。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试用例如何进行评估?4个指标

测试用例评估是确保软件测试活动能够达到预期目标的关键步骤。评估测试用例的有效性和质量&#xff0c;对于确保软件产品的质量和可靠性至关重要。如果未对测试用例进行评估&#xff0c;可能导致测试用例不完整、不准确或无效&#xff0c;进而引发需求遗漏、测试不充分等问题&a…

241015_把一个文件夹中的所有图片按照序列命名

241015_把一个文件夹中的所有图片按照序列命名(数据集重命名) 在数据集制作过程中&#xff0c;我们经常会遇到合并图片数据集后命名格式不统一或者因重复命名自动添加&#xff08;1&#xff09;&#xff08;2&#xff09;的问题&#xff0c;以下是一段代码&#xff0c;对合并后…

中国研究员使用量子计算机破解 RSA 加密

由上海大学的 Wang Chao 领导的研究团队发现&#xff0c;D-Wave 的量子计算机可以优化问题解决&#xff0c;从而可以攻击 RSA 等加密方法。 中国研究人员公布了一种使用 D-Wave 的量子退火系统来破解经典加密的方法&#xff0c;这可能会加快量子计算机对广泛使用的加密系统构成…

推荐系统架构

推荐系统架构 推荐和搜索系统核心的的任务是从海量物品中找到用户感兴趣的内容。在这个背景下&#xff0c;推荐系统包含的模块非常多&#xff0c;每个模块将会有很多专业研究的工程和研究工程师&#xff0c;作为刚入门的应届生或者实习生很难对每个模块都有很深的理解&#xf…

鼠标右键删除使用Visual Studio 打开(v)以及恢复【超详细】

鼠标右键删除使用Visual Studio 打开&#xff08;v&#xff09; 1. 引言2. 打开注册表3. 进入对应的注册表地址4. 右键删除 AnyCode 项5. 效果6. 备份注册表文件——恢复菜单 1. 引言 安装完 Visual Studio 鼠标右键总有 “使用Visual Studio 打开(v)”&#xff0c;让右键菜单…

windows修改文件最后修改时间

一、需要修改日期的文件 背景&#xff1a;有时候我们需要做一些文件定期删除的操作&#xff0c;但是测试时候并不一定有符合测试的文件&#xff0c;这时候就需要可以方便的修改文件的最后修改时间。 系统环境&#xff1a;windows 测试文件&#xff1a;如上 修改时间方式&#x…

Linux网络编程(三)-UDP协议及网络通信详解

1.UDP协议 概念&#xff1a; 除了 TCP 协议外&#xff0c;还有 UDP 协议&#xff0c;想必大家都听过说&#xff0c;UDP 是 User Datagram Protocol 的简称&#xff0c;中文名是用户数据报协议&#xff0c;是一种无连接、不可靠的协议&#xff0c;同样它也是工作在传顺层。它只…

基于FreeRTOS的LWIP移植

目录 前言一、移植准备工作二、以太网固件库与驱动2.1 固件库文件添加2.2 库文件修改2.3 添加网卡驱动 三、LWIP 数据包和网络接口管理3.1 添加LWIP源文件3.2 Lwip文件修改3.2.1 修改cc.h3.2.2 修改lwipopts.h3.2.3 修改icmp.c3.2.4 修改sys_arch.h和sys_arch.c3.2.5 修改ether…

利用Python filestream实现文件流读

在 Python 中&#xff0c;文件流&#xff08;filestream&#xff09;操作通过内置的 open() 函数实现&#xff0c;它提供了对文件的读取、写入、以及流控制的支持。常见的文件模式包括&#xff1a; r&#xff1a;只读模式&#xff08;默认&#xff09;。w&#xff1a;写入模式…

用Python构建动态折线图:实时展示爬取数据的指南

背景/引言 随着大数据和人工智能的不断发展&#xff0c;实时数据分析变得越来越关键&#xff0c;尤其是在金融市场中。股市数据的实时可视化可以帮助投资者快速做出决策&#xff0c;避免错失良机。Python 凭借其强大的数据处理能力和丰富的可视化库&#xff0c;成为分析和展示…

你不是算法工程师,就可以不了解AI大模型技术吗?

身处人工智能的大浪潮之中&#xff0c;除了算法工程师&#xff0c;其他的角色也都应当对人工智能大模型技术有一定的了解。所以&#xff0c;笔者将针对“什么是人工智能&#xff1f;”“非技术人员对于人工智能大模型的理解存在哪些门槛&#xff1f;”等问题与大家分享自己的见…

项目集成工作流,走审批流程,activiti,springboot,集成工作流,业务审批,驳回,会签,流程设计

前言 activiti工作流引擎项目&#xff0c;企业erp、oa、hr、crm等企事业办公系统轻松落地&#xff0c;一套完整并且实际运用在多套项目中的案例&#xff0c;满足日常业务流程审批需求。 项目源码配套文档获取&#xff1a;本文末个人名片直接获取。 一、项目形式 springboot…

健康补充维生素

在快节奏的现代生活中&#xff0c;健康养生已成为我们不可忽视的重要议题。而提及养生&#xff0c;维生素这一关键词往往跃然纸上&#xff0c;它们作为人体不可或缺的微量营养素&#xff0c;对维持生命活动、促进健康起着至关重要的作用。今天&#xff0c;就让我们深入探讨如何…

中小型医院网站:Spring Boot框架详解

5 系统实现 5.1 用户功能模块的实现 用户进入本系统可查看系统信息&#xff0c;包括首页、门诊信息、药库信息以及系统公告信息等&#xff0c;系统前台主界面展示如图5-1所示。 图5-1系统前台主界面图 5.1.1用户登录界面 用户要想实现预约挂号功能&#xff0c;必须登录系统&a…

修改Linux的IP地址

方法一&#xff08;特点&#xff1a;命令执行后&#xff0c;IP立即修改&#xff0c;但重启后会恢复原来的IP地址&#xff09; 1.含义&#xff1a; inet ip地址 netmask 子网掩码 broadcast 广播地址 inet 192.168.44.129 netmask 255.255.255.0 broadcast 192.168.1.255 …

仅涨粉1.3万、清空橱窗,贾跃亭直播带货这么快就哑火了?

还记得上周&#xff0c;贾跃亭声势浩大的做了个重大决定&#xff0c;也就是几个月前说的要个人IP商业化这盘菜端到了直播带货行业。‍‍ 当时&#xff0c;说他口气大&#xff0c;那真是一点也不小&#xff0c;比如要给中美人民、中美零售业、中美产品、中美品牌&#xff0c;搭一…

LeNet-5(论文复现)

LeNet-5&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 LeNet-5&#xff08;论文复现&#xff09;概述LeNet-5网络架构介绍训练过程测试过程使用方式说明 概述 LeNet是最早的卷积神经网络之一。1998年&#xff0c;Yann LeCun第一次将LeN…

站在用户视角审视:以太彩光与PON之争

作者:科技作家-郑凯 园区,是企业数字化转型的“中心战场”。 云计算、大数据、人工智能等数智化技术在园区里“战火交织”;高清视频、协同办公,智慧安防等大量创新应用产生的海量数据在园区内“纵横驰骋”;加上大量的IOT和智能化设备涌入“战场”,让园区网络面对着难以抵御的…

基于YOLOv9的空中飞鸟识别检测系统(附项目源码和数据集下载)

项目完整源码与模型 YOLOv9实现源码&#xff1a;项目完整源码及教程-点我下载YOLOv5实现源码&#xff1a;项目完整源码及教程-点我下载YOLOv7实现源码&#xff1a;项目完整源码及教程-点我下载YOLOv8实现源码&#xff1a;项目完整源码及教程-点我下载数据集&#xff1a;空中飞…

等保测评的技术要求与管理要求详解

等保测评&#xff0c;即网络安全等级保护测评&#xff0c;是根据《中华人民共和国网络安全法》、《信息安全技术网络安全等级保护基本要求》等相关法规和标准&#xff0c;对信息系统的安全性进行评估的过程。等保测评分为技术要求和管理要求两大方面&#xff0c;旨在确保信息系…