初探Golang泛型

在Go语言中,泛型是指一种通用的编程特性,允许在不指定具体类型的情况下编写代码。在之前的Go版本中,该语言是没有泛型支持的,但自Go 1.18版本开始,引入了泛型支持。

泛型的引入主要是为了解决在编写通用算法和数据结构时所面临的类型安全和代码重复的问题。在没有泛型的情况下,开发人员通常需要使用接口或具体类型进行处理,这可能导致代码变得冗长、难以理解,并且可能带来性能损失。

引入泛型的好处包括:

  • 代码重用性: 泛型允许编写更通用、可重用的代码,因为它可以处理不同类型的数据而无需重复相似的代码。
  • 类型安全: 泛型可以提高类型安全性,因为它允许在编译时进行类型检查,避免在运行时出现类型错误。
  • 性能优化: 使用泛型可以避免在运行时进行类型断言和转换,从而提高程序的性能。
  • 更清晰的代码: 泛型可以使代码更清晰、简洁,减少冗余的类型转换和处理代码。

在Go语言中,泛型的语法采用类型参数的方式,允许在函数或数据结构中使用未知类型。以下是几个简单的泛型示例:

泛型函数

func Print[T any](value T) {
    fmt.Println(value)
}

func TestHello(t *testing.T) {
    Print("Hello, Generics!")
    Print(42)
    Print(3.14)
}

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

func TestSwap(t *testing.T) {
    a, b := 3, 4
    swappedA, swappedB := Swap(a, b)
    fmt.Printf("Original: a=%d, b=%d\n", a, b)
    fmt.Printf("Swapped: a=%d, b=%d\n", swappedA, swappedB)

    c, d := "ni", "wo"
    swappedC, swappedD := Swap(c, d)
    fmt.Printf("Original: c=%s, c=%s\n", c, d)
    fmt.Printf("Swapped: c=%s, d=%s\n", swappedC, swappedD)
}

泛型切片操作

/*
*泛型切片操作
使用泛型函数处理不同类型的切片,通过传递不同的切片和判断条件,可以轻松地实现通用的过滤操作
*/

/*
*
slice []T:一个泛型切片,可以包含任意类型的元素。
predicate func(T) bool:一个用于判断元素是否满足条件的函数,返回一个布尔值。
*/
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T //创建了一个空的切片result,然后遍历输入的切片slice。
    for _, value := range slice {
        //对于每个元素,通过predicate函数判断是否满足条件,如果满足条件则将元素追加到result中。
        if predicate(value) {
            result = append(result, value)
        }
    }
    //返回过滤后的切片result
    return result
}

func TestSliceGeneric(t *testing.T) {
    //创建了一个包含整数的切片numbers
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    //通过调用Filter函数,传入切片和一个用于判断偶数的匿名函数作为predicate,
    //获得了过滤后的切片evens。
    evens := Filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Even numbers:", evens)
}

泛型数据结构

/*
*泛型数据结构
 */
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() T {
    if len(s.items) == 0 {
        panic("stack is empty")
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item
}

func TestStructGeneric(t *testing.T) {
    stringStack := Stack[string]{}
    stringStack.Push("first")
    stringStack.Push("second")
    fmt.Println("Popped:", stringStack.Pop())

    intStack := Stack[int]{}
    intStack.Push(42)
    intStack.Push(99)
    fmt.Println("Popped:", intStack.Pop())
}

使用泛型前后代码对比

/**
前后对比
*/

var intSlice = []int{1, 2, 3, 4, 5}

// 不使用泛型的切片反转
func reverseIntSlice(slice []int) []int {
    length := len(slice)
    reversed := make([]int, length)

    for i, value := range slice {
        reversed[length-i-1] = value
    }

    return reversed
}

func TestBeforeGeneric(t *testing.T) {
    reversed := reverseIntSlice(intSlice)
    fmt.Println("Original Slice:", intSlice)
    fmt.Println("Reversed Slice:", reversed)
}

// 使用泛型的切片反转
func reverseSlice[T any](slice []T) []T {
    length := len(slice)
    reversed := make([]T, length)

    for i, value := range slice {
        reversed[length-i-1] = value
    }

    return reversed
}

/*
*
使用泛型的方式使得reverseSlice函数可以接受不同类型的切片,而不仅仅是int类型
*/
func TestAfterGeneric(t *testing.T) {

    reversedIntSlice := reverseSlice(intSlice)
    fmt.Println("Original Slice:", intSlice)
    fmt.Println("Reversed Slice:", reversedIntSlice)

    stringSlice := []string{"香蕉", "苹果", "哈密瓜"}
    reversedStringSlice := reverseSlice(stringSlice)
    fmt.Println("Original Slice:", stringSlice)
    fmt.Println("Reversed Slice:", reversedStringSlice)
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注