我正在参加「启航计划」
泛型的”宿世”
Q: 为什么需要泛型,不是现已有interface{}?
A: 触摸过c++、java等静态语言的童鞋关于泛型这个东西都有一定了解,泛型关于咱们进步代码复用性有极大作用。泛型在静态语言中提供了特别的数据类型做参数的承受或返回,避免了相同事务代码需要写多个不同类型的函数(办法),导致项目中出现大量的重复代码。
A: 在Go1.18引进前通常运用interface{}代替,下面编写个比如:
// Sum calculate the value of two value with the same data type.
// the data type can be int, float64, string
// a the first value
// b the second value
func Sum(a,b interface{}) interface{} {
if reflect.TypeOf(a).Kind() != reflect.TypeOf(b).Kind() {
return nil
}
switch reflect.TypeOf(a).Kind() {
case reflect.Int:
return reflect.ValueOf(a).Int() + reflect.ValueOf(b).Int()
case reflect.Float64:
return reflect.ValueOf(a).Float() + reflect.ValueOf(b).Float()
case reflect.String:
return reflect.ValueOf(a).String() + reflect.ValueOf(b).String()
default:
return nil
}
}
func main() {
a :=Sum(1,2)
b := Sum(1.1,2.1)
c := Sum("1","2")
fmt.Printf("a:%v\t b:%v c:%v\n", a, b, c)
}
为什么要引证泛型,相信看完上述比如的小伙伴现已能够有所理解;inteface{}虽然处理了不同数据类型的参数承受和返回,可是需要反射出对应的数据类型进行分类处理,频繁地进行类型转化,这就形成代码的复杂性和性能低效。下面咱们首要根据go 泛型提案的内容进一步概述,英文阅览才能好的童鞋能够直接看该文档。go.googlesource.com/proposal/+/…
Q: 泛型的引进给go带来哪些新特性?
A: Go 1.18.0 规范库正式引进泛型支撑,其新特性如下:
- 参数类型(Type parameters)
- 束缚类型(constraints)
- 参数类型列表
- 泛型类型(Generic types)
- 泛型函数
- 泛型接收器(receiver)
golang 泛型 “此生”
Golang泛型几个新特性:
1.泛型语法(Generics syntax)
结构体语法:
type container[T any] struct{
elem T
}
函数语法:
// 第一种
func Functionname[T any](p T) {
...
}
// 第二种
func func1[T string | int | float64 ] (a T){
}
type Float interface {
~float32 | ~float64
}
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type Integer interface {
Signed | Unsigned
}
type Ordered interface {
Integer | Float | ~string
}
// 第三种
func func1[T Ordered] (a T){
fmt.Printf("current value is : %v\n", reflect.TypeOf(a))
}
2. 类型束缚(Type constraints)
golang引进了标识符 any , 可用来替代interface{},在泛型运用中,常用于表明任何类型;
func Filter[T any](s []T) {
for _, v := range s {
_, _ = fmt.Printf("%v\t", v)
}
_, _ = fmt.Print("\n")
}
func main() {
assembleSlice([]int{1,2,3,4})
assembleSlice([]string{"chengdu", "sichuan"})
}
slice:
func ForEach[T any](s []T, f func(ele T, i int, s []T)) {
for i, ele := range s {
f(ele, i, s)
}
}
func main() {
s := []int{1, 2, 3, 4, 5}
ForEach(s, func(ele int, i int, s []int) {
fmt.Printf("ele at %d is %d\n", i, ele)
})
}
map:
// keys return the key of a map
// here m is generic using K and V
// V is contraint using any
// K is restrained using comparable i.e any type that supports != and == operation
func keys[K comparable, V any](m map[K]V) []K {
// creating a slice of type K with length of map
key := make([]K, len(m))
i := 0
for k, _ := range m {
key[i] = k
i++
}
return key
}
3.类型参数(Type parameters) 在这里咱们以上图为例,扼要阐述下几个概念:
类型形参 (Type parameter): T
类型束缚 (Type constraint): any 也能够运用 int | float32 | float64 等详细确认的类型表明;
类型形参列表(type parameter list) : T int | float32 | float64
泛型类型(Generic type): 类型界说中带 类型形参的类型
类型实参(Type argument): 详细的类型的参数
实例化(Instantiations): 传入类型实参确认详细类型的操作
“纸上谈来终觉浅,绝知此事要躬行指”,下面就经过一个比如来具象化上面的概念
type Slice[T int | float32 | float64 | string] []T
function main() {
var a Slice[int] = []int{1, 2, 3}
fmt.Printf("slice: %v\n", a) // console slice: [1,2,3]
var b Slice[string] = []string{"thank", "you", "very", "much"}
fmt.Printf("slice: %v\n", b)
// var c Slice[T] = []int{1.1, 2.1, 3.1}
}
这里 Slice[T] 泛型被类型实参 int 实例化为详细的类型 Slice[int]。 T为类型形参; int | float32 | float64 | string为 T的类型束缚;Slice[T int | float32 | float64 | string] 是 泛型类型;变量 a 中 int为类型实参;
4. 其他的泛型类型
type Float interface {
~float32 | ~float64
}
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type Integer interface {
Signed | Unsigned
}
// 泛型接口
type Ordered interface {
Integer | Float |~string
}
// 泛型结构体
type CustStruct[T int | float | string] struct {
Code int
Message string
Data T
}
// 泛型通道
type CustChan[T int | string] chan T
在这里讲一个小知识点,~ 代表了指定底层类型的所有类型。Go新增该标识符就会为处理自界说类型无法实例化的问题,例如:
type Int interface {
int | int8 | int16 | int32 | int64
}
type Slice[T Int] []T
var s Slice[int] // ✔
type CusInt int
var s1 Slice[CusInt] // ✘ 编译过错 缺失int类型束缚,主张用~int
当然~也有限制运用,后面的语法过错内会详细解说。
当然本次1.18 还引进规范库的泛型改造,即 constraints规范包。以及内置泛型限制,匹配所有答应相等比较的类型comparable的新特性带给规范库的影响,这里我不再过多的展开讨论,详细能够参阅
taoshu.in/go/no-chang…
go.dev/blog/intro-…
Golang泛型完成原理:
执行命令 go tool compile -S -N -l sliceGeneric.go 或许 go build -gcflags=”-l -S” sliceGeneric.go > sliceGeneric.s 2>&1 编译成汇编代码,如下
FUNCDATA $2, main.main.stkobj(SB)
LEAQ main..dict.func1[string](SB), AX
LEAQ go.string."a"(SB), BX
MOVL $1, CX
PCDATA $1, $0
CALL main.func1[go.shape.string_0](SB)
经过汇编代码, 咱们知道GO泛型是根据编译器完成,泛型的函数/办法运用了字典来寄存,这样避免了泛型函数/办法每一次调用创立不同的函数实例,附带不同类型的参数,该字典提供了关于类型参数的相关信息,答应单个函数实例对许多不同的类型参数正确运转。为了削减性能的损耗,提出了GC Shape Stenciling 计划。gcshape是类型的调集,当被指定为类型参数之一时,这些类型能够在泛型的完成中共享通用函数/办法的相同实例。关于具有单一类型参数的泛型类型的办法,只需对gcshape相同的类型参数进行一次实例化,终究转化为对应详细的类型。
Golang泛型运用指南:
泛型切片(Generic slice)
type Slice [T int|float64|string] []T
泛型map:
type GenericMap[K int | string, V float32 | float64] map[K]V
var m GenericMap[string, float32] = map[string]float32 {
"golang": 1.19,
"java": 1.18,
}
泛型struct:
type Response [T string | int | float64 | bool] {
Code int
Msg string
Data T
}
留意:匿名结构体是不支撑泛型的,例如:
resp := struct[T int|string] {
Code int
Msg string
Data T
}[int] {
"error",
2,
3,
}
fmt.Println("response:", resp) // ✘ 编译不经过,语法过错
泛型channel:
type CusChan[T any] chan T
func main() {
// 声明string类型带缓冲的channel
ch := make(CusChan[string], 5)
ch <- "hello gopher"
x := <- ch
close(ch)
fmt.Printf("%v\n",x)
}
泛型函数:(generic function)
func Sum[T int|float64](a,b T) T {
return a + b
}
自界说束缚类型
type cusInteger interface {
int | int8 | int16 | int32 | int64
}
type cusNumb interface {
int | float64
}
type cusFloat interface {
float32 | float64
}
type DefinedNumb interface {
cusInteger
cusNumb
}
type DefinedNumb1 interface {
cusInteger
cusFloat
}
func ForEach[T DefinedNumb1](s []T) {
for i,v := range s {
fmt.Printf("the index: %v\t value: %v\n", i, v)
}
}
留意:自界说束缚类型的交集不能为空
泛型办法:(receiver type)
在这里办法是指接收者(receiver)类型变量的函数。因此咱们还是用比如阐明这些概念:
type cusStr string
func (s cusStr)addString(p string) string {
s.append(p)
fmt.Println(s)
}
接收器泛型 + 参数泛型
type Number interface {
~int | ~float32 | ~float64 | ~string
}
type CusSlice[T Number] []T
// 接收器泛型
func (c CusSlice[T]) add() T {
var sum T
for _, v := range c {
sum += v
}
return sum
}
type CusSlice1 []int
// func (c CusSlice1) add[T int](a T) []T { //✘ 编译不经过,办法不支撑泛型
// }
// 接收器泛型 + 参数类型
func (s CusSlice[T]) add(a T) []T{
s = append(s, a)
return s
}
func main() {
s := CusSlice[int] {1, 3, 4, 2, 1}
sum := s.add()
s = s.add(5);
fmt.Printf("%v\n",s) // [1 3 4 2 1 5]
fmt.Printf("sum:%v\n", sum) // sum: 11
}
泛型接口:
在Go1.18之前,常常将接口界说为一个办法集。
An interface type specifies a method set called its interface
在1.18之后,接口由办法集变为了 类型集,类型集即类型的调集。
1.根本接口 (即只要办法)
type error interface {
Error() string
}
// 根本泛型接口
type Cus[T int| string] interface {
Error() T
}
func Error() string {
return "error"
}
2.一般接口(既有办法,又有类型)
type Reader interface {
~int | ~string | ~float32 | ~float64
Read(p []byte) (n int, err error)
}
// 一般泛型接口
type CusInterface [T int| string] interface {
int | string
SetName(d T) T
GetName() T
}
// 实例化(有必要完成Read和底层类型的类型)
func SetName(d string) string {
return d
}
func GetName() string {}
// var c CusInterface[string] = {} // ✘ 编译不经过,一般泛型接口只能作为一个类型束缚
//cannot use type CusInterface[string] outside a type constraint: interface contains type
留意:一般泛型接口,只能被作为类型参数来运用,无法被实例化
语法运用过错集锦:
~限制运用:
type CusInt int
type CusType interface {
~CusInt // ✘ 只能为根本类型
~error // ✘ 不能为接口
}
接口泛型限制
// 运用 | (union)衔接多个类型时不能运用包括类型有交集的类型
type CusInt int
type CusInteger interface {
~int | CusInt // ✘
~int | interface{CusInt} // ✔ 接口包括的类型在外
~interface{int} | CusInt // ✔
}
// 接口中包括的类型中出现空集, 使得该代码无任何意义
type Number interface {
int
float32 // 虽然不会报错,可是这里现已是空集,没有任何意义,尽量不要运用该写法
}
// 一般泛型接口当包换束缚类型时,只能作类型参数运用【借用之前的比如】
type CusInterface [T int| string] interface {
int | string
SetName(d T) T
GetName() T
}
func SetName(d string) string {
return d
}
func main() {
var c CusInterface[string] = {} // ✘ 编译不经过
}
语法歧义导致过错
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type CusRead[T *Reader] []T // ✘ 编译不经过,编译器识别为表达式,而非指针
type CusRead[T interface{*Reader}] []T // ✔ 引荐写法
type CusIo[T *Reader | *Writer] []T // ✘ 编译不经过
type CusIo[T interface{*Reader | *Writer}] []T // ✔ 引荐写法
针对语法歧义的情景,通常咱们主张运用 interface{}
匿名方式不支撑泛型
// 匿名函数
sum := func[T int|float32|float64|string] (a, b T) T { // ✘ 编译不经过
return a +b;
}
fmt.Println(sum(1, 2))
// 匿名结构体
resp := struct [T int|string|bool]{ // ✘ 编译不经过
code int
msg string
data T
}[bool]{
code: 1,
msg: "success",
data: true,
}