Golang 泛型学习

Golang 泛型

今天来学习下Golang中泛型的基础知识。使用泛型,开发者可以声明一个函数来适应不同类型的参数,避免由于参数类型不一致而声明多个处理逻辑类似的函数。在本教程中,将声明两个简单的非泛型函数,然后在单个泛型函数中实现相同的逻辑。

基本要求

  • Go 1.18 及更高版本
  • 合适的编译工具 - text编辑器也满足要求
  • 命令终端 - Linux、Mac系统shell, Windows系统的Cmd、PowerShell

创建目录

新建代码目录

打开shell终端,使用以下命令创建文件夹,作为后续代码案例的根目录

$ mkdir generics
$ cd generics

初始化项目

使用go 初始化命令,对项目进行初始化

$ go mod init example/generics

在这里插入图片描述

项目初始化之后,就可以编写一些简单的示例代码,为了方便对比非泛型函数与泛型函数之间的区别,我们先学习一下非泛型函数使用过程中的缺陷。

非泛型函数

在这个章节中,增加两个函数,每个函数将参数值进行累加并返回。在以下的示例中,由于参数类型的不同(一个是int64类型参数,一个是float64类型参数),开发者必须声明两个函数满足业务的要求(即时函数内部的逻辑处理都一致)

逻辑代码

新增main.go代码文件,代码内容如下

package main

import "fmt"

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

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[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

执行代码

# 使用以下命令运行代码 可以看到两个函数计算的total值
$ go run main.go

在这里插入图片描述

仔细分析下SumInts、SumFloats函数,其内部的处理逻辑几乎一致,声明变量,遍历map,并对map内部的Value只进行累加,返回结果。唯一的区别是参数Map的类型不一致,对开发者而言,虽然粘贴-复制的工作简单,能够快速的实现以上功能。但是其价值不高,而且也会在项目中造成很多冗余的代码,有没有一种方式,可以解决这种问题呢,答案就是使用泛型。

泛型函数

在本节中,我们将添加一个泛型函数,用来接收map类型的参数,不管Value值是 integer、float类型。从而使用一个函数替换之前的两个函数,让代码更加精简。

为了支持不同的类型进行累加计算,需要从两方面进行考虑

  • 函数定义 - 需要一种方法来声明函数支持的类型
  • 函数调用 - 需要一种方法来指定函数调用的类型(使用integer or float)

函数代码

// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

comparable是golang新引入的预定义标识符,comparable仅能用于泛型中的类型限定,golang中对使用comparable限制key有如下约束

  • key类型必须定义比较操作符==和!=
  • key类型必须不能是function、map或slice
  • 对于interface类型,其动态类型必须定义比较操作符
  • 不满足上述约束,则会导致运行时异常(run-time panic)

函数调用一

fmt.Printf("Generic Sums: %v and %v\n",
    SumIntsOrFloats[string, int64](ints),
    SumIntsOrFloats[string, float64](floats))

执行代码

$ go run .

在这里插入图片描述

函数调用二

在上面调用泛型函数时,指定了Map Key-Value的参数类型,其实还有一种更简单的调用方式,无需指定Map数据类型,Golang编译器也能够从函数参数中自动的进行参数推断,调用方式如下

//在main.go main函数中新增代码
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
    SumIntsOrFloats(ints),
    SumIntsOrFloats(floats))

重新执行代码,结果如下

在这里插入图片描述

类型约束

开发者可以把前面定义的约束移动到它自己的接口中,以便可以在多个地方重用它。以这种方式声明约束有助于简化代码。将类型约束声明为接口。该约束允许实现接口的任何类型。例如,如果需要在三个函数上声明类型约束接口,然后将其与泛型函数中的类型参数一起使用,则用于调用该函数的类型参数必须适用于所有方法。

声明约束

type Number interface {
    int64 | float64
}

代码说明

  • 声明要用作类型约束的Number接口类型
  • 在接口内部声明数据类型,int64 、float64

泛型函数

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

接口调用

//在main.go main函数中新增代码
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
    SumNumbers(ints),
    SumNumbers(floats))

执行代码

$ go run .

在这里插入图片描述

完整代码

package main

import "fmt"

type Number interface {
	int64 | float64
}

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
	var s int64
	for _, v := range m {
		s += v
	}
	return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
	var s float64
	for _, v := range m {
		s += v
	}
	return s
}

// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
	var s V
	for _, v := range m {
		s += v
	}
	return s
}

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

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[string]float64{
		"first":  35.98,
		"second": 26.99,
	}

	fmt.Printf("Non-Generic Sums: %v and %v\n",
		SumInts(ints),
		SumFloats(floats))

	fmt.Printf("Generic Sums: %v and %v\n",
		SumIntsOrFloats[string, int64](ints),
		SumIntsOrFloats[string, float64](floats))

	fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
		SumIntsOrFloats(ints),
		SumIntsOrFloats(floats))

	fmt.Printf("Generic Sums with Constraint: %v and %v\n",
		SumNumbers(ints),
		SumNumbers(floats))
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Golang中,泛型接口是一种接口类,可以用于处理不同类的数据。泛型接口在编程中非常有用,因为它允许我们编写可重用、灵活的代码,而无需针对特定类进行硬编码。 在Golang中,泛型接口可以通过使用空接口(`interface{}`)来实现。空接口是一个没有任何方法的接口,可以接受任何类的值。通过使用空接口,可以实现泛型的功能,使得接口可以接收任何类的数据。 使用泛型接口,我们可以在不改变接口定义的情况下,接受不同类的参数。例如,我们可以定义一个泛型接口`Container`,用于表示一个可以容纳不同类元素的容器。这个接口可以定义添加元素、删除元素以及获取元素等方法。 使用泛型接口的好处是可以编写灵活的代码,尽可能减少重复代码的编写。由于泛型接口可以处理多种类的数据,我们可以将相同的逻辑应用于不同的数据类,实现代码的重用。 然而,目前Golang没有原生支持泛型接口的功能,因此在实现泛型接口时可能需要一些额外的代码处理。一种常见的做法是使用类断言来判断接口的实际类,然后进行相应的操作。 总而言之,虽然Golang没有内置的泛型功能,但通过使用空接口和类断言,我们可以实现泛型接口从而处理不同类的数据,提高代码的重用性和灵活性。 ### 回答2: Go语言是一种静态类的编程语言,其最近的版本Go 1.18中引入了泛型接口的概念。泛型指的是在编写代码时不指定具体类,而是允许使用者在使用时根据自己的需求来指定具体的类。 在传统的面向对象编程中,常用的接口表示方式是通过接口类断言来判断对象是否实现了某个接口。但是这种方式在处理不同类的数据时需要进行类转换,不够灵活且有一定的性能损耗。 而泛型接口则可以在接口定义时使用类参数,通过类参数来指定接口的具体类。这样一来,在使用时就可以直接将对应类的数据传入接口中,无需进行类转换。 泛型接口的引入为Go语言提供了更加灵活和高效的编程方式。通过泛型接口,我们可以编写更加通用和复用的代码。它还能帮助我们更好地约束函数和数据类之间的关系,提高代码的健壮性和可读性。 不过需要注意的是,泛型接口的引入也会带来一定的复杂性。在使用泛型接口时,我们需要仔细考虑类参数的合理性和边界条件,并且需要充分测试以确保代码的正确性。 总之,引入泛型接口是Go语言进一步发展的一大步。它提供了更多的编程方式,并且可以在一定程度上简化代码和提高效率。希望未来随着泛型接口的进一步成熟和普及,我们可以看到更多高质量、灵活和通用的Go代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值