【Go 快速入门】泛型 | 类型约束 | 泛型接口 | 通用数据结构

文章目录

本节项目地址:06-GenericsQueue

泛型

如果你经常要分别为不同的类型写完全相同逻辑的代码,那么使用泛型将是最合适的选择

类型参数

类型形参列表:T int | float64

go 复制代码
func min[T int | float64](a, b T) T {
	if a <= b {
		return a
	}
	return b
}

func function01() {
	x := min[int](10, 20)
	y := min[float64](0.1, -0.2)
	fmt.Println(x, y) // 10 -0.2
}

类型形参:T,类型实参:int

类型实例化

min 函数提供类型参数(在本例中为 intfloat64 )称为实例化。

类型实例化:

  • 编译器在整个泛型函数或类型中,将所有类型形参替换为它们各自的类型实参。
  • 编译器验证每个类型参数是否满足相应的约束。
go 复制代码
fmin := min[float64] // 类型实例化
z := fmin(1.1, 2.2)
fmt.Println(z) // 1.1

类型约束

Go支持将类型约束单独拿出来定义到接口中,从而让代码更容易维护。

go 复制代码
type Int interface {
    int | int8 | int16 | int32 | int64
}
type Uint interface {
    uint | uint8 | uint16 | uint32
}
type Float interface {
    float32 | float64
}
type Slice[T Int | Uint | Float] []T  // 使用 '|' 将多个接口类型组合

两种常见的类型约束:

类型约束字面量 :通常 interface{} 可省略

go 复制代码
func max[T interface{int | float64}](a, b T)  T {
	if a >= b {
		return a
	}
	return b
}

约束类型:事先定义,并支持复用

go 复制代码
type Value interface {
	int | float64
}

func max1[T Value](a, b T) T {
	if a >= b {
		return a
	}
	return b
}

几种语法错误:

  1. 定义泛型类型,基础类型不能只有类型形参:
go 复制代码
// 类型形参不能单独使用
type Type[T int | float32] T
  1. 类型约束语义被编译器误认为是表达式:
go 复制代码
// 误认为定义一个存放切片的数组,长度 T * int
type NewType [T * int][]T

// | 误以为是按位或
type NewType[T *int | *float32] []T

解决办法:可使用 interface{} 包裹或加上逗号:

go 复制代码
type NewType[T interface{*int}] []T
type NewType[T interface{*int | *float64}] []T

// 类型约束只有一个类型,可以添加逗号消除歧义
type NewType[T *int,] []T

类型集

从 Go 1.18 开始,一个接口不仅可以嵌入其他接口,还可以嵌入任何类型、类型的联合或共享相同底层类型的无限类型集合。

当用作类型约束时,由接口定义的类型集精确地指定允许作为相应类型参数的类型。

  • | 符号

T1 | T2表示类型约束为 T1T2 这两个类型的并集

  • ~ 符号

~T 表示所有底层类型是 T 的类型集合,~ 后面只能是基本类型

go 复制代码
type Map[K int | string, V float32 | float64] map[K]V
type IValue interface {
	~string | ~int
}
type MyInt int

func sum[T IValue](a []T) (res T) {
	for _, e := range a {
		res += e
	}
	return
}

func function04() {
	m := Map[int, float64]{}
	m[1] = 1.0
	m[2] = 2.0
	fmt.Println(m) // map[1:1 2:2]

	a := []MyInt{1, 2, 3}
	fmt.Println(sum(a)) // 6
}
并集 & 交集
go 复制代码
// 类型并集
type Int interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type Uint interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
// 类型交集
type A interface {
	Int
	Uint
	~string
}
  1. 类型取并集时,用 | 连接多个类型,这些类型必须不交集,相交类型是接口不受约束
go 复制代码
// 错误,overlapping terms ~int and int
type B interface {
	int | ~int
}

// 正确
type B interface {
	int | interface{ ~int }
}
  1. 并集中不能有类型实参
go 复制代码
// 错误
type C[T int | string] interface {
	~float64 | T
}
  1. 接口不能直接或间接并入自己
go 复制代码
// 错误,D嵌入E,间接并入了自己
type D interface {
	E
}

type E interface {
	D
}
// 错误
type F interface {
	int | ~float64 | F
}
  1. 带方法的接口,不能写入接口的并集中
go 复制代码
type ReadWriter[T any] interface {
	Read(data T) (newData T)
	Writer(data T) error
}
// 错误
type G interface {
	int | ReadWriter[int]
}

泛型接收者

定义一个新类型后,可以给新类型添加方法,那么同样也可以给泛型类型添加方法,如下:

  • 本例子为泛型类型 MyFloat[T] 定义了类型方法 avg,计算数组平均值
  • 在使用泛型类型前,始终需要先对类型实参实例化
go 复制代码
type MyFloat[T ~float32 | ~float64] []T

func (mf MyFloat[T]) avg() T {
	var res T
	for _, e := range mf {
		res += e
	}
	return res / T(len(mf))
}

func function05() {
	var fa MyFloat[float32] = []float32{1.0, 2.1, 3.2}
	fmt.Println(fa.avg()) // 2.1000001
	f64 := MyFloat[float64]{1.0, 2.1, 3.2}
	fmt.Println(f64.avg()) // 2.1
}

泛型方法

Go 目前不支持泛型方法,如下:

go 复制代码
type MyStruct struct {}
// 错误:Method cannot have type parameters
func (ms MyStruct) Add[T int | string](other T) MyStruct {}

在方法中使用泛型,可以通过接收者来使用,如下:

go 复制代码
type MyStruct[T int | string] struct {
	value T
}

func (ms MyStruct[T]) Add(other T) MyStruct[T] {
	ms.value += other
	return ms
}

func function06() {
	a := MyStruct[int]{1}
	b := MyStruct[string]{"1"}
	fmt.Println(a.Add(2), b.Add("34")) // {3} {134}
}

泛型接口

两种接口类型
  1. 基本接口:接口中只有方法

  2. 一般接口:接口中不只有方法,还有类型

一般接口不能用来定义变量,只能用于泛型的类型约束中。

go 复制代码
// 基本接口
type MyErr interface {
	Error() string
}

// 一般接口
type MyUint interface {
	~uint | ~uint8 | ~uint16
}

func function07() {
	var a MyErr = fmt.Errorf("这是基本接口")
	fmt.Println(a.Error()) // 这是基本接口
	// 错误:不能使用一般接口定义变量
	// var b MyUint
}
泛型接口

泛型接口,Processer[T] 在实例化后,即为基本接口,可以用于创建变量,如下 XML 类型实现了 Processer[string] 接口:

go 复制代码
type Processer[T any] interface {
	Process(data T) T
	Save(data T) error
}

type XML struct{}

func (x XML) Process(data string) (newData string) {
	return ""
}
func (x XML) Save(data string) error {
	return errors.New("")
}

func function08() {
	var a Processer[string] = XML{}
	a.Process("")
	a.Save("")
}

泛型接口,ProcesserNormal[T] 实例化后,即为一般接口,只能用于类型约束,不能用于定义变量。笔者没探索出来有啥用 ~O(∩_∩)O~

go 复制代码
type ProcesserNormal[T any] interface {
	int | ~struct{ value T }
	Process(data T) T
	Save(data T) error
}

其余需要注意的:

  1. 匿名结构体不支持泛型
go 复制代码
type TypeStruct[T int | string] struct {
	Name string
	Data []T
}

func function03() {
	x := TypeStruct[int]{"Cauchy", []int{1, 2, 3}}
	fmt.Println(x) // {Cauchy [1 2 3]}

	/* 匿名结构体不支持泛型
	y := struct[T []int|[]string]{
		Name string
		Data T
	}[int]{
		"AQ",
		[]int{1, 2, 3},
	}
	*/
}
  1. 当你需要针对不同类型书写同样的逻辑,使用泛型来简化代码是最好的 (比如你想写个队列,写个链表、栈、堆之类的数据结构)

泛型实现通用数据结构

通用数据结构实现参考:https://pkg.go.dev/github.com/emirpasic/gods/v2

数组链表

下述实现,提及 Go 内置的一个可比较对象类型 ------ comparable接口。comparable 接口是由所有可比较类型实现的接口,只能用作类型参数约束,不能用作变量类型。

  1. 泛型容器接口 containers/containers.go,所有容器都需要实现其内部方法:
go 复制代码
type Container[T any] interface {
	Empty() bool
	Size() int
	Clear()
	Values() []T
	String() string
}
  1. 泛型链表接口 lists/lists.go,所有链表都要实现其内部方法:
go 复制代码
type List[T comparable] interface {
	Get(index int) (T, bool)
	Remove(index int)
	Add(values ...T)
	Contains(values ...T) bool
	Sort(comparator utils.Comparator[T])
	Insert(index int, values ...T)
	Set(index int, value T)

	containers.Container[T]
}
  1. 泛型可比较对象 utils/comparator.go
go 复制代码
type Comparator[T any] func(x, y T) int
  1. 泛型数组链表 lists/arraylist/arraylist.go
go 复制代码
package arraylist

import (
	"06-GenericsTest/lists"
	"06-GenericsTest/utils"
	"fmt"
	"slices"
	"strings"
)

// 断言 List 实现接口 lists.List
var _ lists.List[int] = (*List[int])(nil)

// List 链表结构
type List[T comparable] struct {
	elements []T
	size     int
}

const (
	growthFactor = float32(2.0)  // 扩容因子
	shrinkFactor = float32(0.25) // 缩容因子
)

// New 创建链表
func New[T comparable](values ...T) *List[T] {
	list := &List[T]{}
	if len(values) > 0 {
		list.Add(values...)
	}
	return list
}

// Add 向链表末尾添加元素
func (l *List[T]) Add(values ...T) {
	l.growBy(len(values))
	for _, value := range values {
		l.elements[l.size] = value
		l.size++
	}
}

// Get 获取下标 index 的元素
func (l *List[T]) Get(index int) (T, bool) {
	if !l.withinRange(index) {
		var t T
		return t, false
	}
	return l.elements[index], true
}

// Remove 移除下标 index 的元素
func (l *List[T]) Remove(index int) {
	if !l.withinRange(index) {
		return
	}
	clear(l.elements[index : index+1])
	copy(l.elements[index:], l.elements[index+1:l.size])
	l.size--

	l.shrink()
}

// Contains 判断是否包含元素
func (l *List[T]) Contains(values ...T) bool {
	for _, searchValue := range values {
		found := false
		for i := 0; i < l.size; i++ {
			if l.elements[i] == searchValue {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

// Values 获取链表元素切片
func (l *List[T]) Values() []T {
	newElements := make([]T, l.size, l.size)
	copy(newElements, l.elements[:l.size])
	return newElements
}

// IndexOf 获取元素所在下标
func (l *List[T]) IndexOf(value T) int {
	if l.size == 0 {
		return -1
	}
	for index, element := range l.elements {
		if element == value {
			return index
		}
	}
	return -1
}

// Empty 判断链表为空
func (l *List[T]) Empty() bool {
	return l.size == 0
}

// Size 获取元素个数
func (l *List[T]) Size() int {
	return l.size
}

// Clear 清空链表
func (l *List[T]) Clear() {
	l.size = 0
	l.elements = []T{}
}

// Sort 链表元素排序
func (l List[T]) Sort(comparator utils.Comparator[T]) {
	if len(l.elements) < 2 {
		return
	}
	slices.SortFunc(l.elements[:l.size], comparator)
}

// Swap 交换两个元素
func (l *List[T]) Swap(i, j int) {
	if l.withinRange(i) && l.withinRange(j) {
		l.elements[i], l.elements[j] = l.elements[j], l.elements[i]
	}
}

// Insert 指定位置插入
func (l *List[T]) Insert(index int, values ...T) {
	if !l.withinRange(index) {
		if index == l.size {
			l.Add(values...)
		}
		return
	}
	length := len(values)
	l.growBy(length)
	l.size += length
	copy(l.elements[index+length:], l.elements[index:l.size-length])
	copy(l.elements[index:], values)
}

// Set 指定位置设置元素
func (l *List[T]) Set(index int, value T) {
	if !l.withinRange(index) {
		if index == l.size {
			l.Add(value)
		}
		return
	}
	l.elements[index] = value
}

// String 字符串显示链表
func (l *List[T]) String() string {
	str := "ArrayList: "
	values := make([]string, 0, l.size)
	for _, value := range l.elements[:l.size] {
		values = append(values, fmt.Sprintf("%v", value))
	}
	str += "[" + strings.Join(values, ", ") + "]"
	return str
}

// withinRange 判断是否越界
func (l *List[T]) withinRange(index int) bool {
	return index >= 0 && index < l.size
}

// resize 链表底层切片重新分配地址
func (l *List[T]) resize(cap int) {
	newElements := make([]T, cap)
	copy(newElements, l.elements)
	l.elements = newElements
}

// growBy 判断是否增加 n 各元素需要扩容
func (l *List[T]) growBy(n int) {
	currentCapacity := cap(l.elements)
	if l.size+n >= currentCapacity {
		newCapacity := int(growthFactor * float32(currentCapacity+n))
		l.resize(newCapacity)
	}
}

// shrink 缩容
func (l *List[T]) shrink() {
	if shrinkFactor == 0.0 {
		return
	}
	currentCapacity := cap(l.elements)
	if l.size <= int(float32(currentCapacity)*shrinkFactor) {
		l.resize(l.size)
	}
}

数组队列

  1. 泛型队列接口 queues/queues.go
go 复制代码
// Queue 泛型队列接口
type Queue[T comparable] interface {
	Enqueue(value T)
	Dequeue() (value T, ok bool)
	Peek() (value T, ok bool)

	containers.Container[T]
}
  1. 泛型数组队列 queues/arrayqueue/arrayqueue.go
go 复制代码
package arrayqueue

import (
	"06-GenericsTest/lists/arraylist"
	"06-GenericsTest/queues"
	"fmt"
	"strings"
)

var _ queues.Queue[int] = (*Queue[int])(nil)

// Queue 队列结构
type Queue[T comparable] struct {
	list *arraylist.List[T]
}

// New 创建队列
func New[T comparable]() *Queue[T] {
	return &Queue[T]{list: arraylist.New[T]()}
}

// Enqueue 入队
func (q *Queue[T]) Enqueue(value T) {
	q.list.Add(value)
}

// Dequeue 出队
func (q *Queue[T]) Dequeue() (value T, ok bool) {
	value, ok = q.list.Get(0)
	if ok {
		q.list.Remove(0)
	}
	return
}

// Peek 队头元素
func (q *Queue[T]) Peek() (value T, ok bool) {
	return q.list.Get(0)
}

// Empty 判断队列为空
func (q *Queue[T]) Empty() bool {
	return q.list.Empty()
}

// Size 获取队列元素个数
func (q *Queue[T]) Size() int {
	return q.list.Size()
}

// Clear 清空队列
func (q *Queue[T]) Clear() {
	q.list.Clear()
}

// Values 获取队列元素切片
func (q *Queue[T]) Values() []T {
	return q.list.Values()
}

// String 字符串显示队列
func (q *Queue[T]) String() string {
	str := "ArrayQueue: "
	values := []string{}
	for _, value := range q.list.Values() {
		values = append(values, fmt.Sprintf("%v", value))
	}
	str += "[" + strings.Join(values, ", ") + "]"
	return str
}

参考:https://blog.csdn.net/u014374975/article/details/133905842

相关推荐
Chris _data6 分钟前
二叉树oj题解析
java·数据结构
Lenyiin1 小时前
02.06、回文链表
数据结构·leetcode·链表
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
爱摸鱼的孔乙己1 小时前
【数据结构】链表(leetcode)
c语言·数据结构·c++·链表·csdn
烦躁的大鼻嘎2 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
C++忠实粉丝2 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
ifanatic3 小时前
[面试]-golang基础面试题总结
面试·职场和发展·golang
懒是一种态度3 小时前
Golang 调用 mongodb 的函数
数据库·mongodb·golang
daiyang123...4 小时前
测试岗位应该学什么
数据结构
XINGTECODE4 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang