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)

再来个示例

func TestAdd(t *testing.T) {
	Tasq[[]string]([][]string{{"h", "e"}, {"h", "e"}})
	Tasq([]string{"h", "e"})

}

func Tasq[T any](uri []T) ([]T, error) {
	return uri, nil
}
自定义类型约束

声明一个方法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个接口里面,有约束类型的,有或者没有方法的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值