刘沙河 刘沙河
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希

花开半夏,半夏花开
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希
  • go语言基础

  • go语言进阶

    • go 泛型
      • 泛型初识
      • 泛型基础概念
      • 泛型基本使用
        • 1. demo
        • 2. 类型形参的互相套用和约束
        • 3. 泛型的嵌套和约束
        • 4. 动态判断变量的类型
        • 5. ~ 指定底层类型
        • 6. 常见使用错误
      • 泛型接收器(receiver)
        • 1. 泛型切片
        • 2. 泛型结构体
        • 3. 泛型队列
      • 泛型函数
        • 1. 泛型函数
        • 2. 匿名函数
        • 3. 泛型方法
      • 泛型接口
        • 1. 泛型接口定义
        • 2. 从方法集到类型集
        • 3. 类型的交集和并集
        • 4. interface{} 和 any
        • 5. comparable和 ordered
        • 6. 接口的两种类型
      • go泛型的其他限制
    • go条件编译
    • 分布式从ACID、CAP、BASE的理论推进
    • go链接参数 ldflags
    • TCP网络连接以及TIME_WAIT的意义
    • Go异常处理
    • Go性能调优 pprof
    • Go语言设计模式
    • Go 切片的截取
    • Go runtime详解
    • go执行外部命令
    • 标准库container三剑客:head、list、ring
    • go与http代理
    • Go内存管理
    • Go垃圾回收机制
    • Go语言中的并发编程
    • Go协程调度原理及GPM模型
    • Go中逃逸现象, 变量+堆栈
    • Go面向对象的思维理解interface
    • Go中的Defer
    • Go和Python中的深浅拷贝
    • Go语言内存对齐
    • 流和IO多路复用
    • 单点Server的N种并发模型汇总
    • 控制goroutine的数量
    • 配置管理库—Viper
    • 高性能日志库zap
    • Go中的Mutex和RWMutex.md
    • sqlx的使用
    • 分布式id 库snowflake和sonyflake
    • sync.Pool 复用对象
    • sync.Once 单例模式
    • sync.Cond 条件变量
    • unsafe.Pointer 和 uintptr
    • go 信号量
    • go语言代码优化技巧
    • go 接口型函数
    • 位运算
    • cgo教程
    • go调用lib和so动态库
  • go语言实现原理

  • gin框架

  • gorm

  • go测试

  • Go语言
  • go语言进阶
bigox
2023-05-27
目录

go 泛型

在go语言里,对泛型的争议从未停止过,go也在1.18支持了泛型

原文参考:https://www.jb51.net/article/277511.htm

# 泛型初识

  • 在强类型语言中(java,go),因为存在类型的强制约束,导致了数据类型在应用时没有弱类型语言(js、python)灵活

  • 问题假如一个求和函数, 无法计算int类型之外的和.如果想计算浮点或者字符串的和该怎么办?

    func Add(a int, b int) int {
        return a + b
    }
    
    1
    2
    3
  • 泛型引入之前,解决办法之一就是为不同类型定义不同的函数

    func AddFloat32(a float32, b float32) float32 {
        return a + b
    }
     
    func AddString(a string, b string) string {
        return a + b
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 在引入泛型之后

     func[T int | float32 | string](a, b T) T {
            return a + b
    } 
    
    1
    2
    3
    • 在上面这段伪代码中, T 被称为 类型形参(type parameter),它不是具体的类型,在定义函数时类型并不确定。因为 T 的类型并不确定,所以需要像函数的形参那样,在调用函数的时候再传入具体的类型。这样我们不就能一个函数同时支持多个不同的类型了, 在这里被传入的具体类型被称为 类型实参(type argument)
    • 通过引入 类型形参 和 类型实参 这两个概念,让一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 泛型编程
  • 通过Go的 接口+反射 不也能实现这样的动态数据处理吗?是的,泛型能实现的功能通过接口+反射也基本能实现, 但是go的反射机制存在很多问题:用起来麻烦、失去了编译时的类型检查、性能不太理想

# 泛型基础概念

通过引入 类型形参 和 类型实参 这两个概念,让一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 泛型编程

Go在1.18 泛型中引入了很多全新的概念

modb_20230311_b60ea10e-bfc8-11ed-864e-38f9d3cd240d

  • 类型形参 (Type parameter):
    • type Slice[T int|float32|float64 ] []T T 就是类型形参(Type parameter)在定义Slice类型的时候 T 代表的具体类型并不确定,类似一个占位符
  • 类型实参(Type argument)
    • var b Slice[float32] = []float32{1.0, 2.0, 3.0} float32 就是类型实参
  • 类型形参列表( Type parameter list)
    • 中括号里的T int|float32|float64 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为 类型形参列表(type parameter list)
  • 类型约束(Type constraint)
    • 类型约束 指定了类型形参可接受的类型集合,只有属于这个集合中的类型才能替换形参用于实例化
    • int|float32|float64 这部分被称为类型约束(Type constraint),中间的 | 的意思是告诉编译器,类型形参 T 只可以接收 int 或 float32 或 float64 这三种类型的实参
  • 泛型类型(Generic type)
    • type Slice[T int|float32|float64 ] []T这种类型定义的方式中带了类型形参,将这种类型定义中带 类型形参 的类型,称之为 泛型类型(Generic type)
  • 实例化(Instantiations)
    • var a Slice[int] = []int{1, 2, 3} 泛型类型不能直接拿来使用,必须传入类型实参(Type argument) 将其确定为具体的类型之后才可使用。而传入类型实参确定具体类型的操作被称为 实例化(Instantiations) :
  • 泛型接收器(Generic receiver)
  • 泛型函数(Generic function)
  • ...

# 泛型基本使用

# 1. demo

// 定义了一个普通的类型 Slice[int] ,它的底层类型是 []int
type Slice[int] []int     

// MyMap类型定义了两个类型形参 KEY 和 VALUE。分别为两个形参指定了不同的类型约束
// 这个泛型类型的名字叫: MyMap[KEY, VALUE]
type MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE  
 
// 用类型实参 string 和 flaot64 替换了类型形参 KEY 、 VALUE,泛型类型被实例化为具体的类型:MyMap[string, float64]
var a MyMap[string, float64] = map[string]float64{
    "jack_score": 9.6,
    "bob_score":  8.4,
}


// 一个泛型类型的结构体。可用 int 或 sring 类型实例化
type MyStruct[T int | string] struct {  
    Name string
    Data T
}

// 一个泛型接口(关于泛型接口在后半部分会详细讲解)
type IPrintData[T int | float32 | string] interface {
    Print(data T)
}

// 一个泛型通道,可用类型实参 int 或 string 实例化
type MyChan[T int | string] chan T

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 2. 类型形参的互相套用和约束

  • 类型形参是可以互相套用

    type WowStruct[T int | float32, S []T] struct {
        Data     S
        MaxValue T
        MinValue T
    }
    
    // 实例化
    var ws WowStruct[int, []int]   // 泛型类型 WowStuct[T, S] 被实例化后的类型名称就叫 WowStruct[int, []int]
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 上面为T传入了实参 int,然后因为 S 的定义是 []T ,所以 S 的实参自然是 []int ,实例化之后 WowStruct[T,S] 的定义类似如下

    type WowStruct[int, []int] struct {
        Data     []int
        MaxValue int
        MinValue int
    }
    
    1
    2
    3
    4
    5
  • 下面两种定义作用一样

    type WowStruct[T int|string] struct {
        Name string
        Data []T
    }
     
    type WowStruct2[T []int|[]string] struct {
        Name string
        Data T
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 推荐使用方式

    ype WowStruct3[T int | string] struct {
        Data     []T
        MaxValue T
        MinValue T
    }
    
    1
    2
    3
    4
    5

# 3. 泛型的嵌套和约束

  • 泛型和普通的类型一样,可以互相嵌套定义出更加复杂的新类型

    // 先定义个泛型类型 Slice[T]
    type Slice[T int|string|float32|float64] []T
    
    // ✗ 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8
    type UintSlice[T uint|uint8] Slice[T]  
    
    // ✓ 正确。基于泛型类型Slice[T]定义了新的泛型类型 FloatSlice[T] 。FloatSlice[T]只接受float32和float64两种类型
    type FloatSlice[T float32|float64] Slice[T] 
    
    // ✓ 正确。基于泛型类型Slice[T]定义的新泛型类型 IntAndStringSlice[T]
    type IntAndStringSlice[T int|string] Slice[T]  
    // ✓ 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型
    type IntSlice[T int] IntAndStringSlice[T] 
    
    // 在map中套一个泛型类型Slice[T]
    type WowMap[T int|string] map[string]Slice[T]
    // 在map中套Slice[T]的另一种写法
    type WowMap2[T Slice[int] | Slice[string]] map[string]T
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

# 4. 动态判断变量的类型

  • 使用接口的时候经常会用到类型断言或 type swith 来确定接口具体的类型,然后对不同类型做出不同的处理;

    var i interface{} = 123
    i.(int) // 类型断言
     
    // type switch
    switch i.(type) {
        case int:
            // do something
        case string:
            // do something
        default:
            // do something
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • 对于 valut T 这样通过类型形参定义的变量,我们能不能判断具体类型然后对不同类型做出不同处理呢?答案是不允许的

    func (q *Queue[T]) Put(value T) {
        value.(int) // 错误。泛型类型定义的变量不能使用类型断言
     
        // 错误。不允许使用type switch 来判断 value 的具体类型
        switch value.(type) {
        case int:
            // do something
        case string:
            // do something
        default:
            // do something
        }
        
        // ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

# 5. ~ 指定底层类型

var s1 Slice[int] // 正确 
 
type MyInt int

var s2 Slice[MyInt] // ✗ 错误。MyInt类型底层类型是int但并不是int类型,不符合 Slice[T] 的类型约束
1
2
3
4
5
  • 泛型类型 Slice[T]允许的是 int 作为类型实参,而不是 MyInt (虽然 MyInt类型底层类型是int,但它依旧不是 int类型)

  • 为了解决这个问题,Go新增了一个符号 ~ ,在类型约束中使用类似 ~int 这种写法的话,就代表着不光是 int,所有以 int为底层类型的类型也都可用于实例化

  • ~使用限制

    1. ~后面的类型不能为接口

    2. ~后面的类型必须为基本类型

  • demo

    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 
     
    var s Slice[int] // 正确
     
    type MyInt int
    var s2 Slice[MyInt]  // MyInt底层类型是int,所以可以用于实例化
     
    type MyMyInt MyInt
    var s3 Slice[MyMyInt]  // 正确。MyMyInt 虽然基于 MyInt ,但底层类型也是int,所以也能用于实例化
     
    type MyFloat32 float32  // 正确
    var s4 Slice[MyFloat32]
    
    ----------------------------------
    type MyInt int
     
    type _ interface {
        ~[]byte  // 正确
        ~MyInt   // 错误,~后的类型必须为基本类型
        ~error   // 错误,~后的类型不能为接口
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

# 6. 常见使用错误

  • 定义泛型类型的时候,基础类型不能只有类型形参

    // 错误,类型形参不能单独使用
    type CommonType[T int|string|float32] T
    
    1
    2
  • 当类型约束的一些写法会被编译器误认为是表达式时会报错

    //✗ 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针
    type NewType[T *int] []T
    // 上面代码再编译器眼中:它认为你要定义一个存放切片的数组,数组长度由 T 乘以 int 计算得到
    type NewType [T * int][]T 
    
    //✗ 错误。和上面一样,这里不光*被会认为是乘号,| 还会被认为是按位或操作
    type NewType2[T *int|*float64] []T 
    
    //✗ 错误
    type NewType2 [T (int)] []T 
    	
    ///////////// 解决方式 ///////////// 
    
    // 推荐统一用 interface{} 解决问题
    type NewType[T interface{*int}] []T
    type NewType2[T interface{*int|*float64}] []T 
     
    // 如果类型约束中只有一个类型,可以添加个逗号消除歧义
    type NewType3[T *int,] []T
     
    //✗ 错误。如果类型约束不止一个类型,加逗号是不行的
    type NewType4[T *int|*float32,] []T 
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  • 匿名结构体不支持泛型

    // 下面的用法是错误的:
    testCase := struct[T int|string] {
            caseName string
            got      T
            want     T
        }[int]{
            caseName: "test OK",
            got:      100,
            want:     100,
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  • 虽然type switch和类型断言不能用,但我们可通过反射机制达到目的:

    func (receiver Queue[T]) Put(value T) {
        // Printf() 可输出变量value的类型(底层就是通过反射实现的)
        fmt.Printf("%T", value) 
     
        // 通过反射可以动态获得变量value的类型从而分情况处理
        v := reflect.ValueOf(value)
     
        switch v.Kind() {
        case reflect.Int:
            // do something
        case reflect.String:
            // do something
        }
     
        // ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射, 当出现这种情况的时候你可能需要重新思考一下,自己的需求是不是真的需要用泛型(毕竟泛型机制本身就很复杂了,再加上反射的复杂度,增加的复杂度并不一定值得)

# 泛型接收器(receiver)

# 1. 泛型切片

  • 单纯的泛型类型实际上对开发来说用处并不大。但是如果将泛型类型和接下来要介绍的泛型receiver相结合的话,泛型就有了非常大的实用性了

    type MySlice[T int | float32] []T
     
    func (s MySlice[T]) Sum() T {
        var sum T
        for _, value := range s {
            sum += value
        }
        return sum
    }
    
    var s MySlice[int] = []int{1, 2, 3, 4}
    fmt.Println(s.Sum()) // 输出:10
     
    var s2 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.0}
    fmt.Println(s2.Sum()) // 输出:10.0
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

# 2. 泛型结构体

  • 泛型结构体和泛型方法

    package main
    
    import "fmt"
    
    type MyStruct[T int | string | float32] struct {
    	Res T
    }
    
    func (m *MyStruct[T]) Add(a ...T) {
    	var r T
    	for _, i := range a {
    		r += i
    	}
    	fmt.Println(r)
    }
    
    func main() {
    	m1 := MyStruct[int]{}
    	m1.Add(1, 2, 3, 4, 5)
    	m2 := MyStruct[float32]{}
    	m2.Add(1.0, 2.0, 3.1, 4.2, 5.1)
    	m3 := MyStruct[string]{}
    	m3.Add("1.0", "2", "3", "4", "5")
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

# 3. 泛型队列

  • 队列是一种先入先出的数据结构

    // 这里类型约束使用了空接口,代表的意思是所有类型都可以用来实例化泛型类型 Queue[T] (关于接口在后半部分会详细介绍)
    type Queue[T interface{}] struct {
        elements []T
    }
     
    // 将数据放入队列尾部
    func (q *Queue[T]) Put(value T) {
        q.elements = append(q.elements, value)
    }
     
    // 从队列头部取出并从头部删除对应数据
    func (q *Queue[T]) Pop() (T, bool) {
       var value T
        if len(q.elements) == 0 {
            return value, true
        }
     
        value = q.elements[0]
        q.elements = q.elements[1:]
        return value, len(q.elements) == 0
    }
     
    // 队列大小
    func (q Queue[T]) Size() int {
        return len(q.elements)
    }
    -----------------------------------------------------
    
    var q1 Queue[int]  // 可存放int类型数据的队列
    q1.Put(1)
    q1.Put(2)
    q1.Put(3)
    q1.Pop() // 1
    q1.Pop() // 2
    q1.Pop() // 3
     
    var q2 Queue[string]  // 可存放string类型数据的队列
    q2.Put("A")
    q2.Put("B")
    q2.Put("C")
    q2.Pop() // "A"
    q2.Pop() // "B"
    q2.Pop() // "C"
     
    var q3 Queue[struct{Name string}] 
    var q4 Queue[[]int] // 可存放[]int切片的队列
    var q5 Queue[chan int] // 可存放int通道的队列
    var q6 Queue[io.Reader] // 可存放接口的队列
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

# 泛型函数

# 1. 泛型函数

  • 泛型函数定义

    func Add[T int | float32 | float64](a T, b T) T {
        return a + b
    }
    
    1
    2
    3
  • 泛型函数调用

    1. 声明调用

      Add[int](1,2) // 传入类型实参int,计算结果为 3
      Add[float32](1.0, 2.0) // 传入类型实参float32, 计算结果为 3.0
      
      1
      2
    2. Go还支持类型实参的自动推导

      Add(1, 2)  // 1,2是int类型,编译请自动推导出类型实参T是int
      Add(1.0, 2.0) // 1.0, 2.0 是浮点,编译请自动推导出类型实参T是float32
      
      1
      2

# 2. 匿名函数

匿名函数不支持泛型,匿名函数不能自己定义类型形参

  • 匿名函数不能自己定义类型形参

    // 错误,匿名函数不能自己定义类型实参
    fnGeneric := func[T int | float32](a, b T) T {
            return a + b
    } 
    
    1
    2
    3
    4
  • 匿名函数可以使用别处定义好的类型实参

    func MyFunc[T int | float32 | float64](a, b T) {
        // 匿名函数可使用已经定义好的类型形参
        fn2 := func(i T, j T) T {
            return i*2 - j*2
        }
        fn2(a, b)
    }
    
    1
    2
    3
    4
    5
    6
    7

# 3. 泛型方法

  • 目前Go的方法并不支持泛型

    type A struct {
    }
     
    // 错误 不支持泛型方法
    func (receiver A) Add[T int | float32 | float64](a T, b T) T {
        return a + b
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 但是receiver支持泛型, 所以通过receiver使用类型形参在方法中使用泛型的话

    type A[T int | float32 | float64] struct {
    }
     
    // 方法可以使用类型定义中的形参 T 
    func (receiver A[T]) Add(a T, b T) T {
        return a + b
    }
     
    // 用法:
    var a A[int]
    a.Add(1, 2)
     
    var aa A[float32]
    aa.Add(1.0, 2.0)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

# 泛型接口

# 1. 泛型接口定义

  • 有时候使用泛型编程时会书写长长的类型约束

    // 一个可以容纳所有int,uint以及浮点类型的泛型切片
    type Slice[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64] []T
    
    
    1
    2
    3
  • 为了方便维护,go支持如下定义

    type IntUintFloat interface {
        int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
    }
    
    type Slice[T IntUintFloat] []T
    
    
    1
    2
    3
    4
    5
    6
  • 也可以通过接口组合,更加灵活

    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  // 使用 '|' 将多个接口类型组合
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  • 接口嵌套

    type SliceElement interface {
        Int | Uint | Float | string // 组合了三个接口类型并额外增加了一个 string 类型
    }
     
    type Slice[T SliceElement] []T 
    
    
    1
    2
    3
    4
    5
    6

# 2. 从方法集到类型集

  • 在Go1.18之前,Go官方对 接口(interface) 的定义是:接口是一个方法集(method set) An interface type specifies a method set called its interface.

  • ReadWriter 接口定义了一个接口(方法集),这个集合中包含了 Read() 和 Write() 这两个方法。所有同时定义了这两种方法的类型被视为实现了这一接口。

    type ReadWriter interface {
        Read(p []byte) (n int, err error)
        Write(p []byte) (n int, err error)
    }
    
    1
    2
    3
    4
  • 如果换一个角度来重新思考上面这个接口的话,会发现接口的定义实际上还能这样理解:可以把 ReaderWriter 接口看成代表了一个 类型的集合,所有实现了 Read() Writer() 这两个方法的类型都在接口代表的类型集合当中。

  • 通过换个角度看待接口,在我们眼中接口的定义就从 方法集(method set) 变为了 类型集(type set)。而Go1.18开始就是依据这一点将接口的定义正式更改为了 类型集(Type set)。

  • 接口类型 Float 代表了一个 类型集合, 所有以 float32 或 float64 为底层类型的类型,都在这一类型集之中; 而 type Slice[T Float] []T 中, 类型约束 的真正意思是:类型约束 指定了类型形参可接受的类型集合,只有属于这个集合中的类型才能替换形参用于实例化

    var s Slice[int]      // int 属于类型集 Float ,所以int可以作为类型实参
    var s Slice[chan int] // chan int 类型不在类型集 Float 中,所以错误
    
    
    1
    2
    3
  • 接口实现(implement)定义的变化:既然接口定义发生了变化,那么从Go1.18开始 接口实现(implement) 的定义自然也发生了变化:当满足以下条件时,可以说 类型 T 实现了接口 I ( type T implements interface I):

    1. T 不是接口时:类型 T 是接口 I 代表的类型集中的一个成员 (T is an element of the type set of I)。
    2. T 是接口时: T 接口代表的类型集是 I 代表的类型集的子集(Type set of T is a subset of the type set of I)。

# 3. 类型的交集和并集

  • 并集|

    type Uint interface {  // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
    }
    
    1
    2
    3
  • 交集

    type AllInt interface {
        ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
    }
    
    type Uint interface {
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
    }
    
    type A interface { // 接口A代表的类型集是 AllInt 和 Uint 的交集 ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
        AllInt
        Uint
    }
    
    type B interface { // 接口B代表的类型集是 AllInt 和 ~int 的交集  ~int
        AllInt
        ~int
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  • 空集

    type Bad interface {
        int
        float32 
    } // 类型 int 和 float32 没有相交的类型,所以接口 Bad 代表的类型集为空
    
    
    1
    2
    3
    4
    5

    没有任何一种类型属于空集。虽然这样的写法是可以编译的,但实际上并没有什么意义。

# 4. interface{} 和 any

  • Go1.18开始接口的定义发生了改变,所以 interface{} 的定义也发生了一些变更: 空接口代表了所有类型的集合

  • 对于Go1.18之后的空接口应该这样理解:

    1. 虽然空接口内没有写入任何的类型,但它代表的是所有类型的集合,而非一个空集。
    2. 类型约束中指定 空接口 的意思是指定了一个包含所有类型的类型集,并不是类型约束限定了只能使用 空接口 来做类型形参。
    // 空接口代表所有类型的集合。写入类型约束意味着所有类型都可拿来做类型实参
    type Slice[T interface{}] []T
    
    var s1 Slice[int]    // 正确
    var s2 Slice[map[string]string]  // 正确
    var s3 Slice[chan int]  // 正确
    var s4 Slice[interface{}]  // 正确
    
    1
    2
    3
    4
    5
    6
    7
  • 在Go1.18+中,any等价于 interface{} , 这个是官方提供为了方便使用而设定的新的关键字

    type Slice[T any] []T // 代码等价于 type Slice[T interface{}] []T
    
    1
  • any 的定义就位于Go语言的 builtin.go 文件中(参考如下), any 实际上就是 interaface{} 的别名(alias),两者完全等价。

    // any is an alias for interface{} and is equivalent to interface{} in all ways.
    type any = interface{} 
    
    1
    2
  • 所以从 Go 1.18 开始,所有可以用到空接口的地方其实都可以直接替换为any:

    var s []any // 等价于 var s []interface{}
    var m map[string]any // 等价于 var m map[string]interface{}
    
    func MyPrint(value any){
        fmt.Println(value)
    }
    
    1
    2
    3
    4
    5
    6
  • 如果你高兴的话,项目迁移到 Go1.18 之后可以使用下面这行命令直接把整个项目中的空接口全都替换成 any。当然因为并不强制,所以到底是用 interface{} 还是 any 全看自己喜好。

    gofmt -w -r 'interface{} -> any' ./...
    
    1
  • Go语言项目中就曾经有人提出过把Go语言中所有 interface{ }替换成 any 的 issue,然后因为影响范围过大过而且影响因素不确定,理所当然被驳回了。

# 5. comparable和 ordered

comparable(可比较) 和 可排序(ordered)

  • 对于一些数据类型,我们需要在类型约束中限制只接受能 != 和 == 对比的类型,以Go直接内置了一个叫 comparable 的接口,它代表了所有可用 != 以及 == 对比的类型:

    // 错误。因为 map 中键的类型必须是可进行 != 和 == 比较的类型
    type MyMap[KEY any, VALUE any] map[KEY]VALUE 
    
    // 正确
    type MyMap[KEY comparable, VALUE any] map[KEY]VALUE 
    
    1
    2
    3
    4
    5
  • comparable 比较容易引起误解的一点是很多人容易把他与可排序搞混淆。可比较指的是 可以执行 != == 操作的类型,并没确保这个类型可以执行大小比较( >,<,<=,>=)。、

    type OhMyStruct struct {
        a int
    }
    
    var a, b OhMyStruct
    
    a == b // 正确。结构体可使用 == 进行比较
    a != b // 正确
    
    a > b // 错误。结构体不可比大小
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 而可进行大小比较的类型被称为 Orderd 。目前Go语言并没有像 comparable 这样直接内置对应的关键词,所以想要的话需要自己来定义相关接口,比如我们可以参考Go官方包 golang.org/x/exp/constraints 如何定义:

    // Ordered 代表所有可比大小排序的类型
    type Ordered interface {
        Integer | Float | ~string
    }
    
    type Integer interface {
        Signed | Unsigned
    }
    
    type Signed interface {
        ~int | ~int8 | ~int16 | ~int32 | ~int64
    }
    
    type Unsigned interface {
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
    }
    
    type Float interface {
        ~float32 | ~float64
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
  • 虽然可以直接使用官方包 golang.org/x/exp/constraints ,但因为这个包属于实验性质的 x 包,今后可能会发生非常大变动,所以并不推荐直接使用

# 6. 接口的两种类型

  • Go1.18开始将接口分为了两种类型

    1. 基本接口(Basic interface)
    2. 通用接口(General interface)
  • 基本接口(Basic interface):接口定义中如果只有方法的话,那么这种接口被称为基本接口(Basic interface)。这种接口就是Go1.18之前的接口,用法也基本和Go1.18之前保持一致。

    type MyError interface { // 接口中只有方法,所以是基本接口
        Error() string
    }
    
    // 用法和 Go1.18之前保持一致
    var err MyError = fmt.Errorf("hello world")
    
    // io.Reader 和 io.Writer 都是基本接口,也可以用在类型约束中
    type MySlice[T io.Reader | io.Writer]  []Slice
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 通用接口(General interface):果接口内不光只有方法,还有类型的话,这种接口被称为 通用接口(General interface) ;通用接口,只能用于类型约束,不得用于变量定义, 这一限制保证了一般接口的使用被限定在了泛型之中,不会影响到Go1.18之前的代码,同时也极大减少了书写代码时的心智负担

    type Uint interface { // 接口 Uint 中有类型,所以是通用接口
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
    }
    
    type ReadWriter interface {  // ReadWriter 接口既有方法也有类型,所以是通用接口
        ~string | ~[]rune
    
        Read(p []byte) (n int, err error)
        Write(p []byte) (n int, err error)
    }
    
    // 类型 StringReadWriter 实现了接口 Readwriter
    type StringReadWriter string 
     
    func (s StringReadWriter) Read(p []byte) (n int, err error) {
        // ...
    }
     
    func (s StringReadWriter) Write(p []byte) (n int, err error) {
     // ...
    }
     
    // 类型BytesReadWriter 没有实现接口 Readwriter  
    // StringReadWriter 存在于接口 ReadWriter 代表的类型集中,而 BytesReadWriter 因为底层类型是 []byte(既不是string也是不[]rune) ,所以它不属于 ReadWriter 代表的类型集
    type BytesReadWriter []byte 
     
    func (s BytesReadWriter) Read(p []byte) (n int, err error) {
     ...
    }
     
    func (s BytesReadWriter) Write(p []byte) (n int, err error) {
     ...
    }
    
    --------------------------------
    type Uint interface {
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
    }
    
    var uintInf Uint // 错误。Uint是通用接口,只能用于类型约束,不得用于变量定义  
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

# go泛型的其他限制

  1. 用 | 连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集); 但是相交的类型中是接口的话,则不受这一限制
  2. 类型的并集中不能有类型形参
  3. 接口不能直接或间接地并入自己
  4. 接口的并集成员个数大于一的时候不能直接或间接并入 comparable 接口
  5. 带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中
  1. 用 | 连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集); 但是相交的类型中是接口的话,则不受这一限制:

    type MyInt int
    
    // 错误,MyInt的底层类型是int,和 ~int 有相交的部分
    type _ interface {
        ~int | MyInt
    }
    
    type MyInt int
    
    type _ interface {
        ~int | interface{ MyInt }  // 正确
    }
    
    type _ interface {
        interface{ ~int } | MyInt // 也正确
    }
    
    type _ interface {
        interface{ ~int } | interface{ MyInt }  // 也正确
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
  2. 类型的并集中不能有类型形参

    type MyInf[T ~int | ~string] interface {
        ~float32 | T  // 错误。T是类型形参
    }
    
    type MyInf2[T ~int | ~string] interface {
        T  // 错误
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
  3. 接口不能直接或间接地并入自己

    type Bad interface {
        Bad // 错误,接口不能直接并入自己
    }
    
    type Bad2 interface {
        Bad1
    }
    type Bad1 interface {
        Bad2 // 错误,接口Bad1通过Bad2间接并入了自己
    }
    
    type Bad3 interface {
        ~int | ~string | Bad3 // 错误,通过类型的并集并入了自己
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  4. 接口的并集成员个数大于一的时候不能直接或间接并入 comparable 接口

    type OK interface {
        comparable // 正确。只有一个类型的时候可以使用 comparable
    }
    
    type Bad1 interface {
        []int | comparable // 错误,类型并集不能直接并入 comparable 接口
    }
    
    type CmpInf interface {
        comparable
    }
    type Bad2 interface {
        chan int | CmpInf  // 错误,类型并集通过 CmpInf 间接并入了comparable
    }
    type Bad3 interface {
        chan int | interface{comparable}  // 理所当然,这样也是不行的
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  5. 带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中

    type _ interface {
        ~int | ~string | error // 错误,error是带方法的接口(一般接口) 不能写入并集中
    }
    
    type DataProcessor[T any] interface {
        ~string | ~[]byte
    
        Process(data T) (newData T)
        Save(data T) error
    }
    
    // 错误,实例化之后的 DataProcessor[string] 是带方法的一般接口,不能写入类型并集
    type _ interface {
        ~int | ~string | DataProcessor[string] 
    }
    
    type Bad[T any] interface {
        ~int | ~string | DataProcessor[T]  // 也不行
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
上次更新: 2024/06/12, 22:12:34
channel 原理和坑
go条件编译

← channel 原理和坑 go条件编译→

最近更新
01
go与http代理
05-24
02
自制申威架构k8s-reloader
12-06
03
Docker Buildx 教程
12-01
更多文章>
Theme by Vdoing | Copyright © 2020-2024 小刘扎扎 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式