golang.18泛型

非泛型版本:

写一段段冒泡排序的代码,但是这个bubbleSort函数参数类型只能为int64,也就是说我们只能传入一个类型为int64切片,如果我需要传入其他类型的数据,那么我们就需要重新写一个函数逻辑是相同的代码片段但是类型又不同,这就是没有泛型带来的痛苦.

package main

import "fmt"

func bubbleSort(sequence []int64) {
    for i := 0; i < len(sequence)-1; i++ {
        for j := 0; j < len(sequence)-1-i; j++ {
            if sequence[j] > sequence[j+1] {
                sequence[j], sequence[j+1] = sequence[j+1], sequence[j]
            }
        }
    }
}

func main() {
    var sequence = []int64{12, 11, 100, 99, 88, 23}
    bubbleSort(sequence)
    fmt.Println(sequence)
}

有时候在开发的时候,为了统一使用一个函数处理,没有泛型情况下,需要对类型进行断言处理,例如在某种情况下居然写出下面这样的代码:

func bubbleSortByInterface(sequence []interface{}) {
    switch sequence[0].(type) {

    case int64:

        var arr []int64
        for _, val := range sequence {
            arr = append(arr, val.(int64))
        }

        for i := 0; i < len(arr)-1; i++ {
          for j := 0; j < len(arr)-1-i; j++ {
                if arr[j] > arr[j+1] {
                    arr[j], arr[j+1] = arr[j+1], arr[j]
                }
          }
        }
        fmt.Println(arr)
    case float64:

        var arr []float64
        for _, val := range sequence {
            arr = append(arr, val.(float64))
        }

        for i := 0; i < len(arr)-1; i++ {
          for j := 0; j < len(arr)-1-i; j++ {
              if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j]
              }
          }
        }
        fmt.Println(arr)
    default:
        panic("type not support!!!")
    }
}

然后就可以使用interface:

func main() {
    var sequence = []interface{}{12.12, 1.1, 99.4, 99.2, 88.8, 2.3}
    bubbleSortByInterface(sequence)
    // [1.1 2.3 12.12 88.8 99.2 99.4]
}

泛型版本

接下来就是Go Generic的使用介绍了,Go支持泛型函数和泛型类型,首先我们看一下泛型函数,下面是一个标准的泛型函数标准模板:

// GenericFunc 一个标准的泛型函数模板
func GenericFunc[T any](args T) {
	  // logic code
}

函数签名里面多了[T any]部分,这就是Go泛型的参数列表,其中T就是参数,any为参数的约束。

跑起来这个泛型函数,可以正常运行,但是别急,我们写一个泛型加法函数试试:

这是为什么呢?可以看到提示说:Invalid operation: a + b (the operator + is not defined on []T),这是因为我们的泛型声明T的类型约束在下面的逻辑代码里面进行数值运算符操作,如果大家写过Java里面的泛型都知道如果做数值比较操作,那我们的泛型类型参数还要写成<T extends Comparable>才能正常工作,这就是对不能进行数值运算符操作的类型进行规避操作,同理Go里面也是一样的。

为此Go语言在泛型中引入一个叫类型集合概念,下面我们改造一下代码:

type MyInt int8

// 注意看~int8
func add[T int64 | float64 | ~int8](a, b T) T {
	  return a + b
}

func main() {
    // 限制底层数据类型
    fmt.Println("MyInt 1 + 2 = ",add[MyInt](1,2))
}

可以看到上面可以正常运行得到正确的结果,但是有一个问题如果我们是通过内置的数据取一个类型别名怎么办?也就是以前我通过type xx int8这样的代码,泛型该如何限制呢?官方泛型里面映入一个~内置符号,这个符号会限制泛型参数底层是基于某种类型实现的变体或者别名,例如下面我这段代码:

func bubbleSortByGeneric[T int64 | float64](sequence []T) {
    for i := 0; i < len(sequence)-1; i++ {
        for j := 0; j < len(sequence)-1-i; j++ {
            if sequence[j] > sequence[j+1] {
                sequence[j], sequence[j+1] = sequence[j+1], sequence[j]
            }
        }
    }
}

好了,通过上面一通操作,就知道了Go泛型怎么玩了,下面我把刚刚最开始那个排序算法通过泛型改写一下如下:
 

func bubbleSortByGeneric[T int64 | float64](sequence []T) {
    for i := 0; i < len(sequence)-1; i++ {
        for j := 0; j < len(sequence)-1-i; j++ {
            if sequence[j] > sequence[j+1] {
                sequence[j], sequence[j+1] = sequence[j+1], sequence[j]
            }
        }
    }
}

其实这个排序算法已经改写成了泛型版本,并且约束了参数的数据类型也可以认为类型的行为特征约束:

func main() {
    var sequence1 = []int64{100, 23, 14, 66, 78, 12, 8}
    bubbleSortByGeneric(sequence1)
    fmt.Println(sequence1)
    var sequence2 = []float64{120.13, 2.3, 112.3, 66.5, 78.12, 1.2, 8}
    bubbleSortByGeneric(sequence2)
    fmt.Println(sequence2)
}

结果为下图:

上面编写的泛型示例都是基于泛型函数进行的,但是我们有时候编程需要定义一些复合数据类型的数据结构,例如一个stack结构就是内部的value值类型不一样,如下的代码:

type Element interface {
	  int64 | float64 | string
}

type Stack[V Element] struct {
    size  int
    value []V
}

上面的代码我们就通过泛型编程定义一个value类型只能为Element类型集合的Stack结构,Stack[V Element]的中括号里面的就是泛型约束条件。

接着给Stack添加行为方法,方法签名上的s *Stack[V]就代表是一个泛型的Stack结构。

func (s *Stack[V]) Push(v V) {
    s.value = append(s.value, v)
    s.size++
}

func (s *Stack[V]) Pop() V {
    e := s.value[s.size-1]
    if s.size != 0 {
        s.value = s.value[:s.size-1]
        s.size--
    }
    return e
}

使用就和函数泛型差不多,在中括号里面指定泛型类型:

func main() {

    // INT STACK
    strS := Stack[int64]{}
    strS.Push(1)
    strS.Push(2)
    strS.Push(3)
    fmt.Println(strS.Pop())
    fmt.Println(strS.Pop())
    fmt.Println(strS.Pop())

    // FLOAT STACK
    floatS := Stack[float64]{}
    floatS.Push(1.1)
    floatS.Push(2.2)
    floatS.Push(3.3)
    fmt.Println(floatS.Pop())
    fmt.Println(floatS.Pop())
    fmt.Println(floatS.Pop())

}

另外一种就是特殊比较约束,也就是上面我所的Java里面的<T extends Comparable>,比如有时候我们需要限制某个参数是否可以比较或者支持某特征某个行为,例如可比较comparable关键字:

func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

func whoisMin[T Number](a, b T) T {
    if a < b {
       return a
    }
    return b
}

func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[int8]float64{
        -128: 35.98,
        127:  26.99,
    }

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))

    fmt.Println(whoisMin[int64](100, 1000))
}

上面的comparable关键字就可以限制mapKey的类型是否为可比较的,说到这里我估计过不了多久某些卷王肯定又要跑去分析一下comparable底层实现了,我其实挺反感这样的卷王的,我不知道某些没事做的就跑去分析一下底层代码实现卷王有什么优越感???除了面试八股一下,难到你要去实现GO编译器吗?

不用分析了,我顺带说一下,其实comparable底层实现也就是基于一个interface的,只是里面有一个行为方法而已,Go内置的可比较数据类型在更新之后会默认实现comparable,这个如果大家使用其他编程语言的泛型,想一想也就知道怎么实现了,当前一些开发集成环境还没有更好支持,可能格式化代码存在一些问题,不过可以忽略,泛型程序写起来还是挺流畅的,其实我感觉泛型参数指定的时候使用<T>比较好一点,用[T]这种在某种情况下给人一种从map里面运行函数调用一样,不过这个我猜测可能和底层实现有点关系,把生成的通用代码放到map里面,而泛型约束就是map的键.

本文章实例代码仓库:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Golang 是不支持的,所以你不能使用。但是你可以使用类似于的方法来实现类似的效果。 你可以使用 interface{} 类来表示任何类。例如: ``` func Print(a interface{}) { fmt.Println(a) } ``` 这样就可以使用 Print 函数输出任何类的值了。 你也可以使用反射来实现类似的效果。例如: ``` func Print(a interface{}) { v := reflect.ValueOf(a) fmt.Println(v) } ``` 这样就可以使用 Print 函数输出任何类的值了。 你也可以使用模板函数来实现类似的效果。例如: ``` func Print(a interface{}) { t := template.Must(template.New("").Parse("{{.}}")) t.Execute(os.Stdout, a) } ``` 这样就可以使用 Print 函数输出任何类的值了。 希望这对你有帮助。 ### 回答2: 截至2021年1月,Go语言(Golang)尚不具备内建的机制,即没有直接的方法来创建通用的数据结构和算法。然而,Go社区一直在探索如何在缺少的情况下实现通用性。 在没有的情况下,开发者通常采用特定类的接口(interface{})或类断言(type assertion)来实现类似的效果。使用接口可以定义通用的函数签名,使函数能够接受多种类参数。同时,使用类断言可以在运行时检查参数的实际类,并进行相应的处理。 此外,Go语言还提供了一些通用的数据结构和算法库,如container包中的List、HashMap等。通过在这些数据结构和算法中使用interface{}类,并利用类断言来确保类的正确性,可以实现相对通用的功能。 Go语言的开发团队也一直在推动的研究和开发。在2022年将发布的Go 1.18版本中,预计将加入的支持。该特性将为开发者提供使用更为简洁和安全的方式来创建通用的数据结构和算法。 总之,目前在Go语言中还没有内建的机制,但可以通过接口和类断言来实现类似的功能。此外,Go语言的开发者正在努力研发并计划在未来的版本中添加支持。 ### 回答3: 目前(截至2021年),Go语言(Golang)尚不支持原生,这意味着在编写Go代码时,无法直接声明或方法。然而,Go社区一直在积极探索和讨论如何实现,有一些可选方案可以用来模拟的使用。 其中,最常见的一种方法是使用接口类实现。通过创建一个接口类并在函数签名或结构体中使用该接口类作为参数或字段,可以实现对各种类的参数和字段的通用操作。这种方法虽然可以达到类似的效果,但在类实参和类断言方面存在一些限制和复杂性。 另一种方法是使用代码生成工具模拟。通过在编译时使用代码生成工具,可以根据不同的类参数生成特定的代码,并将其插入到源代码中。这种方式可以实现在编译时生成针对不同类的特定代码,从而实现类似的效果。但是,这种方法需要使用额外的工具和开发流程,并且会增加代码的复杂性。 除了这些方法外,Go社区还在不断探索其他更为原生的实现方式,例如Go2的设计,该设计目前正在积极开发中。Go2通过添加函数的直接支持,将大大简化使用的过程,并提供更好的类安全和可读性。尽管Go2还未正式发布,但对于在项目中使用的需求,可以关注最新的Go官方进展和社区讨论。 总的来说,目前在Go语言中,尚不支持原生的,但可以使用接口类或代码生成工具等方式来模拟的使用。未来,随着Go2的正式发布,将会提供更为便捷和直接的方式来使用。环境和项目需求决定了选择合适的方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值