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


    开始使用泛型

    一、说明

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

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

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

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

    二、准备

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

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

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

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

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

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

      $ go mod init example/generics
      go: creating new go.mod: module example/generics
      $
      
      • 1
      • 2
      • 3

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

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

    四、添加非泛型函数

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

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

    4.1 写代码

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

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

      package main
      
      • 1

      一个独立的程序总是在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
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      在这个代码中:

      • 声明了两个函数,为了将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))
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      在这个代码中:

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

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

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

    4.2 运行代码

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

    $ go run .
    非泛型 Sums:55 and 62.97
    $
    
    • 1
    • 2
    • 3

    使用泛型,你可以写一个函数代替两个。接下来,你将添加一个泛型函数,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
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      在这个代码中:

      • 声明了一个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))
      
      • 1

      在这个代码中:

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

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

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

      • 打印从函数返回的和。

    5.2 运行代码

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

    $ go run .
    非泛型 Sums:55 and 62.97
    泛型求和: 5562.97
    $
    
    • 1
    • 2
    • 3
    • 4

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

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

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

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

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

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

    6.1 写代码

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

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

      在这个代码中:

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

    6.2 运行代码

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

    $ go run .
    非泛型 Sums:55 and 62.97
    泛型求和: 5562.97
    泛型求和,推断类型参数: 5562.9
    $
    
    • 1
    • 2
    • 3
    • 4
    • 5

    七、定义类型约束

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

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

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

    7.1 写代码

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

      type Number interface {
        int64 | float64
      }
      
      • 1
      • 2
      • 3

      在代码中:

      • 声明了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
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      在这个代码中:

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

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

      在这个代码中:

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

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

    7.2 运行代码

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

    $ go run .
    非泛型 Sums:55 and 62.97
    泛型求和: 5562.97
    泛型求和,推断类型参数: 5562.97
    泛型求和,使用约束: 5562.97
    $
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    Redis 速度快的原因
    windows结束进程并定时重启应用bat脚本
    OpsWorks
    websocket
    【数据库】mysql索引
    为什么哈希加密后还要加盐?
    HTTP协议报文格式
    代码随想录 第8章 二叉树
    python+request+excel做接口自动化测试
    [StartingPoint][Tier1]Crocodile
  • 原文地址:https://blog.csdn.net/hefrankeleyn/article/details/126571682