首页 > 后端开发 > Golang > Go 中的泛型:改变代码可重用性

Go 中的泛型:改变代码可重用性

Patricia Arquette
发布: 2025-01-08 06:20:41
原创
494 人浏览过

Go 1.18 中引入的泛型彻底改变了编写可重用和类型安全代码的方式。泛型带来了灵活性和强大的功能,同时保持了 Go 的简单哲学。然而,要了解细微差别、优点以及泛型与传统方法(如 interface{} )的比较,需要仔细观察。

让我们探索泛型的复杂性,深入研究约束,将泛型与接口{}进行比较,并演示它们的实际应用。我们还将讨论性能考虑因素和二进制大小的影响。让我们开始吧!

什么是泛型?

泛型允许开发人员编写可以在任何类型上操作的函数和数据结构,同时保持类型安全。泛型不依赖于涉及运行时类型断言的接口{},而是让您指定一组约束来规定类型上允许的操作。

语法

func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}
登录后复制
登录后复制
登录后复制

T: 类型参数,表示类型的占位符。

TypeConstraint:将 T 的类型限制为特定类型或一组类型。

parameterName T:参数使用泛型类型T.

ReturnType:函数还可以返回 T.

类型的值

示例

func Sum[T int | float64](a, b T) T {
    return a + b
}
登录后复制
登录后复制
登录后复制

func Sum: 声明函数名称 Sum

[T int | float64]: 指定引入 T 作为类型参数的类型参数列表,约束为特定类型(int 或 float64)。 Sum 函数只能采用 int 或 float64 参数,不能组合使用,两者都必须是 intfloat64。我们将在下面的部分中进一步探讨这一点。

(a, b T): 声明两个参数 a 和 b,均为 T 类型(泛型类型) ).

T: 指定函数的返回类型,与类型参数 T.

匹配

约束:泛型的构建块

约束定义哪些操作对于泛型类型有效。 Go 提供了强大的约束工具,包括实验性约束包(golang.org/x/exp/constraints)。

内置约束

Go 引入了带有泛型的内置约束,以提供类型安全,同时允许灵活地定义可重用和泛型代码。这些约束使开发人员能够对泛型函数或类型中使用的类型强制执行规则。

Go 有以下内置约束

  1. any:代表任意类型。它是 interface{} 的别名。当不需要约束时使用
func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}
登录后复制
登录后复制
登录后复制
  1. comparable:允许支持相等比较(== 和 !=)的类型。对于映射键、重复检测或相等检查很有用。这不能用于映射、切片和函数,因为这些类型不支持直接比较。
func Sum[T int | float64](a, b T) T {
    return a + b
}
登录后复制
登录后复制
登录后复制

实验限制

  1. constraints.Complex:允许复杂数字类型(complex64 和complex128)。
  2. constraints.Float:允许浮点数值类型(float32 和 float64)
  3. constraints.Integer:允许任何有符号和无符号整数(int8、int16、int32、int64、int、uint8、uint16、uint32、uint64 和 uint)
  4. constraints.Signed:允许任何有符号整数(int8、int16、int32、int64 和 int)
  5. constraints.Unsigned:允许任何无符号整数(uint8、uint16、uint32、uint64 和 uint)。
  6. constraint.Ordered:允许进行比较的类型(<.>、>=),支持所有数字类型和字符串(int、float64、string 等)。
func PrintValues[T any](values []T) {
    for _, v := range values {
        fmt.Println(v)
    }
}
登录后复制
登录后复制

自定义约束

自定义约束是定义泛型类型参数必须满足的一组类型或类型行为的接口。通过创建您自己的约束,我们可以;

  • 将类型限制为特定子集,例如数字类型。

  • 需要类型来实现特定的方法或行为。

  • 为您的通用函数和类型添加更多控制和特异性。

语法

func CheckDuplicates[T comparable](items []T) []T {
    seen := make(map[T]bool)
    duplicates := []T{}
    for _, item := range items {
        if seen[item] {
            duplicates = append(duplicates, item)
        } else {
            seen[item] = true
        }
    }
    return duplicates
}
登录后复制
登录后复制

示例

import (
     "golang.org/x/exp/constraints"
     "fmt"
)

func SortSlice[T constraints.Ordered](items []T) []T {
    sorted := append([]T{}, items...) // Copy slice
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i] < sorted[j]
    })
    return sorted
}

func main() {
    nums := []int{5, 2, 9, 1}
    fmt.Println(SortSlice(nums)) // Output: [1 2 5 9]

    words := []string{"banana", "apple", "cherry"}
    fmt.Println(SortSlice(words)) // Output: [apple banana cherry]
}
登录后复制
登录后复制

Sum 函数 只能使用 int、int64 和 float64 参数调用。

方法的限制

如果你想强制类型必须实现某些方法,你可以使用这些方法来定义它。

type Numeric interface {
    int | float64 | uint
}
登录后复制
登录后复制

Formatter 约束要求任何用作 T 的类型必须具有返回 字符串.

组合约束

自定义约束可以结合类型集和方法要求


type Number interface {
    int | int64 | float64
}

func Sum[T Number](a, b T) T {
    return a + b
}
登录后复制
此约束包括两种特定类型(

int、float54),并且需要存在 abs 方法。

泛型与接口{}

在引入泛型之前,使用interface{}来实现灵活性。然而,这种方法有局限性。

类型安全

  • interface{}:依赖运行时类型断言,增加运行时出错的几率。

  • 泛型:提供编译时类型安全性,在开发过程中尽早捕获错误。

表现

  • 接口{}:由于额外的运行时类型检查,速度较慢。

  • 泛型:更快,因为编译器会生成特定于类型的优化代码路径。

代码可读性

  • 界面{}:通常冗长且不太直观,使代码更难维护。

  • 泛型:更简洁的语法可以带来更直观和可维护的代码。

二进制大小

  • interface{}:生成更小的二进制文件,因为它不会为不同类型重复代码。

  • 泛型:由于类型专门化而略微增加二进制大小以获得更好的性能。

示例

func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}
登录后复制
登录后复制
登录后复制

代码运行良好,类型断言是开销。 Add 函数可以使用任何参数调用,a 和 b 参数可以是不同的类型,但是代码会在运行时崩溃。

func Sum[T int | float64](a, b T) T {
    return a + b
}
登录后复制
登录后复制
登录后复制

泛型消除了由不正确的类型断言引起的运行时恐慌的风险并提高了清晰度。

表现

泛型为每种类型生成专门的代码,与接口{}相比,可以带来更好的运行时性能。

二进制大小

存在一个权衡:泛型由于每种类型的代码重复而增加了二进制大小,但这与好处相比通常可以忽略不计。

Go 泛型的局限性

约束的复杂性: 虽然像 Constraints.Ordered 这样的约束简化了常见用例,但定义高度自定义的约束可能会变得冗长。

结构体中没有类型推断: 与函数不同,您必须为结构体显式指定类型参数。

func PrintValues[T any](values []T) {
    for _, v := range values {
        fmt.Println(v)
    }
}
登录后复制
登录后复制

仅限于编译时约束:Go 泛型专注于编译时安全,而 Rust 这样的语言使用生命周期和特征提供更强大的约束。

让我们进行基准测试——做得比说的更好

我们将实现一个具有接口{}和通用的简单队列,并对结果进行基准测试。

接口{}队列实现

func CheckDuplicates[T comparable](items []T) []T {
    seen := make(map[T]bool)
    duplicates := []T{}
    for _, item := range items {
        if seen[item] {
            duplicates = append(duplicates, item)
        } else {
            seen[item] = true
        }
    }
    return duplicates
}
登录后复制
登录后复制

通用队列实现

import (
     "golang.org/x/exp/constraints"
     "fmt"
)

func SortSlice[T constraints.Ordered](items []T) []T {
    sorted := append([]T{}, items...) // Copy slice
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i] < sorted[j]
    })
    return sorted
}

func main() {
    nums := []int{5, 2, 9, 1}
    fmt.Println(SortSlice(nums)) // Output: [1 2 5 9]

    words := []string{"banana", "apple", "cherry"}
    fmt.Println(SortSlice(words)) // Output: [apple banana cherry]
}
登录后复制
登录后复制
type Numeric interface {
    int | float64 | uint
}
登录后复制
登录后复制

结果分析

  • 执行时间:
    通用实现比 interface{} 版本快大约 63.64%,因为它避免了运行时类型断言并直接对给定类型进行操作。

  • 分配:
    Interface{} 版本的分配量增加了 3 倍,这主要是由于插入和检索值时的装箱/拆箱。这增加了垃圾收集的开销。

对于较大的工作负载,例如 100 万次入队/出队操作,性能差距会扩大。具有高吞吐量要求的实际应用程序(例如消息队列、作业调度程序)可以从泛型中受益匪浅。

最后的想法

Go 中的泛型在功能强大和简单性之间取得了平衡,为编写可重用和类型安全的代码提供了实用的解决方案。虽然不像 Rust 或 C 那样功能丰富,但与 Go 的极简主义哲学完美契合。了解约束等约束。有效有序和利用泛型可以极大地提高代码质量和可维护性。

随着泛型的不断发展,它们注定会在 Go 的生态系统中发挥核心作用。因此,潜入、实验并拥抱 Go 编程类型安全性和灵活性的新时代!

查看 github 存储库以获取一些关于泛型的示例。

GitHub logo 萨达南多达瓦达卡尔 / Go泛型

存储库包含 go 泛型的工作示例

Go 泛型:综合示例存储库

欢迎来到Go 泛型存储库!该存储库是 1.18 版本中引入的用于理解、学习和掌握 Go 中泛型的一站式资源。泛型为 Go 带来了类型参数的强大功能,使开发人员能够编写可重用且类型安全的代码,而不会影响性能或可读性。

该存储库包含精心策划的示例,涵盖广泛的主题,从基本语法到高级模式和实际用例。无论您是初学者还是经验丰富的 Go 开发人员,此集合都将帮助您在项目中有效地利用泛型。


?里面有什么

?基本通用程序

这些示例介绍了泛型的基本概念,帮助您掌握语法和核心功能:

  1. GenericMap:演示通用映射函数来转换任何类型的切片。
  2. 交换:一般交换两个值的简单而强大的示例。
  3. FilterSlice:显示如何过滤...


在 GitHub 上查看


以上是Go 中的泛型:改变代码可重用性的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板