Golang 的泛型

泛型

go的泛型是go 1.18 之后的产物。
泛型主要是为了解决不同类型的代码重复的问题

引用一张网络梗图:

网络梗图

标志

[T any]

相关概念

简单了解下即可,未接触过golang的any的话,直接阅读起来比较干巴且抽象,可以带着这些概念阅读下文中代码。

类型约束

虽然使用了泛型,但是并不能对一个变量随意赋值,比如一个变量先赋值为string,再赋值为int,这一般不会被允许(any、interface是例外)
比如:
我定义了一个泛型函数Echo(),希望可以接受 int , string 类型的参数,
但是不能接受其他类型,比如float64,因为float64不是以上类型中的子类型。

func Echo[T int | string](arg T) T {
    return arg
}
func TestEcho2(t *testing.T) {
    /*
        此时约束为 string ,得到的 domain 也为 string 类型
        所以此处不能使用 int 类型为 domain 赋值
     */
    domain := Echo[string]("fanfine.cn")
    fmt.Println(domain, reflect.TypeOf(domain)) // fanfine.cn string
    // domain = 1                               // 不可以

    /* 
        此时约束为 int ,得到的 num 为 int 类型
        所以此处不能使用 string 类型为 num 赋值
     */
    num := Echo[int](1)
    fmt.Println(num, reflect.TypeOf(num)) // 1 int
    // num = "fanfine.cn"                 // 不可以

    /*
        如果使用了一个函数未被允许的类型作为约束?
        报错:
        float64 does not satisfy int | string (float64 missing in int | string)
        float64不满足int|string(int|string中缺少float64)
    */
    floatData := Echo[float64](1.2)
    fmt.Println(floatData)
}

当 约束设置为 [T any][T interface{}] 时,float64 以及其他类型都可以作为参数传入。
因为 any 和 interface{} 都是所有类型的超集,所以它们都可以作为约束,


func Echo[T any](arg T) T {
    return arg
}
func TestEcho(t *testing.T) {
    // but 如果使用 any / interface{} 作为约束呢?
    everything := Echo[any](1)
    fmt.Println(everything, reflect.TypeOf(everything)) // 1 int
    fmt.Printf("everything 指向: %p \n", &everything)     // 0x1400002a310

    // 因为约束为 any 所以可以使用其他类型赋值,如:
    everything = "fanfine.cn"
    fmt.Println(everything, reflect.TypeOf(everything)) // fanfine.cn string
    fmt.Printf("everything 指向: %p \n", &everything)     // 0x1400002a310

    // interface{} 同理
    everything2 := Echo[interface{}]("Fan的小破站")
    fmt.Println(everything2) // Fan的小破站
    everything2 = 1          // 赋值由 string 的转为 int 的 1
    fmt.Println(everything2) // 1
}

内置约束

  • any
    • any 表示可以是任何类型。
    • 当你使用 any 作为类型参数时,编译器不会强制类型检查。
    • 在运行时,变量的实际类型取决于最后一次赋值的类型。
  • comparable
    • comparable 表示可以比较的类型。
    • 在map中可作为key的类型。

// 内置约束 comparable
func EqualComparable[T comparable](a, b T) bool {
    return a == b
}
func TestEqualComparable(t *testing.T) {
    fmt.Println(EqualComparable(1, 2))        // false
    fmt.Println(EqualComparable("1", "1"))    // true
    fmt.Println(EqualComparable(1.1, 2.2))    // false
    fmt.Println(EqualComparable(true, false)) // false
}

自定义约束

语法如下:
type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}
func EqualSigned[T Signed](a, b T) bool {
    return a == b
}

公开库的约束

"golang.org/x/exp/constraints"
使用详情见下文。

类型推断

当使用泛型时,如果没有显式指定类型,编译器会自动推断类型。例如:

// 推断
func Echo[T any](arg T) T {
    return arg
}
func TestEcho(t *testing.T) {
    fmt.Println("typeof:", reflect.TypeOf(Echo[int](1))) // typeof: int
    fmt.Println("typeof:", reflect.TypeOf(Echo(1)))      // typeof: int
}

在第二个 TestEcho() 调用时,虽然没有显式标示约束的类型,但是编译器时可以推断出其类型为 int。

小建议:
建议使用显式约束,这样泛型代码的阅读性会更好。
能一眼看出来调用了泛型函数,且明确约束的类型是什么。

解决梗图

在需要约束的数量很少的时候,可以这样写:


// 此处约束的 Ordered 也可写作 `int | float32 | uint | 更多类型`
func MaxNum[T int | float64 | uint](a, b T) T {
    if a > b {
        return a
    }
    return b
}
func TestMaxNum(t *testing.T) {
    fmt.Println(MaxNum(int(1), int(2)))   // 2
    fmt.Println(MaxNum(uint(1), uint(2))) // 2
}

如此,便使用了泛型解决了梗图中代码大量重复的问题.

当需要约束的类型比较多的时候,可以这样写:


// 解决比大小的网络梗图,得出小的值
type Ordered interface {
    int | float32 | float64 | uint // 省略其他类型
}

// 得出最小值
func MinNum[T Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func TestMinNum(t *testing.T) {
    fmt.Println(MinNum(int(1), int(2)))   // 1
    fmt.Println(MinNum(uint(1), uint(2))) // 1
}

同时,也有公开库 "golang.org/x/exp/constraints" 提供了 Ordered 类型,可以这样写:


import "golang.org/x/exp/constraints"

func EqualOrdered[T constraints.Ordered](a, b T) bool {
    return a == b
}
func TestEqualOrdered(t *testing.T) {
    fmt.Println(EqualOrdered(int(1), int(1)))         // true
    fmt.Println(EqualOrdered(uint(1), uint(2)))       // false
    fmt.Println(EqualOrdered("domain", "fanfine.cn")) // false
}

constraints还提供了其他约束:

type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
    Signed | Unsigned
}
type Float interface {
    ~float32 | ~float64
}
type Complex interface {
    ~complex64 | ~complex128
}
type Ordered interface {
    Integer | Float | ~string
}

类型篇

泛型的基础使用

// 泛型的基础使用
func TestT1(t *testing.T) {
    var t1 any
    fmt.Println(t1) // nil 零值
    t1 = 1
    fmt.Println(t1) // 1

    t1 = "fanfine.cn"
    fmt.Println(t1) // fanfine.cn

    t1 = map[string]any{
        "domain": "fanfine.cn",
    }
    fmt.Println(t1) // map[domain:fanfine.cn]

    t1 = func(a string) string { return a }
    fmt.Println(t1)

}

泛型结构体


// 结构体泛型
type User[T any] struct {
    Data T
    Age  int
}

func NewUser[T any](data T, age int) *User[T] {
    return &User[T]{
        Data: data,
        Age:  age,
    }
}

func TestAnyStruct(t *testing.T) {
    u1 := NewUser[string]("fanfine.cn", 18)
    fmt.Println(u1) // &{fanfine.cn 18}
    u2 := NewUser[bool](true, 18)
    fmt.Println(u2) // &{true 18}
}

泛型切片

// 泛型 Slice
type AnySlice[T any] []T

func TestAnySlice(t *testing.T) {
    // make
    list := make(AnySlice[int], 0)
    list = append(list, 1)
    fmt.Println(list, reflect.TypeOf(list)) // [1]  包名.AnySlice[int]

    // :=
    list2 := AnySlice[string]{"1", "2"}
    fmt.Println(list2, reflect.TypeOf(list2)) // [1 2]  包名.AnySlice[string]
}

泛型 Map

此处涉及多个约束的使用

// 泛型 Map
type AnyMap[k comparable, v any] map[k]v

// map
func TestAnyMap(t *testing.T) {
    // make
    m := make(AnyMap[string, int])
    m["age"] = 1
    fmt.Println(m, reflect.TypeOf(m)) // map[age:1]  包名.AnyMap[string,int]

    // :=
    m2 := AnyMap[string, string]{
        "domain": "fanfine.cn",
    }
    fmt.Println(m2, reflect.TypeOf(m2)) // map[domain:fanfine.cn]  包名.AnyMap[string,string]
}

泛型 chan

// 泛型 chan
type AnyChan[T any] chan T

func TestAnyChan(t *testing.T) {
    // 约束为 int
    ch := make(AnyChan[int], 1)
    defer close(ch)
    ch <- 1
    fmt.Println(<-ch)               // 1
    fmt.Println(reflect.TypeOf(ch)) // 包名.AnyChan[int]

    // 其他约束,如:string
    ch2 := make(AnyChan[string], 1)
    defer close(ch2)
    ch2 <- "fanfine.cn"
    fmt.Println(<-ch2)               // fanfine.cn
    fmt.Println(reflect.TypeOf(ch2)) // 包名.AnyChan[string]

    /*
        指定约束后,不可写入其他类型,如:
        ch <- "wrong" // cannot use "wrong" (untyped string constant) as int value in sendcompiler IncompatibleAssign
    */
    // any
    ch3 := make(AnyChan[any], 1)
    go func() {
        ch3 <- 1
        ch3 <- "fanfine.cn"
        ch3 <- 3.14
        ch3 <- true
        type Person struct {
            Name string
        }
        ch3 <- &Person{Name: "fanfine"}
        close(ch3)
    }()

    for v := range ch3 {
        fmt.Println("type:", reflect.TypeOf(v), "value:", v)
    }
    /*
        type: int value: 1
        type: string value: fanfine.cn
        type: float64 value: 3.14
        type: bool value: true
        type: *包名.Person value: &{fanfine}
    */
}

泛型接口

// 泛型 接口
type AnyInterface[T any] interface {
    Job1(data T) T
}
type AnyStruct[T any] struct {
    Data T
}

func NewAnyStruct[T any](data T) *AnyStruct[T] {
    return &AnyStruct[T]{
        Data: data,
    }
}
func (s *AnyStruct[T]) Job1(data T) T {
    return data
}
func TestAnyInterface(t *testing.T) {
    // 约束为int
    var anyInterface AnyInterface[int] = NewAnyStruct[int](111)
    fmt.Println(anyInterface.Job1(111)) // 111
    // 约束为string
    var anyInterface2 AnyInterface[string] = NewAnyStruct[string]("fan")
    fmt.Println(anyInterface2.Job1("fanfine.cn")) // fanfine.cn
    // 约束为any
    var anyInterface3 AnyInterface[any] = NewAnyStruct[any]("fan")
    fmt.Println(anyInterface3.Job1("fanfine.cn")) // fanfine.cn
    fmt.Println(anyInterface3.Job1(123))          // 123

}

参数篇

泛型作为函数参数

// 泛型作为函数的参数
func echo[T any](a T) T {
    return a
}

func TestEcho(t *testing.T) {
    // 约束参数类型为 int
    num := echo[int](12)
    /*
        已经约束为 int 后,num 就是 int 类型,后续变量 num 只能传入 int 类型 , 否则会报错,例如:
        num = "wrong" // cannot use "wrong" (untyped string constant) as int value in assignment
    */
    num2 := echo(12)

    /*
        当然,如果约束为 any 、 interface{}
        则 a 的类型就是 any、interface{},所以可以传入任意类型
    */ 
    a := echo[any]("www.fanfine.cn")
    a = 123
    a = map[string]int{
        "a": 1,
    }

    // 传入其他类型
    str := echo("www.fanfine.cn")
    bo := echo(true)
    ma := echo(map[string]string{
        "domain": "www.fanfine.cn",
    })

    fmt.Println("num 的类型", reflect.TypeOf(num))   // int
    fmt.Println("num2 的类型", reflect.TypeOf(num2)) // int
    fmt.Println("a 的类型", reflect.TypeOf(a))       // any
    fmt.Println("str 的类型", reflect.TypeOf(str))   // string
    fmt.Println("bo 的类型", reflect.TypeOf(bo))     // bool
    fmt.Println("ma 的类型", reflect.TypeOf(ma))     // map[string]int
}

泛型作为方法参数

// 泛型作为方法参数
type Animal[T any] struct {
    data T
}

// New 一个Animal,泛型作为函数参数
func NewAnimal[T any](data T) *Animal[T] {
    return &Animal[T]{
        data: data,
    }
}

// 泛型作为方法参数
func (a *Animal[T]) GetData() T {
    return a.data
}
func (a *Animal[T]) SetData(data T) {
    a.data = data
}
func (a *Animal[T]) Job1(data T) T {
    return data
}

// 测试方法的使用
func TestMethod(t *testing.T) {
    a := NewAnimal[any]("fanfine.cn")
    fmt.Println(a.GetData(), reflect.TypeOf(a.GetData())) // fanfine.cn string

    a.SetData(1)
    fmt.Println(a.GetData(), reflect.TypeOf(a.GetData())) // 1 int

    // 也可约束为其他类型,比如 string
    a2 := NewAnimal[string]("fanfine.cn")
    fmt.Println(a2.GetData(), reflect.TypeOf(a2.GetData())) // fanfine.cn string

    /*
        此时约束为 string ,所以不能使用其他类型赋值
        a2.SetData(1) //  cannot use 1 (untyped int constant) as string value in argument to a2.SetData
    */

    // a2 的初始化也可写作:编译器会根据参数的类型,推断出约束为 string
    a2 = NewAnimal("fanfine.cn")
    fmt.Println(reflect.TypeOf(a2.GetData())) // string

    a3 := NewAnimal[int](1)                   // 此时约束为 int
    fmt.Println(reflect.TypeOf(a3.GetData())) // int

}

本文由 上传。


如果您喜欢这篇文章,请点击链接 Golang 的泛型 查看原文。


您也可以直接访问:https://www.fanfine.cn/blog/126

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇