golang-泛型基础篇(一)

本文简单介绍go泛型的概念和使用。

函数重载

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

这个函数很简单,但是它无法计算int类型之外的和。如果我们想计算浮点或者字符串的和该怎么办?解决方法就是对它进行方法的重载。但是golang不支持对方法进行重载。

//错误
func AddFloat32(a float32, b float32) float32 {
    return a + b
}

func AddString(a float64, b float64) float64{
    return a + b
}

可以通过泛型来解决。

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

泛型方法:该方法在调用时可以接收不同类型的参数。一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 泛型编程
泛型有着自己的适用场景: 如果你经常要分别为不同的类型写完全相同逻辑的代码,那么使用泛型将是最合适的选择。

Go的泛型

之前我们定义一个可以容纳 int 或 float32或 int32 等其他类型的切片。

type intSlice []int 
type Float32Slie []float32
type int32Slice []int32 

现在我们可以这样

//不同于一般的类型定义,这里类型名称 SliceT 后带了中括号
type SliceT[T int | float32 | int32 ] []T
  • 类型形参: T 在定义SliceT类型的时候 T 代表的具体类型并不确定,类似一个占位符。
  • 类型约束: int | float32 | int32 中间的 | 的意思是告诉编译器,类型形参 T 只可以接收 int 或 float32 或 float64 这三种类型的实参。
  • 类型形参列表: T int | float32 | int32
  • 这里新定义的类型名称叫 SliceT[T]

泛型类型:类型定义中带 类型形参 的类型

泛型实例

切片泛型定义

type SliceT[T int | float32 | int32 | string] []T
func SliceFunc() {
	// 这里传入了类型实参int,泛型类型SliceT[T]被实例化为具体的类型 Slice[int]
	var a SliceT[int] = []int{1, 2, 3}
	fmt.Printf("Type Name: %T\n", a) //输出:Type Name: Slice[int]

	// 传入类型实参float32, 将泛型类型SliceT[T]实例化为具体的类型 Slice[string]
	var b SliceT[float32] = []float32{1.0, 2.0, 3.0}
	fmt.Printf("Type Name: %T\n", b) //输出:Type Name: Slice[float32]

	// ✗ 错误。string不在类型约束 int|float32|float64 中,不能用来实例化泛型类型
	//var c SliceT[string] = []string{"Hello", "World"}
}

map泛型定义

type MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE
func MapFunc() {
	var mp MyMap[string, float64] = map[string]float64{
		"jack_score": 9.6,
		"bob_score":  8.4,
	}
	fmt.Println(mp)
}

channel泛型定义

type MyChan[T int | string] chan T
func ChanFunc() {
	ch := make(MyChan[int], 3)
	for i := 1; i <= 3; i++ {
		ch <- i
	}
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

接口泛型定义

// 一个泛型接口
type IPrintData[T int | float32 | string] interface {
    Print(data T)
}

简单结构体泛型定义

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

func StructFunc() {
	var my MyStruct[int] = MyStruct[int]{
		Name: "caicai",
		Data: 23,
	}
	fmt.Println(my)

	var my2 MyStruct2[int, bool] = MyStruct2[int, bool]{
		Name: "caicai",
		Data: 23,
		Sex:  true,
	}
	fmt.Println(my2)
}

类型形参的互相套用

type SliceT[T int | float32 | int32 | string] []T
type SliceT2[T int | float32 | int32] []T
type MyStruct3[T int | string] struct {
	Name  string
	Data  T
	habby SliceT[T]
	//habby2 SliceT2[T]
}
func Struct2Func() {
	var my MyStruct3[string] = MyStruct3[string]{
		Name:  "caicai",
		Data:  "23",
		habby: []string{"swim"},
	}

	fmt.Println(my)
}

我们知道 MyStruct3的约束是 int或 string 当传入的 string 时,属性habby2 不满足string,报错。

几种语法错误

  • 定义泛型类型的时候,基础类型不能只有类型形参,如下:
// 错误,类型形参不能单独使用
type CommonType[T int|string|float32] T
  • 当类型约束的一些写法会被编译器误认为是表达式时会报错
//✗ 错误。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 
因为上面逗号的用法限制比较大,这里推荐统一用 interface{} 解决问题

泛型类型的套娃

// 先定义个泛型类型 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]

// 在map中套一个泛型类型Slice[T]
type WowMap[T int | string] map[string]Slice[T]

小结

  • 为了实现泛型,Go引入了一些新的概念:
    • 类型形参
    • 类型形参列表
    • 类型约束
    • 实例化 - 泛型类型不能直接使用,要使用的话必须传入类型实参进行实例化。
  • 匿名结构体不支持泛型;
  • 泛型类型的定义;
  • 泛型类型的嵌套使用。

你可能感兴趣的:(golang,golang,后端)