Go Generic 入门笔记

Hashnode好读版本


类型安全

过往使用interface{}

a和b的类型在执行时才会被检查,这就增加了出错的可能性。

func Add(a, b interface{}) interface{} {    return a.(int) + b.(int)  // 需要type assertion,且不安全}

参考type assertion

Generic 类型约束

泛型透过在编译时期进行类型检查,来解决这个问题。

func Add[T Addable](a, b T) T {    return a + b  // 类型安全}

Addable是一个类型约束,只允许那些满足某些条件的类型(比如,可以进行加法操作的类型)作为泛型参数。

性能议题

Generic 由于其高度抽象,可能会让人担心性能损失议题. 但事实上, 在Go语言中, Generic的实现方式是在编译时期产生特定类型的程式码, 因此性能损失议题是可以控制评估的.

// 编译时期会产生以下程式码func Add_int(a, b int) int {    return a + b}func Add_float64(a, b float64) float64 {    return a + b}

类型参数

基础语法

在Go中,泛型的类型参数通常使用中括号来声明,紧随在函数或结构体名称之后。

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

T是一个类型参数, 并且使用了any类型约束, 意味着它可以是任何类型.

多类型参数

Go泛型不仅支持单一个类型参数, 还可以定义多个类型参数.

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

Pair函数接受两个不同类型约束的参数a和b, 并回传这两个参数类型.

类型约束

内建约束

Go内置了几种类型约束,如 any,表示任何类型都可以作为参数。

func PrintSlice[T any](s []T) {    for _, v := range s {        fmt.Println(v)    }}

自定义约束

除了内建约束,Go还允许开发者定义自己要的约束条件。这通常是透过interface来实现的。

// Addable只允许int 或 float64类型type Addable interface {    int | float64}func Add[T Addable](a, b T) T {    return a + b}

Addable 是一个自定义的类型约束.

底层类型(Underlying Type)

在Go中,每个类型都有一个底层类型: 对于预先定义的类型,比如 int、float64 等,底层类型就是它们自己。对于类型定义(比如 type MyInt int),底层类型是在类型定义之前的类型。

~ 符号的作用

~ 符号用于表示与指定类型有相同底层类型的所有类型。当你在类型参数的约束中使用 ~ 符号时,你指定了一个类型集合,这个集合包含所有底层类型与约束中指定的类型相同的类型。

假阿设我们有以下类型定义:

type MyInt inttype YourInt int

这里,MyIntYourInt 都是基于 int 类型定义的。它们的底层类型都是 int。

如果我定义一个函数,我们希望这个函数接受任何底层类型为 int 的类型,我们可以使用 ~ 符号来实现:

func PrintInt[T ~int](t T) {    fmt.Println(t)}

使用时

var a int = 5var b MyInt = 10var c YourInt = 15PrintInt(a) PrintInt(b) PrintInt(c)

在这个例子中,PrintInt 可以接受 int、MyInt 和 YourInt 类型的参数类型,因为它们的底层类型都是 int。

通过使用 ~ 符号,Go的泛型允许你编写更灵活和通用的程式码架构,同时保持类型安全。

泛型函数与泛型结构体

泛型函数

Max函数接受a和b只要满足comparable约束的类型都能作为参数, 并且回传也是满足comparable约束的类型.

func Max[T comparable](a, b T) T {    if a > b {        return a    }    return b}

泛型结构体

除了常用在函数上, 也支持泛型结构体。

Box 是一个泛型结构体,它有一个Content 属性,类型为 T。

type Box[T any] struct {    Content T}

泛型成员函数

在泛型结构体中,你还可以定义泛型成员方法。

func (b Box[T]) Empty() bool {    return b.Content == nil}

Go泛型进阶特性

类型列表

类型组合

Go泛型允许使用类型组合,在一个约束中指定多种允许的类型。

type Numeric interface {    int | float64}func Sum[T Numeric](s []T) T {    var total T    for _, v := range s {        total += v    }    return total}

Numeric 约束允许 int 和 float64 类型,使得 Sum 函数能在这两种类型的slice上进行操作。

多约束

多约束的概念,即一个介面类型需要满足多个介面(Union)。 介面的联合(Union)。这允许一个介面可以由多个介面组成,一个类型只需要满足其中任何一个介面即可。

type Serializable interface {    json.Marshaler | xml.Marshaler}

Serializable 是一个接口,它由两个介面组成:json.Marshalerxml.Marshaler。这意味着,任何实现了 json.Marshalerxml.Marshaler 介面的类型都被认为实现了 Serializable 介面。这称为多约束或介面联合。

泛型与介面的交互

泛型作为介面的方法

你可以在介面中定义包含泛型的方法。

type Container[T any] interface {    Add(element T)    Get(index int) T}

这里的 Container 介面可以用于任何类型的容器,如slice、list或自定义容器类型,只要这些容器实现了 Add 和 Get 方法。

使用介面约束泛型

与泛型约束相似,介面也可以用于约束泛型类型。

type HumanLike interface {    IsHuman() bool}func PrintIfHuman[T HumanLike](entity T) {    if entity.IsHuman() {        fmt.Println(entity)    }}

HumanLike 是一个介面,IsHuman 是它的一个方法。 PrintIfHuman 是一个函数,它接受一个泛型参数 T,并且这个参数被约束为必须满足 HumanLike 介面。

泛型的常见场景

泛型资料结构

在实际应用中,泛型通常用于实现通用的资料结构,比如链表、伫列和堆栈。

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 {    element := s.elements[len(s.elements)-1]    s.elements = s.elements[:len(s.elements)-1]    return element}

Stack[T any] 定义了一个泛型结构体,T 是一个类型参数,可以是任何类型。 elements []T 是一个slice,用于储存stack中的元素。slice的元素类型是泛型类型 T。

这个泛型stack的实现是类型安全的,意味着如果建立了一个 Stack[int],你只能向其中添加 int 类型的元素,尝试使用其他类型的元素会在编译时报错。这提供了强类型检查的同时保持了程式码的灵活性和可重用性。

用于演算法的实现

泛型也在算法的实现中有广泛被应用,特别是那些不依赖于具体类型的演算法。

func Sort[T Ordered](arr []T) []T {    // 排序演算法的实现}

[T Ordered] 是泛型类型参数部分。T 是类型参数,而 Ordered 是对 T 的约束。 Ordered 是Go的一个预先定义好的介面,用于表示类型是可以排序的(即支持 <, <=, >, >= 操作)。 (arr []T) 是函数的参数,表示函数接受一个 T 类型的slice。 []T 是函数回传类型,表示函数回传一个 T 类型的slice。

Go泛型实作例子

泛型实作一个简单的array list

定义

一个泛型array list需要能够进行Add、Delete和Get元素。我们可以使用泛型来定义这样的资料结构。

type ArrayList[T any] struct {    items []T}func (al *ArrayList[T]) Add(item T) {    al.items = append(al.items, item)}func (al *ArrayList[T]) Get(index int) (T, error) {    if index < 0 || index >= len(al.items) {        return zero(T), errors.New("Index out of bounds")    }    return al.items[index], nil}func (al *ArrayList[T]) Delete(index int) error {    if index < 0 || index >= len(al.items) {        return errors.New("Index out of bounds")    }    al.items = append(al.items[:index], al.items[index+1:]...)    return nil}

使用

有一个ArrayList[int],我们加入整数 1 和 2,然后尝试取得索引为 1 的元素。

al := &ArrayList[int]{}al.Add(1)al.Add(2)element, err := al.Get(1) // output:element=2, err=nilerr = al.Delete(0) // 删除索引为 0 的元素

使用泛型打造快取元件

定义

快取元件通常需要储存任意类型的数据并能够在给定的时间内存取它们。我我们可以使用泛型和Go的内建 map 类型来实现。

type Cache[T any] struct {    store map[string]T}func (c *Cache[T]) Set(key string, value T) {    c.store[key] = value}func (c *Cache[T]) Get(key string) (T, bool) {    value, exists := c.store[key]    return value, exists}

使用

c := &Cache[string]{store: make(map[string]string)}c.Set("name", "John")value, exists := c.Get("name") // output:value="John", exists=true

泛型实作快速排序

定义

快速排序不依赖于具体的资料类型,因此很适合使用泛型来实现。

func QuickSort[T comparable](arr []T) []T {    if len(arr) < 2 {        return arr    }    pivot := arr[len(arr)/2]    var less, pivotList, greater []T    for _, x := range arr {        if x < pivot {            less = append(less, x)        } else if x > pivot {            greater = append(greater, x)        } else {            pivotList = append(pivotList, x)        }    }    less = QuickSort(less)    greater = QuickSort(greater)    // 合併结果    less = append(less, pivotList...)    less = append(less, greater...)    return less}
  arr := []int{3, 1, 4, 1, 5, 9, 2, 6, 5}    sortedArr := QuickSort(arr)    fmt.Println(sortedArr) // output: [1 1 2 3 4 5 5 6 9]

参考Tutorial: Getting started with generics


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章