Go的简单入门:开始使用泛型

开始使用泛型

一、说明

这篇文章介绍Go中基本的泛型。使用泛型,你能声明和使用函数或者类型,它们被写用来和被调用者代码提供的任意集合类型一起工作。

在这篇指导中,你将声明两个简单的非泛型函数,然后在单个泛型函数中获取一些逻辑。

你将通过下面的部分进行处理:

  1. 为你的代码创建一个目录;
  2. 添加非泛型函数;
  3. 添加泛型函数去处理多种数据类型;
  4. 当调用泛型函数的时候,移除类型参数;
  5. 声明一个类型的约束;

二、准备

  1. 安装Go1.18 及以上版本;
  2. 一个用于编写你的代码的工具;
  3. 一个命令行终端;

三、为你的代码创建一个目录

为了开始,创建一个目录为你将要写的代码。

  1. 打开命令行终端,切换到主目录

    $ cd
    $
    
  2. 通过命令行,为你的代码创建一个目录,叫做generics

    $ cd generics/
    $ cd generics
    $
    
  3. 创建一个模块用于持有你的代码

    $ go mod init example/generics
    go: creating new go.mod: module example/generics
    $
    

    注意,对于生产代码,你会指定一个更符合你自己需求的模块路径。

接下来,你会添加一些代码,用于处理maps。

四、添加非泛型函数

在这一步中,您将添加两个函数,每个函数将map的值相加并返回总数。

你将声明两个函数,而不是一个,因为你将使用两种不同的数据类型去工作:一个存储int64值,一个存储float64值。

4.1 写代码

  1. 使用你的编译器,在generics文件夹中创建一个main.go文件。你将在这个文件中写代码。

  2. 进入main.go,在文件的顶部,粘贴下面的包声明:

    package main
    

    一个独立的程序总是在main 包中。

  3. 在包声明下面,粘贴下面的两个函数声明

    // SumInts 将m的值添加在一起
    func SumInts(m map[string]int64) int64 {
      var s int64
      for _,v := range m {
        s += v
      }
      return s
    }
    
    // SumFloats 将m的值添加在一起
    func SumFloats(m map[string]float64) float64 {
      var s float64
      for _, v := range m {
        s += v
      }
      return s
    }
    

    在这个代码中:

    • 声明了两个函数,为了将map中的值添加到一块,并返回和:
      • SumFloats 获取一个map: String到float64 的值;
      • SumInts 获取一个map: String到int64的值;
  4. main.go文件的顶部,包声明的下面,粘贴下面的main函数,去初始化两个map,并使用它们作为参数,调用你在前面步骤中声明的函数

    func main() {
      // 为Int 值初始化一个map
      ints := map[string]int64 {
        "first" : 34,
        "second" : 21,
      }
      // 为float值 初始化一个map
      floats := map[string]float64 {
        "first" : 35.98,
        "second" : 26.99,
      }
      
      fmt.Printf("非泛型 Sums:%v and %v\n", SumInts(ints), SumFloats(floats))
    }
    

    在这个代码中:

    • 初始化一个float64和一个int64的map,每一个都有两项;
    • 调用你声明的两个函数,查询每一个结果的值;
    • 打印结果;
  5. 在靠近main.go的顶部,仅仅在包声明的下面,导入需要支持你刚刚写的代码的包

    第一行代码看起来像这样:

    package main
    import "fmt"
    
  6. 保存main.go文件

4.2 运行代码

在包含main.go文件的目录的命令行,运行代码:

$ go run .
非泛型 Sums:55 and 62.97
$

使用泛型,你可以写一个函数代替两个。接下来,你将添加一个泛型函数,map既可以是float,又可以是integer。

五、添加泛型函数,用于处理多类型

在这一部分,你将添加一个单个泛型函数,它可以接收包含integer或float值的map。有效的使用单个函数替换了你刚刚写的两个函数。

为了支持两种数据类型,单个函数需要有一种方法声明它支持的数据类型。调用代码,另一个方面,将需要一种方法明确它正在调用一个integer的map或一个float的map。

为了支持这一点,您将编写一个除了普通函数参数之外还声明类型参数的函数。这种类型参数让函数泛型化,使其能够处理不同类型的参数。你将使用类型参数和普通的函数参数来调用函数。

每个类型参数都有一个类型的约束。充当类型参数的元类型。每一个类型约束指明了允许的类型参数,调用代码能够使用,对于各自的类型参数。

尽管一个类型参数的约束通常代表着一个类型集合。在编译的时候,编译时类型参数代表单一类型——类型被提供做为被调用代码的类型参数。如果类型参数的约束不允许类型参数的类型,代码将不能编译。

记住,类型参数必须支持泛型代码对其执行的所有操作。例如,如果你的函数试着在类型参数上进行string操作(例如索引),它如果包含数值类型,代码将不能编译。

在你将写的代码中,你将使用一个约束,即允许integer类型,又允许float类型。

5.1 写代码

  1. 在上一部分你添加的两个函数下面,粘贴下面的泛型函数。

    // SumIntsOrFloat 获取m值的和。它既支持int64类型,也支持float64类型
    func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
      var s V
      for _, v := range m {
        s += v
      }
      return s
    }
    

    在这个代码中:

    • 声明了一个SumIntsOrFloats函数,带有两个类型参数(在中括号里面),KV,和一个参数,使用的参数,一个类型为map[K]Vm。函数返回一个V类型的值。
    • 指定了K的类型参数,约束comparable。专门针对此类型,comparable约束,是Go的预定义约束。它允许任何类型的值能够被用于作为比较运算符==!=的操作数。Go需要map的key是可比较的。因此声明Kcomparable是必须的,以便于你能使用K作为key在map变量中。它还确保调用代码对映射键使用允许的类型。
    • 指定V类型参数一个约束,是两种类型的并集:int64 和 float64。使用|指明两种类型的并集。意味着这种约束允许两种类型的任意个。编译器将允许任一类型作为调用代码中的参数。
    • 指定了一个m参数,它的类型是map[K]V,K和V就是指定的类型参数。注意,我们知道map[K]V是一个有效的map类型,因为K是一个可比较类型。如果我们没有声明K为comparable,编译器将拒绝指向map[K]V的引用。
  2. main.go文中,在你已经写的代码下面,粘贴下面的代码

    fmt.Printf("泛型求和: %v 和 %v\n", SumIntsOrFloats[string, int64](ints), SumIntsOrFloats[string, float64](floats))
    

    在这个代码中:

    • 调用泛型函数,传递你创造的map;

    • 指定了类型参数:类型的名称在中括号里。清楚在你要调用的函数中,要替换的类型参数。

      就像你在下部分将看到的,在函数调用中,你通常省略类型参数。Go能够从你的代码中推断。

    • 打印从函数返回的和。

5.2 运行代码

在包含main.go文件目录的命令行下,运行代码:

$ go run .
非泛型 Sums:55 and 62.97
泛型求和: 5562.97
$

为你运行你的代码,在每次调用中,编译器将使用调用中的具体的类型替换类型参数。

在调用你写的泛型函数时,你指定了类型参数。它告诉编译器,什么类型将替换函数的类型参数。正如你在下部分看到的,很多场景下,你能忽略这些类型参数,因为编译器能推断它们。

六、在调用泛型函数的时候,移除类型参数

在这一部分,你将添加一个修改版本的泛型参数调用,制造一个聪明的改变去简化调用代码。你将移除类型参数,它们在这些场景下不需要。

当Go编译器能够推断你使用的类型参数时,你可以忽略类型参数。编译器从函数参数类型推断类型参数。

注意,它并不是总是可能的。例如,如果你需要调用没有参数泛型函数。你在函数调用的时候,需要包含类型参数。

6.1 写代码

  • main.go文件中,在上面的代码下面,粘贴下面的代码:

    fmt.Printf("泛型求和,推断类型参数: %v 和 %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
    

    在这个代码中:

    • 调用泛型函数,省略了类型参数。

6.2 运行代码

在包含main.go文件的目录下,运行下面的代码:

$ go run .
非泛型 Sums:55 and 62.97
泛型求和: 5562.97
泛型求和,推断类型参数: 5562.9
$

七、定义类型约束

在最后一部分,你将移动你早期定义的约束到你自己的接口中,以便于你重复使用它们在多个地方。以这种方式声明约束有助于简化代码,例如当约束更复杂时。

你能声明一个类型约束作为一个接口。这个约束允许任何类型实现接口。例如,如果你声明一个类型约束接口,带有三个方法。那么使用它,在一个带有一个类型参数的泛型函数中,用于调用函数的类型参数必须具有所有这些方法。

约束接口也能指向特定的类型,就像你在这部分看到的。

7.1 写代码

  1. main函数上面,导入语句下面,粘贴下面的类型参数去声明一个类型约束。

    type Number interface {
      int64 | float64
    }
    

    在代码中:

    • 声明了Number接口类型,用于作为类型约束;

    • 在接口的里面声明一个int64和float64的并集;

      本质上,你是移动了并集从函数声明中,进入新的类型约束。这种方法,当你想要约束一个类型参数成为int64或float64,你能使用Number类型约束代替写int64 | flat64

  2. 在你已经声明的函数下面,粘贴下面的泛型函数

    // SumNumbers 对m的值求和。它既支持integer,又支持floats 作为map的值
    func SumNumbers[K comparable, V Number](m map[K]V) V {
    	var s V
    	for _, v := range m {
    		s += v
    	}
    	return s
    }
    

    在这个代码中:

    • 声明泛型函数带有和上部分定义的泛型函数相同的逻辑,除了使用新的接口类型替换并集作为类型约束。之后,你能使用类型作为参数和返回值类型。
  3. main.go文件中,在已经存在的代码下面,粘贴下面的代码:

    fmt.Printf("泛型求和,使用约束: %v 和 %v\n", SumNumbers(ints), SumNumbers(floats))
    

    在这个代码中:

    • 对每一个map,调用SumNumbers,打印每个值

      就像上一部分,在调用泛型函数的时候省略了类型参数。编译器能够推断出类型参数。

7.2 运行代码

在包含main.go文件目录的命令行下,运行代码:

$ go run .
非泛型 Sums:55 and 62.97
泛型求和: 5562.97
泛型求和,推断类型参数: 5562.97
泛型求和,使用约束: 5562.97
$
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值