12.泛型

    Go语言在v1.18版本添加。其中泛即通用的意思

12.1 泛型函数

    如果没有泛型,同一种类型需要使用重载功能,但Go又不支持重载功能。如果写一个简单的加法计算函数,我们只能定义出如下所示的代码:

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

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

func JoinString(a, b string) string {
	return a + b
}

    从上面代码可以看出,每个函数的参数个数都一样,仅仅参数的数据类型不一样,如果还有类似的功能,仅仅是参数数据类型不一致,就还得增加函数,从而导致代码存在大量的重复。那么有没有一种,可以将参数的数据类型再抽象一次来解决这样的问题,我们来看看以下代码

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

    上面代码中的T表示类型形参,即参数的数据类型是可变的,a,b T则表明a和b的类型要一致。即在运行时,T最终一定会确定是某一种数据类型。Go语言针对这一问题的解决方案是泛型。其语法格式如下所示:

func name[T,P](parameters-1 T,parameters-2 P) P{
	函数语句块
}

    示例代码如下所示:

package main

import "fmt"

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

func main() {
	fmt.Printf("int: %v,%[1]T\n", Add(4, 5))
	fmt.Printf("float: %v,%[1]T\n", Add(4.7, 5.8))
	fmt.Printf("string: %v,%[1]T\n", Add("Sur", "pass"))
}

    运行结果如下所示:

int: 9,int
float: 10.5,float64
string: Surpass,string

    通过以上代码,可以看到代码量大大减小,而且也非常简洁。

  • T: 称为类型形参,仅仅是一个类型占位符
  • int | float64 | string: 称为类型约束,其中|表示或的意思
  • T int | float64 | string:称为类型参数列表,存在多个时,使用逗号进行分隔,例如 [T int | string , P string | bool]
  • Add[T]:即泛型函数
  • Add[int]: 即类型实参,传入int给泛型函数的过程称为泛型函数实例化
  • Add[int](4,5) 可以简写为 Add(4,5),因为可以根据传入的参数自动推断出数据类型
  • 定义泛型函数时,函数名后面紧跟着类型参数列表。因此匿名函数不可以定义为泛型函数,但可通过使用定义的类型开通T

12.2 类型约束

    类型约束是一个接口。为支持泛型,Go v1.18对接口进行了语法扩展。用在泛型中,接口含义就是符合这些特征的类型集合。在Go语言中,内置了两个约束:

  • any: 表示任意类型
  • comparable: 表示类型的值可以使用==!=进行比较
[T int ] 等价于[T interface{int}] // 表示T只能是int类型

type Constraint interface{
	int | string
}

[T int | string ] 、 [ T interface{int | string }] 和 [T Constraint]  三者等价,都表示类型只能是int或string类型

12.3 泛型类型

    示例代码如下所示:

package main

import "fmt"

// 接口从本质上讲,就是一种类型约束,如果要使用接口类型,则要求其结构必须接口中所有的方法
type Runer interface {
	Run()
}

// 定义一种新的Map类型,要求Key的数据类型为int或string,而value为Runner类型
// [K string | int, V Runer] 对自定义类型的参数进行约束
type MyMap[K string | int, V Runer] map[K]V

type MyString string // 类型新定义

func (myString MyString) Run() {
	fmt.Printf("MyString:%#v - %[1]T\n", myString)
}

func main() {
	// var m MyMap[string, MyString] = make(MyMap[string, MyString])
	d := make(MyMap[string, MyString]) //相当于map[string]MyString{}
	fmt.Printf("d: %#v - %[1]T\n", d)
	d["name"] = "Surpass"
	fmt.Printf("d: %#v - %[1]T\n", d)
	d["name"].Run()
}

    运行结果如下所示:

d: main.MyMap[string,main.MyString]{} - main.MyMap[string,main.MyString]
d: main.MyMap[string,main.MyString]{"name":"Surpass"} - main.MyMap[string,main.MyString]
MyString:"Surpass" - main.MyString

    结合以上代码,泛型是对函数的参数或返回值设置多个数据类型,比普通函数更灵活的设置参数类型和返回值类型,但却又比不上空接口参数的开放自由。

    既然泛型比不上空接口,那为什么还要引入泛型呢?我们知道空接口参数不受数据类型限制,但如果在调用过程中,函数传入的参数是无法处理的数据类型,则很容易导致出现异常情况。而使用泛型时,既可以对函数的参数和返回值数据类型进行约束,也能保证传入参数的多样性

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

posted @ 2025-08-26 00:22  Surpassme  阅读(11)  评论(0)    收藏  举报