前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >搞懂Go泛型,看这一篇就够了

搞懂Go泛型,看这一篇就够了

作者头像
闫同学
发布2025-01-22 09:26:34
发布2025-01-22 09:26:34
14000
代码可运行
举报
文章被收录于专栏:扯编程的淡扯编程的淡
运行总次数:0
代码可运行

在Go语言1.17版本及后续的升级迭代中,泛型新特性无疑是非常重大的一次更新,这个特性的引入无疑为开发者们带来了更多的灵活性和代码复用性。虽然大部分功能不使用泛型也能正常实现,但是泛型带来的灵活性和效率值得我们进行学习和掌握,这篇文章就和大家一下讨论下Go语言的泛型。

什么是泛型?

泛型是一种编程语言特性,允许在编写代码时不指定具体的数据类型,而是在使用时再确定具体类型。原理层面,Go语言的泛型主要基于类型参数化和类型推断,在编译时为不同类型参数组合生成具体实现,以实现通用且类型安全的代码。通过泛型,可以编写更加通用和可重用的代码,避免重复代码的出现,提高代码的可维护性和灵活性。

Go 1.18版本中,泛型被正式引入,极大地增强了Go语言的表达能力。

泛型的基本语法

在Go中,泛型主要通过类型参数(Type Parameters)来实现。类型参数通常在函数、方法、类型(如结构体、接口)等声明中使用,使用方括号 [] 包裹。

类型参数的定义

类型参数的基本语法如下:

代码语言:javascript
代码运行次数:0
复制
func FunctionName[T any](param T) {
    // 函数体
}

这里的 [T any] 表示函数 FunctionName 有一个类型参数 T,并且 T 可以是任意类型。any 是一个预定义的接口类型,等价于 interface{},表示无类型限制。

多类型参数

如果需要使用多个类型参数,可以用逗号分隔:

代码语言:javascript
代码运行次数:0
复制
func FunctionName[T any, U comparable](param1 T, param2 U) {
    // 函数体
}

在这个例子中,FunctionName 函数有两个类型参数,T 可以是任意类型,U 必须是可比较的类型。

comparable 是 Go 1.18 引入的一个预定义标识符,它表示可以使用 == 和 != 运算符进行比较的类型,包括所有基本类型(如 int, float64, string 等)和某些复合类型(如数组、结构体等,但不包括切片、映射和函数)

类型约束(Type Constraints)

类型约束用于限制类型参数的可接受类型。Go通过接口来定义类型约束。

例如:

代码语言:javascript
代码运行次数:0
复制
type Number interface {
    ~int | ~float64
}

这里定义了一个 Number 接口,表示类型参数必须是 intfloat64 或它们的别名。

泛型的实践案例

为了更好地理解泛型的使用,下面通过几个具体的例子来展示泛型在实际开发中的应用。

示例一:泛型函数

假设我们需要编写一个函数,返回一组元素中的最大值。使用泛型可以使这个函数适用于多种类型。

代码语言:javascript
代码运行次数:0
复制
package main

import (
    "fmt"
)

func Max[T constraints.Ordered](slice []T) T {
    iflen(slice) == 0 {
        var zero T
        return zero
    }
    max := slice[0]
    for _, v := range slice {
        if v > max {
            max = v
        }
    }
    return max
}

func main() {
    ints := []int{1, 3, 2, 5, 4}
    floats := []float64{1.1, 3.3, 2.2, 5.5, 4.4}
    strings := []string{"apple", "banana", "cherry"}

    fmt.Println("Max int:", Max(ints))
    fmt.Println("Max float:", Max(floats))
    fmt.Println("Max string:", Max(strings))
}

解释:

1)函数 Max 使用了类型参数 Tconstraints.Ordered 约束,它确保了类型 T 是一个有序类型,因此可以使用 > 运算符进行比较。这样,代码就可以正确编译和运行了。

2)函数可以接受任何可比较类型的切片,并返回其中的最大值。

3)在 main 函数中,我们分别传入 intfloat64string 类型的切片,展示了泛型函数的通用性。

示例二:泛型数据结构

栈是一种常见的数据结构,使用泛型可以使其适用于任何数据类型。我们就使用栈来举个例子:

代码语言:javascript
代码运行次数:0
复制
package main

import (
    "fmt"
)

// 定义一个泛型栈
type Stack[T any] struct {
    elements []T
}

// 压栈
func (s *Stack[T]) Push(element T) {
    s.elements = append(s.elements, element)
}

// 弹栈
func (s *Stack[T]) Pop() (T, bool) {
    iflen(s.elements) == 0 {
        var zero T
        return zero, false
    }
    index := len(s.elements) - 1
    element := s.elements[index]
    s.elements = s.elements[:index]
    return element, true
}

// 查看栈顶元素
func (s *Stack[T]) Peek() (T, bool) {
    iflen(s.elements) == 0 {
        var zero T
        return zero, false
    }
    return s.elements[len(s.elements)-1], true
}

func main() {
    intStack := Stack[int]{}
    intStack.Push(10)
    intStack.Push(20)
    fmt.Println("Pop from intStack:", intStack.Pop())

    stringStack := Stack[string]{}
    stringStack.Push("hello")
    stringStack.Push("world")
    fmt.Println("Peek from stringStack:", stringStack.Peek())
}

解释:

1)定义了一个泛型栈 Stack[T any],其中 T 可以是任意类型。

2)提供了 PushPopPeek 方法,分别用于压栈、弹栈和查看栈顶元素。

3)在 main 函数中,创建了 int 类型和 string 类型的栈实例,展示了泛型数据结构的灵活性。

示例三:泛型接口

假设我们需要定义一个接口,表示可以序列化的类型。使用泛型接口可以使接口更加通用。

代码语言:javascript
代码运行次数:0
复制
package main

import (
    "encoding/json"
    "fmt"
)

// 定义泛型接口
type Serializable[T any] interface {
    Serialize() ([]byte, error)
    Deserialize(data []byte) (T, error)
}

// 实现 Serializable 接口的结构体
type Person struct {
    Name string
    Age  int
}

func (p *Person) Serialize() ([]byte, error) {
    return json.Marshal(p)
}

func (p *Person) Deserialize(data []byte) (Person, error) {
    var person Person
    err := json.Unmarshal(data, &person)
    return person, err
}

func main() {
    person := &Person{Name: "Alice", Age: 30}
    data, err := person.Serialize()
    if err != nil {
        fmt.Println("Serialization error:", err)
        return
    }
    fmt.Println("Serialized data:", string(data))

    newPerson, err := person.Deserialize(data)
    if err != nil {
        fmt.Println("Deserialization error:", err)
        return
    }
    fmt.Println("Deserialized Person:", newPerson)
}

解释:

1)定义了一个泛型接口 Serializable[T any],包含 SerializeDeserialize 方法。

2)Person 结构体实现了 Serializable 接口,实现了序列化和反序列化的功能。

3)在 main 函数中,展示了如何使用 Person 结构体进行序列化和反序列化。

泛型的优势

代码复用性高:通过泛型,可以编写适用于多种类型的通用代码,减少重复代码的编写。

类型安全:与使用 interface{} 不同,泛型在编译时会进行类型检查,避免了运行时的类型错误。

性能优化:泛型代码在编译时会生成具体类型的代码,避免了反射带来的性能开销。

注意事项

复杂度增加:泛型的引入虽然提升了代码的灵活性,但也可能增加代码的复杂度,尤其是对于初学者来说。

类型约束的合理使用:合理定义类型约束可以提高泛型的适用范围,但过于严格的约束可能限制泛型的通用性。

编译时间:泛型可能会增加编译时间,特别是在大量使用泛型的情况下。

小总结

Go语言的泛型为开发者提供了更强大的表达能力,使得代码更加简洁和可维护。除了上述内容之外,还有一些需要注意的地方,比如泛型约束:可以使用接口来定义类型约束,限制类型参数的范围。

代码语言:javascript
代码运行次数:0
复制
type Number interface {
 int | float64
}

func Add[T Number](a, b T) T {
 return a + b
}

还有就是类型近似的概念,在Go语言中,~符号用于表示类型近似,它允许接口类型匹配到具体类型及其底层类型。

具体来说,~int表示所有底层类型为int的类型,而不仅仅是int本身。这意味着,如果有一个自定义类型type MyInt int,那么MyInt也会满足~int的约束。

比如下面两段代码:

代码语言:javascript
代码运行次数:0
复制
type Number1 interface { 
    ~int | ~float64 
}

代码语言:javascript
代码运行次数:0
复制
type Number2 interface { 
    int | float64 
}

的区别在于,Number1接口可以被任何底层类型为intfloat64的类型实现,包括自定义类型。Number2接口只能被intfloat64类型实现,不能被自定义类型实现,即使这些自定义类型的底层类型是intfloat64。下面是一个示例,展示了这种区别:

代码语言:javascript
代码运行次数:0
复制
package main

import"fmt"

type MyInt int

func (i MyInt) String() string {
return fmt.Sprintf("MyInt(%d)", i)
}

func PrintNumber[T interface{ ~int | ~float64 }](n T) {
 fmt.Println(n)
}

func main() {
var a int = 42
var b MyInt = 42

 PrintNumber(a) // 输出: 42
 PrintNumber(b) // 输出: MyInt(42)
}

所以在Go项目中看到这种不常见的符号我们要知道其用意。

本篇结束~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 扯编程的淡 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是泛型?
  • 泛型的基本语法
    • 类型参数的定义
    • 多类型参数
    • 类型约束(Type Constraints)
  • 泛型的实践案例
    • 示例一:泛型函数
    • 示例二:泛型数据结构
    • 示例三:泛型接口
  • 泛型的优势
  • 注意事项
  • 小总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档