golang泛型使用

现在要计算a b两个值的加和,会有整型 浮点型两种可能

package main

import "fmt"

func main() {
    fmt.Println(SumByInt(1, 3)) // 输出: 4

    fmt.Println(SumByFloat(1.6, 3.2)) // 输出: 4.8
}

func SumByInt(a, b int) int {
    return a + b
}

func SumByFloat(a, b float32) float32 {
    return a + b
}

为了解决可能出现的整型 浮点型两种可能,声明了SumByInt方法与SumByFloat方法,这种方式代码利用率太低,那换种写法

func main() {
    fmt.Println(SumByInt(1.6, 3.2))
}

func SumByInt(a, b int) int {
    return a + b
}

弱类型语言将自动转化并正常计算输出4.8,强类型语言对变量、数据类型的声明及其严格,编译都无法通过

# command-line-arguments
.\fanxing.go:6:23: cannot use 1.6 (untyped float constant) as int value in argument to SumByInt (truncated)
.\fanxing.go:6:28: cannot use 3.2 (untyped float constant) as int value in argument to SumByInt (truncated)

如果不定义两个方法,达到a+b的实现,也是可以的

func main() {
    fmt.Println(Sum(1, 3))
    fmt.Println(Sum(1.6, 3.2))
}

func Sum(a, b interface{}) interface{} {
    switch a.(type) {
    case int:
        a1 := a.(int)
        b1 := b.(int)
        return a1 + b1
    case float64:
        a1 := a.(float64)
        b1 := b.(float64)
        return a1 + b1
    default:
        return nil
    }
}

根据所有的数据类型都继承了空接口,利用断言类型转换写逻辑来实现

泛型的概念

这一句话,足够清晰表达了,需要注意的是 golang 1.18版本后才支持

定义一类通用的模板变量,从而可以传入不同类型的变量,使得逻辑更加通用,代码更加精简

还是计算a+b的值,代码用泛型重新写一次

我们把T当作int float64这类类型的模板名字,行参a b类型为模板T,输出也是T类型

func main() {
    fmt.Println(Sum(1, 3))
    fmt.Println(Sum(1.6, 3.2))
}

func Sum[T int | float64](a, b T) T {
    return a + b
}

这样子,就正常输出了

各种类型的泛型使用

切片变量

定义一个泛型切片Slice1,切片里的值类型,即可以是int,也可以是float64,也可以是string

type Slice1[T int | float64 | string] []T

语句说明;

  1. Slice1 切片变量名
  2. T表示我们提炼出来的通用类型参数(Type parameter),是我们就用来表示不同类型的模板,T只是取的一个通用的名字,你可以取名任意其他名字都行。
  3. 后面的 [int|float64|string] 叫类型约束(Type constraint)或类型参数列表(type parameter list),也就是约束了T的取值范围,只能从(int、float64、string)中取值。中间的|表示的是的关系,等于语法 ||,可以根据你类型的使用场景定义更多的类型约束。
  4. 最后面的[]T这个我们就很熟悉了,就是申请一个切片类型,比如常见的:[]int, []string 等等,只不过我们这里的类型是T,也就是参数列表里面定义的变量值。

这整个类型,就叫做 Slice1[T],它是一个切片泛型变量

这种写法,等同于

type SliceInt []int
type SliceFloat []float64
type SliceInt []string
map变量

定义一个 Map1[KEY, VALUE]

要求其中参数KEY的类型约束是int、string,参数VALUE的类型约束为string、float64

type Map1[KEY int | string, VALUE string | float64] map[KEY]VALUE

等同于2*2叉乘4种组合

type Map2 map[int]string
type Map3 map[int]float64
type Map4 map[string]string
type Map5 map[string]float64
结构体变量

创建名为Struct1结构体的泛型变量。其中的泛型参数T,有3个类型约束

type Struct1 [T string|int|float64] struct {
  Title string
  Content  T
}

等同于

type Struct3 struct {
  Title string
  Content  string
}
 
type Struct4 struct {
  Title string
  Content  int
}
 
type Struct5 struct {
  Title string
  Content  float64
}

变量实例化、使用

泛型切片
    //申明一个泛型切片
    type Slice1[T int | float64 | string] []T
    //实例化成int型的切片,并赋值,T的类型和后面具体值的类型保持一致。
    var MySlice1 Slice1[int] = []int{1, 2, 3}

    //简写方式
    MySlice2 := Slice1[int]{1, 2, 3}
    //实例化成string型的切片,并赋值, T的类型和后面具体值的类型保持一致。
    var MySlice3 Slice1[string] = []string{"hello", "small", "yang"}

    //简写方式
    MySlice4 := Slice1[string]{"hello", "small", "yang"}
泛型map
    //申明
    type Map1[KEY int | string, VALUE string | float64] map[KEY]VALUE
    //实例化:KEY和VALUE要替换成具体的类型。map里面的也要保持一致
    var MyMap1 Map1[int, string] = map[int]string{
        1: "hello",
        2: "small",
    }

    //简写方式
    MyMap2 := Map1[int, string]{
        1: "hello",
        2: "small",
    }

    fmt.Println(MyMap1, MyMap2) // map[1:hello 2:small]
    //实例化:KEY和VALUE要替换成具体的类型。map里面的也要保持一致
    var MyMap3 Map1[string, string] = map[string]string{
        "one": "hello",
        "two": "small",
    }

    //简写方式
    MyMap4 := Map1[string, string]{
        "one": "hello",
        "two": "small",
    }
    fmt.Println(MyMap3, MyMap4) // map[one:hello two:small]
泛型结构体

定义1个结构体泛型变量

type Struct1 [T string|int|float64] struct {
  Title string
  Content  T
}
//实例化成float64
var MyStruct1 Struct1[float64]
 
//赋值
MyStruct1.Title = "hello"
MyStruct1.Content = 3.149
 
//简写方式
var MyStruct2 = Struct1[string]{
  Title:   "hello",
  Content: "small",
}

继承类型支持

type List1[T ~int | ~string] struct { // type myint int 也将支持
    arr []T
}
type List2[T int | string] struct {
    arr []T
}

func main() {
    type myint int
    intList1 := List1[myint]{arr: []myint{1, 2, 3}}
    fmt.Println(intList1.arr)

    intList2 := List2[myint]{arr: []myint{1, 2, 3}} // 此处会报错,因为没有泛型符号 ~ 声明
    fmt.Println(intList2.arr)
}
泛型变量嵌套

定义一个泛型结构体,泛型变量S P,P是嵌套了S的变量

    type MyStruct[S int | string, P map[S]string] struct {
        Name    string
        Content S
        Job     P
    }
    //实例化int的实参
    var MyStruct1 = MyStruct[int, map[int]string]{
        Name:    "small",
        Content: 1,
        Job:     map[int]string{1: "ss"},
    }

    fmt.Printf("%+v", MyStruct1) // {Name:small Content:1 Job:map[1:ss]}
    //实例化string的实参
    var MyStruct2 = MyStruct[string, map[string]string]{
        Name:    "small",
        Content: "yang",
        Job:     map[string]string{"aa": "ss"},
    }

    fmt.Printf("%+v", MyStruct2) //{Name:small Content:yang Job:map[aa:ss]}

再为复杂的例子,2个泛型变量之间的嵌套使用

//切片泛型
type Slice1[T int | string] []T

//结构体泛型,它的第二个泛型参数的类型是第一个切片泛型。
type Struct1[P int | string, V Slice1[P]] struct {
  Name  P
  Title V
}

实例化

//实例化切片
mySlice1 := Slice1[int]{1, 2, 3}
 
//用int去替换P, 用Slice1去替换Slice1[p]
myStruct1 := Struct1[int, Slice1[int]]{
  Name:  123,
  Title: []int{1, 2, 3},
}
//用int去替换P, 用Slice1去替换Slice1[p]
myStruct2 := Struct1[string, Slice1[string]]{
  Name:  "hello",
  Title: []string{"hello", "small", "yang"},
}
泛型函数

计算2个数之和,就是文章最开始的例子,但是我们换种调用方式

func main() {
    number1 := Sum[int](1, 3)
    number2 := Sum[float32](1.1, 3.2)
    number3 := Sum(1, 2) // 这种方式更加自然一些

    fmt.Println(number1, number2, number3)
}

func Sum[T int | float32](a, b T) T {
    return a + b
}

注意:编译器会会根据传入的类型进行简单的类型推断,例如int类型,调用泛型函数Sum时,无需指定类型 Sum(1, 2) Sum[int](1, 2)

自定义类型约束

声明一个方法Foreach

func Foreach[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](list []T) {
    for _, t := range list {
        fmt.Println(t)
    }
}

这个方法类型约束又臭又长,换一个清爽写法

type MyNumber interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

func Foreach[T MyNumber](list []T) {
    for _, t := range list {
        fmt.Println(t)
    }
}

有些难区分MyNumber是接口还是自定义约束类型,再换种更清晰写法

type myInt interface {
    int | int8 | int16 | int32 | int64
}
 
type myUint interface {
    uint | uint8 | uint16 | uint32
}
 
type myFloat interface {
    float32 | float64
}
 
func Foreach[T myInt| myUint | myFloat](list []T) {
  for _, t := range list {
    fmt.Println(t)
  }
}

再换一种

type myInt interface {
    int | int8 | int16 | int32 | int64
}
 
type myUint interface {
    uint | uint8 | uint16 | uint32
}
 
type myFloat interface {
    float32 | float64
}
 
type myNumber interface {
  myInt | myUint | myFloat
}
 
func Foreach[T myNumber](list []T) {
  for _, t := range list {
    fmt.Println(t)
  }
}

也可以单独和某种具体类型使用

type myNumber interface {
  myInt | myUint | myFloat | string
}

以上的|表示,是合集意思,那么并集呢,例如myType_1(int32,string),myType_2(int64,string) 取二者的交集string作为类型约束,也很简单

type myInt interface {
    int | int8 | int16 | int32 | int64
}

type myInt2 interface {
    int | int64
}
 
type myFloat interface {
    float32 | float64
}
 
//每一个自定义约束类型单独一行
type myNumber interface {
  myInt
  myInt2
}

当交集约束为空时,调用函数就会报错

函数Foreach的形参list约束为空,传入string

Foreach[string]([]string{"hello", "small"})
any约束类型

系统提供的,全局可用的,any是interface{}的别名,可以是任何的变量类型

//相等
type MySmall interface{}
type MySmall any
 
//相等
scans := make([]interface{}, 6)
scans := make([]any, 6)

但用作两个值相加时,会报错

func Sum[T any] (a, b T) T {
  return a+b
}
// invalid operation: operator + not defined on a (variable of type T constrained by any)
泛型接口

定义一个泛型接口

type MyInterface[T int | string] interface {
  WriteOne(data T) T
  ReadOne() T
}

最后,来总结下接口(interface)与泛型的相似点
官方定义1.18版本后,对接口的定义为 接口类型定义了一个类型集合。接口类型的变量可以存储这个接口类型集合的任意一种类型的实例值。这种类型被称之为实现了这个接口。接口类型的变量如果未初始化则它的值为nil
对于接口也分为了两类

  1. 基本接: 如果,1个接口里面只有方法,也就是老的语法写法
  2. 一般接口: 如果,1个接口里面,有约束类型的,有或者没有方法的


作者:大口吃饭大口吐
链接:https://www.jianshu.com/p/0b711ec80d4b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

### 回答1: 在Golang中,泛型接口是一种接口类型,可以用于处理不同类型的数据。泛型接口在编程中非常有用,因为它允许我们编写可重用、灵活的代码,而无需针对特定类型进行硬编码。 在Golang中,泛型接口可以通过使用空接口(`interface{}`)来实现。空接口是一个没有任何方法的接口,可以接受任何类型的值。通过使用空接口,可以实现泛型的功能,使得接口可以接收任何类型的数据。 使用泛型接口,我们可以在不改变接口定义的情况下,接受不同类型的参数。例如,我们可以定义一个泛型接口`Container`,用于表示一个可以容纳不同类型元素的容器。这个接口可以定义添加元素、删除元素以及获取元素等方法。 使用泛型接口的好处是可以编写灵活的代码,尽可能减少重复代码的编写。由于泛型接口可以处理多种类型的数据,我们可以将相同的逻辑应用于不同的数据类型,实现代码的重用。 然而,目前Golang没有原生支持泛型接口的功能,因此在实现泛型接口时可能需要一些额外的代码处理。一种常见的做法是使用类型断言来判断接口的实际类型,然后进行相应的操作。 总而言之,虽然Golang没有内置的泛型功能,但通过使用空接口和类型断言,我们可以实现泛型接口从而处理不同类型的数据,提高代码的重用性和灵活性。 ### 回答2: Go语言是一种静态类型的编程语言,其最近的版本Go 1.18中引入了泛型接口的概念。泛型指的是在编写代码时不指定具体类型,而是允许使用者在使用时根据自己的需求来指定具体的类型。 在传统的面向对象编程中,常用的接口表示方式是通过接口类型断言来判断对象是否实现了某个接口。但是这种方式在处理不同类型的数据时需要进行类型转换,不够灵活且有一定的性能损耗。 而泛型接口则可以在接口定义时使用类型参数,通过类型参数来指定接口的具体类型。这样一来,在使用时就可以直接将对应类型的数据传入接口中,无需进行类型转换。 泛型接口的引入为Go语言提供了更加灵活和高效的编程方式。通过泛型接口,我们可以编写更加通用和复用的代码。它还能帮助我们更好地约束函数和数据类型之间的关系,提高代码的健壮性和可读性。 不过需要注意的是,泛型接口的引入也会带来一定的复杂性。在使用泛型接口时,我们需要仔细考虑类型参数的合理性和边界条件,并且需要充分测试以确保代码的正确性。 总之,引入泛型接口是Go语言进一步发展的一大步。它提供了更多的编程方式,并且可以在一定程度上简化代码和提高效率。希望未来随着泛型接口的进一步成熟和普及,我们可以看到更多高质量、灵活和通用的Go代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值