当前位置: 首页 > news >正文

固安网站建设/pc网站优化排名软件

固安网站建设,pc网站优化排名软件,mac类似wordpress,简阳建设网站公司Go 泛型之类型参数 文章目录 Go 泛型之类型参数一、Go 的泛型与其他主流编程语言的泛型差异二、返回切片中值最大的元素三、类型参数(type parameters)四、泛型函数3.1 泛型函数的结构3.2 调用泛型函数3.3 泛型函数实例化(instantiation&…

Go 泛型之类型参数

文章目录

  • Go 泛型之类型参数
    • 一、Go 的泛型与其他主流编程语言的泛型差异
    • 二、返回切片中值最大的元素
    • 三、类型参数(type parameters)
    • 四、泛型函数
      • 3.1 泛型函数的结构
      • 3.2 调用泛型函数
      • 3.3 泛型函数实例化(instantiation)
    • 五、泛型类型
      • 5.1 声明泛型类型
      • 5.2 使用泛型类型
        • 5.2.1 泛型类型与类型别名
        • 5.2.2 泛型类型与类型嵌入
    • 六、泛型方法

一、Go 的泛型与其他主流编程语言的泛型差异

Go泛型和其他支持泛型的主流编程语言之间的泛型设计与实现存在差异一样,Go 的泛型与其他主流编程语言的泛型也是不同的。我们先看一下 Go 泛型设计方案已经明确不支持的若干特性,比如:

  • 不支持泛型特化(specialization),即不支持编写一个泛型函数针对某个具体类型的特殊版本;
  • 不支持元编程(metaprogramming),即不支持编写在编译时执行的代码来生成在运行时执行的代码;
  • 不支持操作符方法(operator method),即只能用普通的方法(method)操作类型实例(比如:getIndex(k)),而不能将操作符视为方法并自定义其实现,比如一个容器类型的下标访问 c[k];
  • 不支持变长的类型参数(type parameters);

这些特性如今不支持,后续大概率也不会支持。在进入 Go 泛型语法学习之前,一定要先了解 Go 团队的这些设计决策。

二、返回切片中值最大的元素

我们先来看一个例子,实现一个函数,该函数接受一个切片作为输入参数,然后返回该切片中值最大的那个元素。题目并没有明确使用什么元素类型的切片,我们就先以最常见的整型切片为例,实现一个 maxInt 函数:

// max_int.go
func maxInt(sl []int) int { if len(sl) == 0 { panic("slice is empty")} max := sl[0]for _, v := range sl[1:] { if v > max { max = v } } return max
}func main() {fmt.Println(maxInt([]int{1, 2, -4, -6, 7, 0})) // 输出:7
}

maxInt 的逻辑十分简单。我们使用第一个元素值 (max := sl[0]) 作为 max 变量初值,然后与切片后面的元素 (sl[1:]) 进行逐一比较,如果后面的元素大于 max,则将其值赋给 max,这样到切片遍历结束,我们就得到了这个切片中值最大的那个元素(即变量 max)。

我们现在给它加一个新需求:能否针对元素为 string 类型的切片返回其最大(按字典序)的元素值呢?

答案肯定是能!我们来实现这个 maxString 函数:

// max_string.go
func maxString(sl []string) string {if len(sl) == 0 {panic("slice is empty")}max := sl[0]for _, v := range sl[1:] {if v > max {max = v}}return max
}func main() {fmt.Println(maxString([]string{"11", "22", "44", "66", "77", "10"})) // 输出:77
}

maxString 实现了返回 string 切片中值最大元素的需求。不过从实现上来看,maxStringmaxInt 异曲同工,只是切片元素类型不同罢了。这时如果让你参考上述 maxIntmaxString 实现一个返回浮点类型切片中最大值的函数 maxFloat,你肯定“秒秒钟”就可以给出一个正确的实现:

// max_float.go
func maxFloat(sl []float64) float64 {if len(sl) == 0 {panic("slice is empty")}max := sl[0]for _, v := range sl[1:] {if v > max {max = v}}return max
}func main() {fmt.Println(maxFloat([]float64{1.01, 2.02, 3.03, 5.05, 7.07, 0.01})) // 输出:7.07
}

问题来了!你肯定在上面三个函数发现了的“糟糕味道”:代码重复。上面三个函数除了切片的元素类型不同,其他逻辑都一样。

那么能否实现一个“通用”的函数,可以处理上面三种元素类型的切片呢?提到“通用”,你一定想到了 Go 语言提供的 anyinterface{}的别名),我们来试试:

// max_any.go
func maxAny(sl []any) any {if len(sl) == 0 {panic("slice is empty")}max := sl[0]for _, v := range sl[1:] {switch v.(type) {case int:if v.(int) > max.(int) {max = v}case string:if v.(string) > max.(string) {max = v}case float64:if v.(float64) > max.(float64) {max = v}}}return max
}func main() {i := maxAny([]any{1, 2, -4, -6, 7, 0})m := i.(int)fmt.Println(m) // 输出:7fmt.Println(maxAny([]any{"11", "22", "44", "66", "77", "10"})) // 输出:77fmt.Println(maxAny([]any{1.01, 2.02, 3.03, 5.05, 7.07, 0.01})) // 输出:7.07
}

我们看到,maxAny 利用 anytype switch 和类型断言(type assertion)实现了我们预期的目标。不过这个实现并不理想,它至少有如下几个问题:

  1. 若要支持其他元素类型的切片,我们需对该函数进行修改;
  2. maxAny 的返回值类型为 anyinterface{}),要得到其实际类型的值还需要通过类型断言转换;
  3. 使用 anyinterface{})作为输入参数的元素类型和返回值的类型,由于存在装箱和拆箱操作,其性能与 maxInt 等比起来要逊色不少,实测数据如下:
// max_test.go
func BenchmarkMaxInt(b *testing.B) {sl := []int{1, 2, 3, 4, 7, 8, 9, 0}for i := 0; i < b.N; i++ {maxInt(sl)}
}func BenchmarkMaxAny(b *testing.B) {sl := []any{1, 2, 3, 4, 7, 8, 9, 0}for i := 0; i < b.N; i++ {maxAny(sl)}
}

测试结果如下:

$go test -v -bench . ./max_test.go max_any.go max_int.go
goos: darwin
goarch: amd64
... ...
BenchmarkMaxInt
BenchmarkMaxInt-8     398996863           2.982 ns/op
BenchmarkMaxAny
BenchmarkMaxAny-8     85883875          13.91 ns/op
PASS
ok    command-line-arguments  2.710s

我们看到,基于 anyinterface{}) 实现的 maxAny 其执行性能要比像 maxInt 这样的函数慢上数倍。

在 Go 1.18 版本之前,Go 的确没有比较理想的解决类似上述“通用”问题的手段,直到 Go 1.18 版本泛型落地后,我们可以用泛型语法实现 maxGenerics 函数:

// max_generics.go
type ordered interface {~int | ~int8 | ~int16 | ~int32 | ~int64 |~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |~float32 | ~float64 |~string
}func maxGenerics[T ordered](sl []T) T {if len(sl) == 0 {panic("slice is empty")}max := sl[0]for _, v := range sl[1:] {if v > max {max = v}}return max
}type myString stringfunc main() {var m int = maxGenerics([]int{1, 2, -4, -6, 7, 0})fmt.Println(m) // 输出:7fmt.Println(maxGenerics([]string{"11", "22", "44", "66", "77", "10"})) // 输出:77fmt.Println(maxGenerics([]float64{1.01, 2.02, 3.03, 5.05, 7.07, 0.01})) // 输出:7.07fmt.Println(maxGenerics([]int8{1, 2, -4, -6, 7, 0})) // 输出:7fmt.Println(maxGenerics([]myString{"11", "22", "44", "66", "77", "10"})) // 输出:77
}

我们看到,从功能角度看,泛型版本的 maxGenerics 实现了预期的特性,对于 ordered 接口中声明的那些原生类型以及以这些原生类型为底层类型(underlying type)的类型(比如示例中的 myString),maxGenerics 都可以无缝支持。并且,maxGenerics 返回的类型与传入的切片的元素类型一致,调用者也无需通过类型断言做转换。

此外,通过下面的性能基准测试我们也可以看出,与 maxAny 相比,泛型版本的 maxGenerics 性能要好很多,但与原生版函数如 maxInt 等还有差距。性能测试如下:

$go test -v -bench . ./max_test.go max_any.go max_int.go max_generics.go
goos: darwin
goarch: amd64
BenchmarkMaxInt
BenchmarkMaxInt-8          400910706           2.983 ns/op
BenchmarkMaxAny
BenchmarkMaxAny-8          85257433          14.04 ns/op
BenchmarkMaxGenerics
BenchmarkMaxGenerics-8     209468593           5.701 ns/op
PASS
ok    command-line-arguments  4.492s

通过这个例子,我们也可以看到 Go 泛型十分适合实现一些操作容器类型(比如切片、map 等)的算法,这也是 Go 官方推荐的第一种泛型应用场景,此类容器算法的泛型实现使得容器算法与容器内元素类型彻底解耦!

三、类型参数(type parameters)

根据官方说法,由于“泛型”(generic)一词在 Go 社区中被广泛使用,所以官方也就接纳了这一说法。但 Go 泛型方案的实质是对类型参数(type parameter)的支持,包括:

  • 泛型函数(generic function):带有类型参数的函数;
  • 泛型类型(generic type):带有类型参数的自定义类型;
  • 泛型方法(generic method):泛型类型的方法。

首先,以泛型函数为例来具体说明一下什么是类型参数。

四、泛型函数

3.1 泛型函数的结构

我们回顾一下上面的示例,maxGenerics 就是一个泛型函数,我们看一下 maxGenerics 的函数原型:

func maxGenerics[T ordered](sl []T) T {// ... ...
}

我们看到,maxGenerics 这个函数与我们之前学过的普通 Go 函数(ordinary function)相比,至少有两点不同:

  • maxGenerics 函数在函数名称与函数参数列表之间多了一段由方括号括起的代码:[T ordered]
  • maxGenerics 参数列表中的参数类型以及返回值列表中的返回值类型都是 T,而不是某个具体的类型。

maxGenerics 函数原型中多出的这段代码[T ordered]就是 Go 泛型的类型参数列表(type parameters list示例中这个列表中仅有一个类型参数 Tordered 为类型参数的类型约束(type constraint)。类型约束之于类型参数,就好比常规参数列表中的类型之于常规参数。

Go 语言规范规定:**函数的类型参数列表位于函数名与函数参数列表之间,由方括号括起的固定个数的、由逗号分隔的类型参数声明组成,**其一般形式如下:

func genericsFunc[T1 constraint1, T2, constraint2, ..., Tn constraintN](ordinary parameters list) (return values list)

函数一旦拥有类型参数,就可以用该参数作为常规参数列表和返回值列表中修饰参数和返回值的类型。我们继续 maxGenerics 泛型函数为例分析,它拥有一个类型参数 T,在常规参数列表中,T 被用作切片的元素类型;在返回值列表中,T 被用作返回值的类型。

按 Go 惯例,类型参数名的首字母通常采用大写形式,并且类型参数必须是具名的,即便你在后续的函数参数列表、返回值列表和函数体中没有使用该类型参数,也是这样。比如下面例子中的类型参数 T

func print[T any]() { // 正确
}     func print[any]() {   // 编译错误:all type parameters must be named 
}

和常规参数列表中的参数名唯一一样,在同一个类型参数列表中,类型参数名字也要唯一,下面这样的代码将会导致 Go 编译器报错:

func print[T1 any, T1 comparable](sl []T) { //  编译错误:T1 redeclared in this block//...
}

常规参数列表中的参数有其特定作用域,即从参数声明处开始到函数体结束。和常规参数类似,泛型函数中类型参数也有其作用域范围,这个范围从类型参数列表左侧的方括号[开始,一直持续到函数体结束,如下图所示:

类型参数的作用域也决定了类型参数的声明顺序并不重要,也不会影响泛型函数的行为,于是下面的泛型函数声明与上图中的函数是等价的:

func foo[M map[E]T, T any, E comparable](m M)(E, T) {//... ...
}

3.2 调用泛型函数

首先,我们对“类型参数”做一下细分。**和普通函数有形式参数与实际参数一样,类型参数也有类型形参(type parameter)和类型实参(type argument)之分。**其中类型形参就是泛型函数声明中的类型参数,以前面示例中的 maxGenerics 泛型函数为例,如下面代码,maxGenerics 的类型形参就是 T,而类型实参则是在调用 maxGenerics 时实际传递的类型 int

// 泛型函数声明:T为类型形参
func maxGenerics[T ordered](sl []T) T// 调用泛型函数:int为类型实参
m := maxGenerics[int]([]int{1, 2, -4, -6, 7, 0})

从上面这段代码我们也可以看出调用泛型函数与调用普通函数的区别。**在调用泛型函数时,除了要传递普通参数列表对应的实参之外,还要显式传递类型实参,比如这里的 int。**并且,显式传递的类型实参要放在函数名和普通参数列表前的方括号中。

在反复揣摩上面代码和说明后,你可能会提出这样的一个问题:如果泛型函数的类型形参较多,那么逐一显式传入类型实参会让泛型函数的调用显得十分冗长,比如:

foo[int, string, uint32, float64](1, "hello", 17, 3.14)

这样的写法对开发者而言显然谈不上十分友好。其实不光大家想到了这个问题,Go 团队的泛型实现者们也考虑了这个问题,并给出了解决方法:函数类型实参的自动推断(function argument type inference)。

顾名思义,这个机制就是通过判断传递的函数实参的类型来推断出类型实参的类型,从而允许开发者不必显式提供类型实参,下面是以 maxGenerics 函数为例的类型实参推断过程示意图:

我们看到,当 maxGenerics 函数传入的实际参数为 []int{…} 时,Go 编译器会将其类型 []int 与泛型函数参数列表中对应参数的类型([]T)作比较,并推断出 T == int 这一结果。当然这个例子的推断过程较为简单,那些有难度的,甚至无法肉眼可见的就交给 Go 编译器去处理吧,我们没有必要过于深入。

不过,这个类型实参自动推断有一个前提,你一定要记牢,那就是它必须是函数的参数列表中使用了的类型形参,否则就会像下面的示例中的代码,编译器将报无法推断类型实参的错误:

func foo[T comparable, E any](a int, s E) {
}foo(5, "hello") // 编译器错误:cannot infer T

在编译器无法推断出结果时,我们可以给予编译器“部分提示”,比如既然编译器无法推断出 T 的实参类型,那我们就显式告诉编译器 T 的实参类型,即在泛型函数调用时,在类型实参列表中显式传入 T 的实参类型,但 E 的实参类型依然由编译器自动推断,示例代码如下:

var s = "hello"
foo[int](5, s)  //ok
foo[int,](5, s) //ok

那么,除了函数参数列表中的参数类型可以作为类型实参推断的依据外,函数返回值的类型是否也可以呢?我们看下面示例:

func foo[T any](a int) T {var zero Treturn zero
}var a int = foo(5) // 编译器错误:cannot infer T
println(a)

我们看到,这个函数仅在返回值中使用了类型参数,但编译器没能推断出 T 的类型,所以我们切记:不能通过返回值类型来推断类型实参。

有了函数类型实参推断后,在大多数情况下,我们调用泛型函数就无须显式传递类型实参了,开发者也因此获得了与普通函数调用几乎一致的体验。

其实泛型函数调用是一个不同于普通函数调用的过程,为了揭开其中的“奥秘”,接下来我们看看泛型函数调用过程究竟发生了什么。

3.3 泛型函数实例化(instantiation)

我们还以 maxGenerics 为例来演示一下这个过程:

maxGenerics([]int{1, 2, -4, -6, 7, 0})

上面代码是对 maxGenerics 泛型函数的一次调用,Go 对这段泛型函数调用代码的处理分为两个阶段,如下图所示:

我们看到,Go 首先会对泛型函数进行实例化(instantiation),即根据自动推断出的类型实参生成一个新函数(当然这一过程是在编译阶段完成的,不会对运行时性能产生影响),然后才会调用这个新函数对输入的函数参数进行处理。

我们也可以用一种更形象的方式来描述上述泛型函数的实例化过程。实例化就好比一家生产“求最大值”机器的工厂,它会根据要比较大小的对象的类型将这样的机器生产出来。以上面的例子来说,整个实例化过程如下:

  • 工厂接单:调用 maxGenerics([]int{…}),工厂师傅发现要比较大小的对象类型为 int
  • 模具检查与匹配:检查 int 类型是否满足模具的约束要求,即 int 是否满足 ordered 约束,如满足,则将其作为类型实参替换 maxGenerics 函数中的类型形参 T,结果为 maxGenerics[int]
  • 生产机器:将泛型函数 maxGenerics 实例化为一个新函数,这里将其起名为 maxGenericsInt,其函数原型为 func([]int) int。本质上 maxGenericsInt := maxGenerics[int]

我们实际的 Go 代码也可以真实得到这台新生产出的“机器”,如下面代码所示:

maxGenericsInt := maxGenerics[int] // 实例化后得到的新“机器”:maxGenericsInt
fmt.Printf("%T\n", maxGenericsInt) // func([]int) int

一旦针对 int 对象的“求最大值”的机器被生产出来了,它就可以对目标对象进行处理了,这和普通的函数调用没有区别。这里就相当于调用如下代码:

maxGenericsInt([]int{1, 2, -4, -6, 7, 0}) // 输出:7

整个过程只需检查传入的函数实参([]int{1, 2, …})的类型与 maxGenericsInt 函数原型中的形参类型([]int)是否匹配即可。

另外要注意,当我们使用相同类型实参对泛型函数进行多次调用时,Go 仅会做一次实例化,并复用实例化后的函数,比如:

maxGenerics([]int{1, 2, -4, -6, 7, 0})
maxGenerics([]int{11, 12, 14, -36,27, 0}) // 复用第一次调用后生成的原型为func([]int) int的函数

好了,接下来我们再来看 Go 对类型参数的另一类支持:带有类型参数的自定义类型,即泛型类型。

五、泛型类型

5.1 声明泛型类型

所谓泛型类型,就是在类型声明中带有类型参数的 Go 类型,比如下面代码中的 maxableSlice

// maxable_slice.gotype maxableSlice[T ordered] struct {elems []T
}

顾名思义,maxableSlice 是一个自定义切片类型,这个类型的特点是总可以获取其内部元素的最大值,其唯一的要求是其内部元素是可排序的,**它通过带有 ordered 约束的类型参数来明确这一要求。**像这样在定义中带有类型参数的类型就被称为泛型类型(generic type)。

从例子中的 maxableSlice 类型声明中我们可以看到,在泛型类型中,类型参数列表放在类型名字后面的方括号中。和泛型函数一样,泛型类型可以有多个类型参数,类型参数名通常是首字母大写的,这些类型参数也必须是具名的,且命名唯一。其一般形式如下:

type TypeName[T1 constraint1, T2 constraint2, ..., Tn constraintN] TypeLiteral

和泛型函数中类型参数有其作用域一样,泛型类型中类型参数的作用域范围也是从类型参数列表左侧的方括号[开始,一直持续到类型定义结束的位置,如下图所示:

这样的作用域将方便我们在各个字段中灵活使用类型参数,下面是一些自定义泛型类型的示例:

type Set[T comparable] map[T]struct{}type sliceFn[T any] struct {s   []Tcmp func(T, T) bool
}type Map[K, V any] struct {root    *node[K, V]compare func(K, K) int
}type element[T any] struct {next *element[T]val  T
}type Numeric interface {~int | ~int8 | ~int16 | ~int32 | ~int64 |~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |~float32 | ~float64 |~complex64 | ~complex128
}type NumericAbs[T Numeric] interface {Abs() T
}

我们看到,泛型类型中的类型参数可以用来作为类型声明中字段的类型(比如上面的 element 类型)、复合类型的元素类型(比如上面的 SetMap 类型)或方法的参数和返回值类型(如 NumericAbs 接口类型)等。

如果要在泛型类型声明的内部引用该类型名,必须要带上类型参数,如上面的 element 结构体中的 next 字段的类型:*element[T]。按照泛型设计方案,如果泛型类型有不止一个类型参数,那么在其声明内部引用该类型名时,不仅要带上所有类型参数,类型参数的顺序也要与声明中类型参数列表中的顺序一致,比如:

type P[T1, T2 any] struct {F *P[T1, T2]  // ok
}

不过从实测结果来看,对于下面不符合技术方案的泛型类型声明也并未报错:

type P[T1, T2 any] struct {F *P[T2, T1] // 不符合技术方案,但Go 编译器并未报错
}

5.2 使用泛型类型

和泛型函数一样,使用泛型类型时也会有一个实例化(instantiation)过程,比如:

var sl = maxableSlice[int]{elems: []int{1, 2, -4, -6, 7, 0},
} 

Go 会根据传入的类型实参(int)生成一个新的类型并创建该类型的变量实例,sl 的类型等价于下面代码:

type maxableIntSlice struct {elems []int
}

看到这里你可能会问:泛型类型是否可以像泛型函数那样实现类型实参的自动推断呢?很遗憾,目前的 Go 1.21.4 尚不支持,下面代码会遭到 Go 编译器的报错:

var sl = maxableSlice {elems: []int{1, 2, -4, -6, 7, 0}, // 编译器错误:cannot use generic type maxableSlice[T ordered] without instantiation
} 

不过这一特性在 Go 的未来版本中可能会得到支持。

既然涉及到了类型,你肯定会想到诸如类型别名、类型嵌入等 Go 语言机制,那么这些语言机制对泛型类型的支持情况又是如何呢?我们逐一来看一下。

5.2.1 泛型类型与类型别名

我们知道类型别名type alias)与其绑定的原类型是完全等价的,但这仅限于原类型是一个直接类型,即可直接用于声明变量的类型。那么将类型别名与泛型类型绑定是否可行呢?我们来看一个示例:

type foo[T1 any, T2 comparable] struct {a T1b T2
}type fooAlias = foo // 编译器错误:cannot use generic type foo[T1 any, T2 comparable] without instantiation

在上述代码中,我们为泛型类型 foo 建立了类型别名 fooAlias,但编译这段代码时,编译器还是报了错误!

这是因为,泛型类型只是一个生产真实类型的“工厂”,它自身在未实例化之前是不能直接用于声明变量的,因此不符合类型别名机制的要求。泛型类型只有实例化后才能得到一个真实类型,例如下面的代码就是合法的:

type fooAlias = foo[int, string]

也就是说,我们只能为泛型类型实例化后的类型创建类型别名,实际上上述 fooAlias 等价于实例化后的类型 fooInstantiation

type fooInstantiation struct {a int   b string
}
5.2.2 泛型类型与类型嵌入

类型嵌入是运用 Go 组合设计哲学的一个重要手段。引入泛型类型之后,我们依然可以在泛型类型定义中嵌入普通类型,比如下面示例中 Lockable 类型中嵌入的 sync.Mutex

type Lockable[T any] struct {t Tsync.Mutex
}func (l *Lockable[T]) Get() T {l.Lock()defer l.Unlock()return l.t
}func (l *Lockable[T]) Set(v T) {l.Lock()defer l.Unlock()l.t = v
}

在泛型类型定义中,我们也可以将其他泛型类型实例化后的类型作为成员。现在我们改写一下上面的 Lockable,为其嵌入另外一个泛型类型实例化后的类型 Slice[int]

type Slice[T any] []Tfunc (s Slice[T]) String() string {if len(s) == 0 {return ""}var result = fmt.Sprintf("%v", s[0])for _, v := range s[1:] {result = fmt.Sprintf("%v, %v", result, v)}return result
}type Lockable[T any] struct {t TSlice[int]sync.Mutex
}func main() {n := Lockable[string]{t:     "hello",Slice: []int{1, 2, 3},}println(n.String()) // 输出:1, 2, 3
}

我们看到,代码使用泛型类型名(Slice)作为嵌入后的字段名,并且 Slice[int] 的方法 String 被提升为 Lockable 实例化后的类型的方法了。同理,在普通类型定义中,我们也可以使用实例化后的泛型类型作为成员,比如让上面的 Slice[int] 嵌入到一个普通类型 Foo 中,示例代码如下:

type Foo struct {Slice[int]
}func main() {f := Foo{Slice: []int{1, 2, 3},}println(f.String()) // 输出:1, 2, 3
}

此外,Go 泛型设计方案支持在泛型类型定义中嵌入类型参数作为成员,比如下面的泛型类型 Lockable 内嵌了一个类型 T,且 T 恰为其类型参数:

type Lockable[T any] struct {Tsync.Mutex
}

不过,Go 最新版1.21.4 编译上述代码时会针对嵌入 T 的那一行报如下错误:

编译器报错:embedded field type cannot be a (pointer to a) type parameter

关于这个错误,Go 官方在其 issue 中给出了临时的结论:暂不支持。

六、泛型方法

我们知道 Go 类型可以拥有自己的方法(method),泛型类型也不例外,为泛型类型定义的方法称为泛型方法(generic method),接下来我们就来看看如何定义和使用泛型方法。

我们用一个示例,给 maxableSlice 泛型类型定义 max 方法,看一下泛型方法的结构:

func (sl *maxableSlice[T]) max() T {if len(sl.elems) == 0 {panic("slice is empty")}max := sl.elems[0]for _, v := range sl.elems[1:] {if v > max {max = v}}return max
}

我们看到,在定义泛型类型的方法时,方法的 receiver 部分不仅要带上类型名称,还需要带上完整的类型形参列表(如 maxableSlice[T]),这些类型形参后续可以用在方法的参数列表和返回值列表中。

不过在 Go 泛型目前的设计中,泛型方法自身不可以再支持类型参数了,不能像下面这样定义泛型方法:

func (f *foo[T]) M1[E any](e E) T { // 编译器错误:syntax error: method must have no type parameters//... ...
}

关于泛型方法未来是否能支持类型参数,目前 Go 团队倾向于否,但最终结果 Go 团队还要根据 Go 社区在使用泛型过程中的反馈而定。

在泛型方法中,receiver 中某个类型参数如果没有在方法参数列表和返回值中使用,可以用“_”代替,但不能不写,比如:

type foo[A comparable, B any] struct{}func (foo[A, B]) M1() { // ok
}func (foo[_, _]) M1() { // ok
}func (foo[A, _]) M1() { // ok
}func (foo[]) M1() { // 错误:receiver部分缺少类型参数}

另外,泛型方法中的 receiver 中类型参数名字可以与泛型类型中的类型形参名字不同,位置和数量对上即可。我们还以上面的泛型类型 foo 为例,可以为它添加下面方法:

type foo[A comparable, B any] struct{}func (foo[First, Second]) M1(a First, b Second) { // First对应类型参数A,Second对应类型参数B}

相关文章:

Go 泛型之类型参数

Go 泛型之类型参数 文章目录 Go 泛型之类型参数一、Go 的泛型与其他主流编程语言的泛型差异二、返回切片中值最大的元素三、类型参数&#xff08;type parameters&#xff09;四、泛型函数3.1 泛型函数的结构3.2 调用泛型函数3.3 泛型函数实例化&#xff08;instantiation&…...

KafkaLog4jAppender

Apache Log4j 中有一个 Appender 概念&#xff0c;它负责将日志信息输出到各种目的地&#xff0c;例如控制台、文件、数据库等。KafkaLog4jAppender 是 Log4j 的一个扩展&#xff0c;它可以将日志信息发送到 Apache Kafka。 下面是如何在 Log4j 中使用 KafkaLog4jAppender 的一…...

IntelliJ IDEA插件

插件安装目录&#xff1a;C:\Users\<username>\AppData\Roaming\JetBrains\IntelliJIdea2021.2\plugins aiXcoder Code Completer&#xff1a;代码补全 Bookmark-X&#xff1a;书签分类 使用方法&#xff1a;鼠标移动到某一行&#xff0c;按ALT SHIFT D...

鸿蒙开发中的坑(持续更新……)

最近在使用鸿蒙开发时&#xff0c;碰到了一些坑&#xff0c;特做记录&#xff0c;如&#xff1a;鸿蒙的preview不能预览&#xff0c;轮播图组件Swiper使用时的问题&#xff0c;console.log() 打印的内容 一、鸿蒙的preview不能预览 首先&#xff0c;只有 ets文件才能预览。 其…...

单体项目-动态上下文问题

在HTML中使用Thymeleaf解决动态上下文问题&#xff0c;你可以使用Thymeleaf的模板语法来生成动态的链接&#xff08;例如CSS和JavaScript文件的链接&#xff09;以适应不同的应用程序上下文。以下是一个示例&#xff1a; <!DOCTYPE html> <html xmlns:th"http:/…...

Qt/QML编程学习之心得:实现一个图片浏览器(十八)

QML中有个重要控件,经常使用就是image,通常可以用它来显示一张图片。如果想结合openfiledialog来让image显示图片,也就是做一个简易的图片浏览器,怎么弄呢? DefaultFileDialog.qml: import QtQuick 2.0 import QtQuick.Dialogs 1.0FileDialog {id: fileDialogtitle: &qu…...

kafka发送大消息

1 kafka消息压缩 kafka关于消息压缩的定义&#xff08;来源于官网&#xff09;&#xff1a; 此为 Kafka 中端到端的块压缩功能。如果启用&#xff0c;数据将由 producer 压缩&#xff0c;以压缩格式写入服务器&#xff0c;并由 consumer 解压缩。压缩将提高 consumer 的吞吐量…...

React AntDesign form表单文件上传 nodejs formidable 接受参数并把文件放置后端项目相对目录指定文件夹下面

@umijs/max 请求方法 // 上传文件改成form表单 export async function uploadFile(data, options) {return request(CMMS_UI_HOST + /api/v1/uploadFile, {method: POST,data,requestType: form,...(options || {}),}); }前端调用方法 注意upload组件上传 onChange的如下方法,…...

设计模式之-6大设计原则简单易懂的理解以及它们的适用场景和代码示列

系列文章目录 设计模式之-6大设计原则简单易懂的理解以及它们的适用场景和代码示列 设计模式之-单列设计模式&#xff0c;5种单例设计模式使用场景以及它们的优缺点 设计模式之-3种常见的工厂模式简单工厂模式、工厂方法模式和抽象工厂模式&#xff0c;每一种模式的概念、使用…...

css 实现满屏升空的气球动画

最终实现效果 demo放在最后了。。。。 问题一 怎么实现满屏气球&#xff1f;简单理解就是多个气球的合并&#xff0c;难道要写多个盒子吗&#xff1f;确实是这样子&#xff0c;但可以有更好的办法&#xff0c;其实就是通过原生操作多个盒子生成&#xff0c;所以只需要实现一个…...

批量归一化

目录 一、BN层介绍 1、深层神经网络存在的问题 2、批量归一化公式的数学推导 3、BN层的作用位置 4、 预测过程中的批量归一化 5、BN层加速模型训练的原因 6、总结 二、批量归一化从零实现 1、实现批量归一化操作 2、创建BN层 3、对LeNet加入批量归一化 4、开始训练…...

C语言:字符串字面量及其保存位置

相关阅读 C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482 虽然C语言中不存在字符串类型&#xff0c;但依然可以通过数组或指针的方式保存字符串&#xff0c;但字符串字面量却没有想象的这么简单&#xff0c;本文就将对此进行讨论…...

【开源】基于Vue+SpringBoot的新能源电池回收系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户档案模块2.2 电池品类模块2.3 回收机构模块2.4 电池订单模块2.5 客服咨询模块 三、系统设计3.1 用例设计3.2 业务流程设计3.3 E-R 图设计 四、系统展示五、核心代码5.1 增改电池类型5.2 查询电池品类5.3 查询电池回…...

共享和独享的区别是什么?有必要用独享IP吗?

通俗地讲&#xff0c;共享IP就像乘坐公共汽车一样&#xff0c;您可以到达目的地&#xff0c;但将与其他乘客共享旅程&#xff0c;座位很可能是没有的。独享IP就像坐出租车一样&#xff0c;您可以更快到达目的地&#xff0c;由于车上只有您一个人&#xff0c;座位是您一个人专用…...

leetcode——打家劫舍问题汇总

本章汇总一下leetcode中的打家劫舍问题&#xff0c;使用经典动态规划算法求解。 1、梦开始的地方——打家劫舍&#xff08;★&#xff09; 本题关键点就是不能在相邻房屋偷东西。 采用常规动态规划做法&#xff1a; 根据题意设定dp数组&#xff0c;dp[i]的含义为&#xff1a…...

Java经典框架之Spring MVC

Spring MVC Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. Spring MVC 入门案例 2. 基…...

Golang make vs new

文章目录 1.简介2.区别3.new 可以初始化 slice&#xff0c;map 和 channel 吗&#xff1f;4.make 可以初始化其他类型吗&#xff1f;5.小结参考文献 1.简介 在 Go 语言中&#xff0c;make 和 new 是两个用于创建对象的内建函数&#xff0c;但它们有着不同的用途和适用范围。 …...

Arthas

概述 Arthas&#xff08;阿尔萨斯&#xff09; 能为你做什么&#xff1f; Arthas 是Alibaba开源的Java诊断工具&#xff0c;深受开发者喜爱。 当你遇到以下类似问题而束手无策时&#xff0c;Arthas可以帮助你解决&#xff1a; 这个类从哪个 jar 包加载的&#xff1f;为什么会…...

IP代理科普| 共享IP还是独享IP?两者的区别与优势

通俗地讲&#xff0c;共享IP就像乘坐公共汽车一样&#xff0c;您可以到达目的地&#xff0c;但将与其他乘客共享旅程&#xff0c;座位很可能是没有的。独享IP就像坐出租车一样&#xff0c;您可以更快到达目的地&#xff0c;由于车上只有您一个人&#xff0c;座位是您一个人专用…...

龙芯loongarch64服务器编译安装tensorflow-io-gcs-filesystem

前言 安装TensorFlow的时候,会出现有些包找不到的情况,直接使用pip命令也无法安装,比如tensorflow-io-gcs-filesystem,安装的时候就会报错: 这个包需要自行编译,官方介绍有限,这里我讲解下 编译 准备 拉取源码:https://github.com/tensorflow/io.git 文章中…...

开源持续测试平台Linux MeterSphere本地部署与远程访问

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…...

Kubernetes(K8S)快速入门

概述 在本门课程中&#xff0c;我们将会学习K8S一些非常重要和核心概念&#xff0c;已经操作这些核心概念对应组件的相关命令和方式。比如Deploy部署&#xff0c;Pod容器&#xff0c;调度器&#xff0c;Service服务&#xff0c;Node集群节点&#xff0c;Helm包管理器等等。 在…...

将遗留系统分解为微服务:第 2 部分

在当今不断发展的技术环境中&#xff0c;从整体架构向微服务的转变对于许多企业来说都是一项战略举措。这在报销计算系统领域尤其重要。正如我在上一篇文章第 1 部分应用 Strangler 模式将遗留系统分解为微服务-CSDN博客中提到的&#xff0c;让我们探讨如何有效管理这种转变。 …...

RK3588平台开发系列讲解(AI 篇)RKNN-Toolkit2 模型的加载转换

文章目录 一、Caffe 模型加载接口二、TensorFlow 模型加载接口三、TensorFlowLite 模型加载接口四、ONNX 模型加载五、DarkNet 模型加载接口六、PyTorch 模型加载接口沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 RKNN-Toolkit2 目前支持 Caffe、TensorFlow、Tensor…...

CNVD原创漏洞审核和处理流程

一、CNVD原创漏洞审核归档和发布主流程 &#xff08;一&#xff09;审核和归档流程 审核流程分为一级、二级、三级审核&#xff0c;其中一级审核主要对提交的漏洞信息完整性进行审核&#xff0c;漏洞符合可验证&#xff08;通用型漏洞有验证代码信息或多个互联网实例、事件型…...

【java爬虫】基于springboot+jdbcTemplate+sqlite+OkHttp获取个股的详细数据

注&#xff1a;本文所用技术栈为&#xff1a;springbootjdbcTemplatesqliteOkHttp 前面的文章我们获取过沪深300指数的成分股所属行业以及权重数据&#xff0c;本文我们来获取个股的详细数据。 我们的数据源是某狐财经&#xff0c;接口的详细信息在下面的文章中&#xff0c;本…...

智能优化算法应用:基于人工兔算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于人工兔算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于人工兔算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工兔算法4.实验参数设定5.算法结果6.参考文…...

【ubuntu 22.04】安装vscode并配置正常访问应用商店

注意&#xff1a;要去vscode官网下载deb安装包&#xff0c;在软件商店下载的版本不支持输入中文 在ubuntu下用火狐浏览器无法访问vscode官网&#xff0c;此时可以手动进行DNS解析&#xff0c;打开DNS在线查询工具&#xff0c;解析以下主机地址&#xff08;复制最后一个IP地址&a…...

K8s出现问题时,如何排查解决!

K8s问题的排查 1. POD启动异常、部分节点无法启动pod2. 审视集群状态3. 追踪事件日志4. 聚焦Pod状态5. 检查网络连通性6. 审视存储配置7. 研究容器日志8. K8S集群网络通信9. 问题&#xff1a;Service 是否通过 DNS 工作&#xff1f;10. 总结1、POD启动异常、部分节点无法启动p…...

2015年第四届数学建模国际赛小美赛B题南极洲的平均温度解题全过程文档及程序

2015年第四届数学建模国际赛小美赛 B题 南极洲的平均温度 原题再现&#xff1a; 地表平均温度是反映气候变化和全球变暖的重要指标。然而&#xff0c;在以前的估计中&#xff0c;在如何界定土地平均数方面存在一些方法上的差异。为简单起见&#xff0c;我们只考虑南极洲。请建…...